is it actually invalid to invoke a `Continuation` ...
# coroutines
j
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
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
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
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
Check exception? Are you using java?
j
It's passing through a library written in Java, yes
d
Send me a link when you're done. This sounds interesting.
j
Will do! (should be tomorrow)
m
Post it to this thread
i
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
It really seems bad...
Not something the framework has been engineered towards either, understandably
e
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
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
Ah.... that is more complicated indeed. Some food for thought.