https://kotlinlang.org logo
Title
j

jw

07/30/2019, 3:34 PM
is it actually invalid to invoke a
Continuation
in the same stack frame? or is that just a best-practice thing to avoid blowing the stack?
j

jw

07/30/2019, 3:39 PM
I don't mean inside of
suspendCoroutine
. I mean the
Continuation
parameter that is passed directly in bytecode.
This will cause the caller's state machine to resume beneath the current stack frame. Seems like it would create an invalid state since the existing state has not yet received a return value of
CORUTINE_SUSPENDED
unless it's already technically in that state. the more i think about it the worse the implications become...
d

Dominaezzz

07/30/2019, 3:40 PM
I see what you mean now.
For CPS to work. Invoking the
Continuation
in any stackframe has to be valid.
Out of curiosity, what do you need this info for?
j

jw

07/30/2019, 3:45 PM
I'm working around a bug that's caused by
suspendContinuation
throwing a checked exception synchronously that was supplied to its
Continuation
in a seemingly-asynchronous way
writing a blog post about it now!
👍 3
to be clear, it's not doing anything wrong, it's just causing some unsavory behavior in a case that seems impossible
d

Dominaezzz

07/30/2019, 3:46 PM
Check exception? Are you using java?
j

jw

07/30/2019, 3:47 PM
It's passing through a library written in Java, yes
d

Dominaezzz

07/30/2019, 3:49 PM
Send me a link when you're done. This sounds interesting.
j

jw

07/30/2019, 3:50 PM
Will do! (should be tomorrow)
m

Marko Mitic

07/30/2019, 4:00 PM
Post it to this thread
i

Ilmir Usmanov [JB]

07/30/2019, 4:35 PM
Seems like it would create an invalid state
Let the function be
foo
.
BaseContinuationImpl.resumeWith
calls
Continuation.invokeSuspend
, which calls
foo
. Since you are using the same continuation object in recursive call, after the call's completion,
BaseContinuationImpl.resumeWith
calls
completion.resumeWith
, effectively dropping the frames (since recursion is unrolled). At first glance, I see no harm in resuming at the same frame.
But, after completing the root continuation, you will return to the very first frame. The continuation object is now corrupted. The 'label' field will be recovered, but spilled variables is a different story: if they are primitives, they will recover, but references now have completely different values (and point to completely different objects). TL;DR: Do not do this, unless you are completely sure about what you are going. Even then, a compiler update with change in spilling/unspilling is likely to break the code.
Now, I am thinking about exploiting the 'feature'.
d

Dico

07/30/2019, 4:57 PM
It really seems bad...
Not something the framework has been engineered towards either, understandably
e

elizarov

07/31/2019, 1:38 PM
Great writeup! Now I fully understand your problem. Since the exception is always delivered from another thread you can also replace
suspendCoroutine
with intrinsic version without protection. It is Ok to race it with resumption from another thread: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.intrinsics/suspend-coroutine-unintercepted-or-return.html
(It is the lowest level function - it directly gives you access to continuation as if you’ve been implementing suspend function in Java code)
j

jw

07/31/2019, 1:41 PM
So the real Retrofit code is actually using
suspendCancellableCoroutine
which I don't think has an equivalent, right? I had to hand-wave over some stuff or the post would have been twice as long.
e

elizarov

07/31/2019, 3:44 PM
Ah.... that is more complicated indeed. Some food for thought.