frankelot
08/27/2021, 7:50 AMsuspending
function is transformed into a regular function that takes an additional parameter Continuation<OriginalReturnType>
... (emphasis in OriginalReturnType
)
But then in the second screenshot, the same Continuation
seems to be used to call multiple suspending functions which different return types (requestToken
and createPost
)
I would think suspend fun requestToken
is transformed to requestToken(Continuation<Token>)
and that suspend fun createPost
should be createPost(Continution<Post>)
right? So how come we can pass sm
into both?louiscad
08/27/2021, 8:14 AMfrankelot
08/27/2021, 8:19 AMfrankelot
08/27/2021, 8:20 AMsuspending
function gets called, it gets the "parent" state machine continuation... but also, this suspending function creates its own (to pass it down to it's children)frankelot
08/27/2021, 8:21 AMlouiscad
08/27/2021, 8:22 AMfrankelot
08/27/2021, 8:24 AMreturn
statements in the decompiled code, I thought they would be replaced by continuation.resumeWith(...)
frankelot
08/27/2021, 8:25 AMresumeWith
is nowhere to be seenfrankelot
08/27/2021, 8:26 AMlouiscad
08/27/2021, 8:31 AMfrankelot
08/27/2021, 8:37 AMlouiscad
08/27/2021, 8:39 AMResult
inline class. You might be interested in the coroutines KEEP document (I also read it a few years ago).louiscad
08/27/2021, 8:39 AMfrankelot
08/27/2021, 8:42 AMfrankelot
08/27/2021, 8:42 AMraulraja
08/27/2021, 2:14 PMdecompile java
with IDEA the codegen the compiler is generating. Suspend function application is desugared into a state machine loop with labels where COROUTINE_SUSPENDED is also handled. Here is a simple decompiled program that calls two suspend functions in main. foo2 actually suspends the coroutine and resumes it https://gist.github.com/raulraja/3f339c00ed4b5e25330f4e80700bf91bfrankelot
08/27/2021, 2:18 PMfrankelot
08/27/2021, 2:18 PMsuspend fun Bar(): Int {
delay(100)
return 1
}
and decompiled...frankelot
08/27/2021, 2:19 PMpublic final Object Bar(@NotNull Continuation var1) {
Object $continuation;
label20: {
// ... ommited code ...
$continuation = new ContinuationImpl(var1) {
// $FF: synthetic field
Object result;
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return Foo.this.Bar(this);
}
};
}
... // ... ommited code (state machine) ... ....
return Boxing.boxInt(1);
}
frankelot
08/27/2021, 2:20 PMContinuationImpl
and passes the parent continuation in the constructor parameter 🙂 ...frankelot
08/27/2021, 2:21 PMreturn
statements chained between parent and child suspending function
frankelot
08/27/2021, 2:22 PMObject
because it's a union between T and a singleton SUSPENDING
.. but I don't get how that bit worksraulraja
08/27/2021, 2:27 PMsuspend foo(): Unit
suspend foo2(): Unit // this one calls suspendCoroutine { resumeWith(Unit) }
https://gist.github.com/raulraja/3f339c00ed4b5e25330f4e80700bf91b#file-suspendtestkt-decompiled-java-L78-L84
Here you can see how it checks the result of the function and it may actually be equal to the constant COROUTINE_SUSPENDEDraulraja
08/27/2021, 2:30 PMthrowOnFailure
and also extracts values from it with getOrThrow
frankelot
08/27/2021, 2:33 PMfrankelot
08/27/2021, 2:33 PMfrankelot
08/27/2021, 2:35 PMlaunch { foo() }
to see what the coroutine does with thisephemient
08/28/2021, 6:38 AMephemient
08/28/2021, 6:40 AMraulraja
08/28/2021, 7:19 AMMatthias Geisler
08/28/2021, 9:52 AMIlmir Usmanov [JB]
08/28/2021, 3:17 PMsm
into both?
As explained by @louiscad we erase type parameters of generics.
2. is the parent continuation stored inside the child continuation?
Yes. Every generated continuation class has $completion field, which is a parent continuation.
3. Im actually surprised to see return
statements in the decompiled code
There two ways a suspend function can be called. Directly (by calling the function) and using continuation.resumeWith method. At first, all suspend functions are called directly, then they either suspend (by returning COROUTINE_SUSPENDED marker) or return. If they simply return, there is no need to call $completion.resumeWith. If they suspend, we need to resume them first, by calling continuation.resumeWith method. The method calls the suspend function, and when the function returns, it returns to continuation.resumeWith, which, in turn, calls $completion.resumeWith (remember, $completion is the parent continuation) and the cycle continues - it in turn calls its suspend function, waits for its completion, calls its $completion.resumeWith and so on until we reach root continuation, which is defined in a builder function (async, runBlocking etc). More on than in Continuation Passing Style section of the aforementioned documentation. https://github.com/JetBrains/kotlin/blob/f91688d1a338d54649bfde1fe1e71b8a5108e91b/[…]c/org/jetbrains/kotlin/codegen/coroutines/coroutines-codegen.md
4. how are the return
statements chained between parent and child suspending function
See 3.
5. so what happens at the root 🤔 where is this handled
Well, it reaches the builder function (async) and simply... ignored. So the execution returns to the caller of the builder. The coroutine (in a broad sense) is suspended as well as all the suspend functions, forming it.
So, basically, there are two main parts of how coroutines work - suspend and resume. When a coroutine suspend (by calling suspendCoroutine and alike), it returns COROUTINE_SUSPENDED marker, which bubble through call stack, reaching a builder function, where it is ignored and the execution returns to non-coroutine code.
When we resume a coroutine, we call continuation.resumeWith. It runs the rest of suspend function and then calls $completion.resumeWith. Again, all this bubbles till the builder function.frankelot
08/29/2021, 7:13 PMBaseContinuationImpl#resumeWith
frankelot
08/29/2021, 7:15 PMstackoverflows
(just a guess)frankelot
08/29/2021, 7:21 PM