1 year ago

#139589

test-img

jox

Propagate exception thrown from Continuation::resumeWith() back to the calling await()

I have the following coroutine and corresponding Java wrapper:

suspend fun test() : String {
    delay(1000)
    return "OK"
}

@JvmOverloads
fun testWrapper(scope: CoroutineScope = CoroutineScope(Dispatchers.IO)) : Deferred<String> =
        scope.async { 
    test() 
}

The following 2 test cases:

@Test
fun test1() = runBlocking {
    try {
        test()
        throw RuntimeException("TEST EXCEPTION")
    } catch (e: Exception) {
        Log.i(TAG, "Got the exception ", e) // We get the exception, all good
    }
    assertTrue(true)
}

@Test
fun test2() = runBlocking {
    try {
        testWrapper()
        throw RuntimeException("TEST EXCEPTION")
    } catch (e: Exception) {
        Log.i(TAG, "Got the exception ", e); // We get the exception, all good
    }
    assertTrue(true)
}

Test1: Calls test() and then in the "completion" throws an exception.
Test2: Same but calls the wrapper instead (testWrapper())

In both cases the exception is caught and "Got the exception" is printed.

All good.

Now doing somewhat the same from Java:

@Test
public void testJava() {
    try {
        testWrapper().await(new Continuation<String>() {
            @NotNull
            @Override
            public CoroutineContext getContext() {
                return Dispatchers.getIO();
            }

            @Override
            public void resumeWith(@NotNull Object o) {
                Log.i(TAG, "IN CALLBACK");
                throw new RuntimeException("TEST EXCEPTION");
            }
        });

    } catch (Exception e) {
        Log.i(TAG, "Got the exception ", e);  // <---- NEVER CALLED
    }


    try {
        Thread.sleep(2000);
    } catch (Exception e) {
        assertTrue(false);
    }
}

... results in the exception in the completion not getting to the exception handler with "Got the exception" log.

Instead looks like the generic unhandled exception handler is called and the app is terminated as a result.

My questions are:

  • How do I propagate the exceptions from the resumeWith() going down to await() so I can catch them at the place where the call is made (similar to the Kotlin test)?
  • Obviously I am missing something in my understanding. What exactly is happening here when called form Java?

This is the log from the last test (testJava()):

    AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-2
    AndroidRuntime: Process: com.x.test, PID: 18038
    AndroidRuntime: kotlinx.coroutines.CoroutinesInternalError: Fatal exception in coroutines machinery for AwaitContinuation(DispatchedContinuation[Dispatchers.IO, Continuation at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:101)@2bf2e83]){Completed}@7e4e800. Please read KDoc to 'handleFatalException' method and report this incident to maintainers
    AndroidRuntime:     at kotlinx.coroutines.DispatchedTask.handleFatalException(DispatchedTask.kt:144)
    AndroidRuntime:     at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:115)
    AndroidRuntime:     at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
    AndroidRuntime:     at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
    AndroidRuntime:     at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
    AndroidRuntime:     at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
    AndroidRuntime: Caused by: java.lang.RuntimeException: TEST EXCEPTION
    AndroidRuntime:     at com.x.tests.TestsJava$4.resumeWith(TestsJava.java:223)
    AndroidRuntime:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    AndroidRuntime:     at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    AndroidRuntime:     ... 4 more

kotlin

kotlin-coroutines

coroutine

coroutinescope

0 Answers

Your Answer

Accepted video resources