Hi everyone. I have a bit of a crazy reflection re...
# coroutines
p
Hi everyone. I have a bit of a crazy reflection related coroutine question. I am in the process of creating dynamic proxy classes on the JVM. I would like these proxies to be able to have
suspend
function, which our
InvocationHandler
then wraps and ends up calling another function marked with
suspend
. I found this article on how handle dynamic proxies, however it seems to be calling the same method on a different implementation of the same interface. Whereas I'm trying to map to a different class. Is there a way I can pass in the continuation provided by the caller onto the suspend function from kotlin? I image I could do it from java, but I would rather not write any java code. Thanks!
y
kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
is likely what you want
p
not sure that works for me, as it's marked
suspend
. I will need to make the call from a non-
suspend
function, the
InvocationHandler
.
y
Ohhh, well the continuation should be passed to your Invocationhandler as the last argument I believe, then you can either call the method reflectively or do
createCoroutineUnintercepted
or something like that
p
that looks more like it! Will see if it works.
do I just return the result of that call in the
InvocationHandler
?
y
Well actually doing
startCoroutineUninterceptedOrReturn
might be better then. Basically, coroutines have an optimization where, if they don't need to suspend, they'll return their value immediately, but if they need to suspend, they return a special marker value
COROUTINE_SUSPENDED
. So doing the
orReturn
version plugs in well with that optimization
p
ok; but then we're back at the problem of not being able to call that function?
At the moment I have a java static function that takes a continuation and calls the suspend function from there, that seems to work.
y
That one is non-suspending either. Suspend functions compile down to functions that take in a Continuation and return Any? Returning the result of
startBlahOrReturn
should do the same exact trick as well
p
I think I got it, I need to use the version that you can call on a suspending lambda
👍🏼 1
nice, that worked, it's a lot tidier than the java solution
thanks for you help here!
❤️ 1
j
Be mindful of exceptions that are thrown synchronously from functions backing a proxy, even when they're suspend. A proxy enforces the Java language exception specification, not the JVM specification. Checked exceptions must be declared, or you need to force them through the continuation with an artificial dispatch. Wrote about it here https://jakewharton.com/exceptions-and-proxies-and-coroutines-oh-my/
y
Reading this makes me a little scared because IIRC
yield
is a no-op if you use Unconfined as your dispatcher or if you have a custom continuation interceptor that doesn't offer dispatching. I'll see if I can reproduce that.
j
Oh yeah that's not the real solution
I guess I should update the post
y
Of course Unconfined and custom interceptor are very unlikely scenarios, but it'd still be very confusing if someone runs into them
p
Thanks for the pointer on exceptions. I did notice an
UndeclaredThrowableException
while debugging at some point. Will have to dig a little deeper to make sure those are handled correctly.
This seems to work correctly for me, both for the success and failure scenarios. The stacktrace looks good on the exception, and we don't get any
UndeclaredThrowableException
in there either. Suspending and resuming with the exception that was already thrown, probably comes with a cost, but it looks like it's either that or an
UndeclaredThrowableException
.
j
And that codepath is only taken when the exception is thrown synchronously. Exceptions that occur if your coroutine is redispatched will go directly to the continuation.
Wait no you're not dispatching in the
catch
block
So you still should be susceptible to the undeclaerd as written
p
do we still need to dispatch if we're not using
suspendCoroutine
?
j
If anything inside your
try
can throw synchronously, then yes
p
Just testing that, would you expect the
UndeclaredThrowableException
at that point?
I've added the
yield()
call and then using the
startCoroutineUninterceptedOrReturn
call. However, the stacktraces are the same in this code, and the code above.