Caught exceptions are still propagated to the Thread.uncaughtExceptionHandler

I’ve been playing around with coroutines for a little, and most of it seems smooth except for this particular case where Caught Exceptions are still propagated to the uncaughtExceptionHandler, which obviously makes the process crash.

The crashing code looks as following:

class MainActivity : AppCompatActivity(), CoroutineScope {

override val coroutineContext: CoroutineContext
    get() = Dispatchers.Main

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    fab.setOnClickListener {
        launch {
            try {
                async {
                    delay(1000)
                    throw IllegalStateException("This is my exception")
                }.await()
            } catch (e: IllegalStateException) {
                Log.e("$this@MainActivity", "It is caught! $e ")
            }
        }
    }
}

Note that there is an async inside of a launch and both of them are part of the same scope, and share the same context.
I am throwing the exception inside of the async, and I subscribe immediately via await().
Now, the log of running this piece of code in Android is as follows:

com.efren.myapplication E/StandaloneCoroutine{Cancelling}@8ef5efd@MainActivity: It is caught! java.lang.IllegalStateException: This is my exception 

Process: com.efren.myapplication, PID: 31097
java.lang.IllegalStateException: This is my exception
    at com.efren.myapplication.MainActivity$onCreate$1$1$1.invokeSuspend(MainActivity.kt:37)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
    at kotlinx.coroutines.ResumeModeKt.resumeMode(ResumeMode.kt:67)
    at kotlinx.coroutines.DispatchedKt.resume(Dispatched.kt:272)
    at kotlinx.coroutines.DispatchedKt.dispatch(Dispatched.kt:261)
    at kotlinx.coroutines.AbstractContinuation.dispatchResume(AbstractContinuation.kt:182)
    at kotlinx.coroutines.AbstractContinuation.completeStateUpdate(AbstractContinuation.kt:250)
    at kotlinx.coroutines.AbstractContinuation.updateStateToFinal(AbstractContinuation.kt:222)
    at kotlinx.coroutines.AbstractContinuation.resumeImpl(AbstractContinuation.kt:195)
    at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuation.kt:262)
    at kotlinx.coroutines.android.HandlerContext$scheduleResumeAfterDelay$$inlined$Runnable$1.run(Runnable.kt:19)
    at android.os.Handler.handleCallback(Handler.java:789)
    at android.os.Handler.dispatchMessage(Handler.java:98)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6592)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:769)

According to my tests, this can fixed by:

  1. Replace async + await() for a withContext()
  2. Use a different CoroutineScope

Although I am new to coroutines, this seems like it is either a bug, or I am missing something in the explanation of how all the elements work combined when handling exceptions.

Could somebody shed some light into what’s happening here? Since I am trying to move an entire project to coroutines, it’d be much appreciated.

1 Like

I think this is related to Exception handling with structured concurrency (rx & async) · Issue #691 · Kotlin/kotlinx.coroutines · GitHub

So, in summary, async should NOT be used unless we want to run several things in parallel in a parent scope. The naming is indeed confusing coming back from other languages