kptlronyttcna
kptlronyttcna

Reputation: 1491

Why CoroutineExceptionHandler didn't catch/handle my exception?

In this code why handler only prints the stack-trace for JobCancellationException and not SocketException? The foo function inside launch definitely throws SocketException, so what happens to it?

suspend fun foo() {
  val job = coroutineContext[Job]!!
  val socket = Socket()

  job.invokeOnCompletion(onCancelling = true) {
    if (!socket.isClosed) {
      socket.close()
    }
  }

  // non-routable address -> timeout
  // will throw SocketException after socket.close() is called above
  socket.connect(InetSocketAddress("10.0.0.0", 1234), 2000)
}

fun test() = runBlocking {
  val handler = CoroutineExceptionHandler { _, throwable ->
    throwable.printStackTrace()
  }

  val job = launch(DefaultDispatcher + handler) {
    foo()
  }

  delay(100)
  job.cancelAndJoin()
  delay(100)
}

Upvotes: 3

Views: 5313

Answers (1)

Julian A.
Julian A.

Reputation: 11450

I can't tell you why CoroutineExceptionHandler didn't catch the exception thrown in launch. But I can tell you 2 things -

  1. I verified the behavior you found - you are correct, the exception isn't caught.
  2. By experimenting, I learned how to catch the exception in CoroutineExceptionHandler.

Here's the code that shows how to catch it:

fun f() = runBlocking {
    val eh = CoroutineExceptionHandler { _, e -> trace("exception handler: $e") }
    val cs1 = CoroutineScope(Dispatchers.Default)
    val j1 = cs1.launch(eh + CoroutineName("first"))  {
        trace("launched")
        delay(1000)
        throw RuntimeException("error!")
    }
    trace("joining j1")
    j1.join()
    val cs2 = CoroutineScope(Dispatchers.Default + eh)
    val j2 = cs2.launch(CoroutineName("second"))  {
        trace("launched")
        delay(1000)
        throw RuntimeException("error!")
    }
    trace("joining j2")
    j2.join()
    trace("after join")
}
f()

Console output:

[main @coroutine#1]: joining j1
[DefaultDispatcher-worker-1 @first#2]: launched
[DefaultDispatcher-worker-1 @first#2]: exception handler: java.lang.RuntimeException: error!
[main @coroutine#1]: joining j2
[DefaultDispatcher-worker-1 @second#3]: launched
[DefaultDispatcher-worker-3 @second#3]: exception handler: java.lang.RuntimeException: error!
[main @coroutine#1]: after join

The key takeaway is that if you call launch on a custom CoroutineScope, any CoroutineExceptionHandler provided directly to the CoroutineScope constructor or to launch will be executed when an exception is thrown within the launched coroutine.

Hope that helps!!

UPDATE

I found out why the exception isn't caught. See my answer here.

Upvotes: 2

Related Questions