kotlinx.coroutines: App exits after catching exception thrown by coroutine

I’m new to Kotlin and coroutines so maybe I’m doing something wrong, but…

The test code below throws an exception which is propagated to the caller in await().

The caller catches the exception, logs it, and ignores. So far so good.

But - immediately after this, the app exits like this, looks like the exception gets rethrown.

This is quite unexpected to me (but again I’m a novice). Do exceptions propagated by await() always get rethrown? What if I want to ignore it (show message to user, log, etc. but don’t want the exception to keep bubbling up)?

        GlobalScope.launch(Dispatchers.Main) {

            val run = async(Dispatchers.IO) {
                if (System.currentTimeMillis() != 0L) {
                    throw IOException("Test")
                }
                "foobar"
            }

            try {
                val res = run.await()
                Log.i(TAG, "Result: " + res)
            } catch (x: Throwable) {
                Log.w(TAG, "Error:", x)
            }
        }
W MainActivity: Error:
W MainActivity: java.io.IOException: Test
W MainActivity: 	at org.kman.updatechecker.MainActivity$startCheckJob$2$run$1.invokeSuspend(MainActivity.kt:99)
W MainActivity: 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
W MainActivity: 	at kotlinx.coroutines.DispatchedTask$DefaultImpls.run(Dispatched.kt:221)
W MainActivity: 	at kotlinx.coroutines.DispatchedContinuation.run(Dispatched.kt:67)
W MainActivity: 	at kotlinx.coroutines.scheduling.Task.run(Tasks.kt:94)
W MainActivity: 	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
W MainActivity: 	at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
W MainActivity: 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:732)

^^^^^ This is from test code's Log.w(TAG, "Error:", x)

E AndroidRuntime: FATAL EXCEPTION: main
E AndroidRuntime: Process: org.kman.updatechecker, PID: 8026
E AndroidRuntime: java.io.IOException: Test
E AndroidRuntime: 	at org.kman.updatechecker.MainActivity$startCheckJob$2$run$1.invokeSuspend(MainActivity.kt:99)
E AndroidRuntime: 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
E AndroidRuntime: 	at kotlinx.coroutines.DispatchedTask$DefaultImpls.run(Dispatched.kt:221)
E AndroidRuntime: 	at kotlinx.coroutines.DispatchedContinuation.run(Dispatched.kt:67)
E AndroidRuntime: 	at kotlinx.coroutines.scheduling.Task.run(Tasks.kt:94)
E AndroidRuntime: 	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
E AndroidRuntime: 	at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
E AndroidRuntime: 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:732)

^^^^^ But the exception continues to bubble up

W ActivityManager:   Force finishing activity org.kman.updatechecker/.MainActivity

^^^^^ The app is terminated

I’m using Android Studio 3.2, Kotlin 1.3.0-rc-190-Studio3.2-1 and kotlinx-coroutines-android 1.0.0-RC1

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 23 (12 by maintainers)

Most upvoted comments

@jcornaz I just tried it, yes nice that there is less “syntax noise” but (still!)…

The “recipe” you gave is very close to one found here:

https://proandroiddev.com/async-code-using-kotlin-coroutines-233d201099ff

Let me step back and restate what I’m trying to do.

Consider this all synchronous code, no coroutines, async / await’s or anything:

fun showOrder(accountId: String, orderId: OrderId) {
  val account = try {
    getAccount(accountId)
  } catch (x: Execption) {
    // Network error, whatever, inform the user
    accountView = "Error getting account info: " + x.toString()
    return
   }

  if (isCancelled) {
    return
  }

  accountView = account.formatAsText()

  val order = try {
    account.getOrder(orderId)
  } catch (x: Exception) {
    // Error, inform the user
    orderView = "Error geting order info: " + x.toString()
    return
  }

  if (isCancelled) {
    return
  }

  orderView = order.formatAsText()
}

Things to note:

  1. I am able to handle errors close to where they can occur, to show the most relevant message to the user (not just “error” but “error getting account / order info”);

  2. Cancellations (without trying to continue with updating the UI which perhaps has gone away);

  3. The operations are sequential (can’t get order object without account object), but I don’t want to make a new function that combines both and returns a single result - this is the function I’m writing here already.

The problem here is of course that getAccount / getOrder need to execute on an IO thread, and updating the UI needs to execute on the “main” (UI, GUI toolkit) thread.

Kotlin coroutines to the rescue! I can just run some parts of my code on an IO thread and other parts on the UI thread - but write the logic as if it were sequential and synchronous.

val job = SupervisorJob()
GlobalScope.launch(job + Dispatchers.Main) {
  try {
    // Get account
    val awaitAccount = withContext(Dispatchers.IO) {
      async {
          getAccount()
      }
    }

    val valAccount = try {
      awaitAccount.await()
    } catch (x: Exception) {
      // Error getting account
      accountView = "Error getting account info: " + x.toString()
      return@launch
    }

    // Got account, update the UI
    accountView = valAccount.formatAsText()

    // Get order
    val awaitOrder = withContext(Dispatchers.IO) {
      async {
        account.getOrder()
      }
    }

    val valOrder = try {
      awaitOrder.await()
    } catch (x: Exception) {
      // Error getting order
      orderView = "Error getting order info: " + x.toString()
      return@launch
    }

    // Got order, update the UI
    orderView = valOrder.formatAsText()
  } catch (x: Exception) {
    // Log / show the exception
  }
}

This magically executes every piece of code on the appropriate thread. Wonderful.

But for some reason, any exceptions thrown by getAccount / getOrder are now not caught in the catch blocks around the respective await()'s.

This is “astonitishing” again, there clearly are catch blocks around the awaits, and it’s awaits which propagate exceptions, right?

Exceptions are caught only in the top-level catch (“Log / show the exception”) now and are thrown out of async{} blocks. Before adding withContext, they were getting thrown out of await()'s.

And so, new questions if you don’t mind:

  1. Why are my exceptions now getting thrown out of async{} (vs. before withConext: out of await)?

  2. I feel like I’m stumbling in the dark, chanting magical incantations (which sometimes work, but more often turn random objects into stones or worse)…

Are there any guides that explain in more depth what’s going on?

I have read this:

https://kotlinlang.org/docs/reference/coroutines/exception-handling.html

… but it just says that “async exposes exceptions to uses” and doesn’t explain why the introduction of withContext exposes them out of async{} and not from await(),

PS:

I see in the exception guide

https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/exception-handling.md#exception-handling

that launch “propagates exceptions automatically” but async “exposes them to users”.

Indeed, changing the code snippet to use async instead of launch makes it work as I expect:

GlobalScope.async(Dispatchers.Main) {

            val run = async(Dispatchers.IO) {
                if (System.currentTimeMillis() != 0L) {
                    throw IOException("Test")
                }
                "foobar"
            }

            try {
                val res = run.await()
                Log.i(TAG, "Result: " + res)
            } catch (x: Throwable) {
                Log.w(TAG, "Error:", x)
            }

            MyLog.i(TAG, "After the exception")
        }

But but but… this still seems pretty weird to me (remember I’m a newbie at Kotlin):

  • Async is for producing a value, launch seems more appropriate for an “async job” that doesn’t return anything (but just “does something”).

Yes I know it’s possible to .await() on async and its type will be just Unit which is “void” but still strange when a particular coroutine “by its nature” doesn’t return / produce anything at all (and it can be argued that “returns nothing” isn’t quite the same as “returns void”).

  • What if there is some library code (somewhere in the coroutine) that throws?

Since Kotlin doesn’t have checked exceptions (yes I currently use Java), when writing a coroutine we won’t be “told” by the compiler that “hey this can result in an exception, be careful”

  • Last but not least: the docs talk about “propagating exceptions” - but…

There is no exception here. Yes one did occur, but it was caught and not re-thrown.

That’s there in the code, look, the exception does not escape, it’s done for, dead-ended:

catch (x: Throwable) {
    Log.w(TAG, "Error:", x)
}

I’m sure there are reasons why things are as they are –

– would it be possible to explain the rationale behind the “automatic rethrow even if caught and ignored” that’s done by launch? I don’t see anything in the official docs.

You see that it is very much the same. The only difference are the suspend keyword for the function and the use of withContext wherever needed.

Yes it is, just one thing, you switched the threads around.

As a side benefit you got better defined cancellation points (i.e. every time the code tries to reach into the UI state).

You still use async which is unnessacary in your code, and introduce the astonishing behavior.

I see it now - yesterday I misread your ViewController example, and saw something (async) which wasn’t there.

I now changed my code to use only withContext without async:

GlobalScope.launch(job + Dispatchers.Main) {
	val account = try {
		withContext(Dispatchers.IO) {
			network.getAccount()
		}
	} catch (x: Throwable) {
		// This does get caught
	}
}

It now works much more to my taste.

Not “astounding” anymore. Exceptions get propagated and caught in the nearest catch blocks.

And then the whole withContext block now returns the “real” value type (not a Deferred which then needs to be await’ed on!) which also simplifies code structure.

Two remaining problems are that exceptions from inside withContext take priority over cancellations - and that it’s necessary to catch CancellationException separately from other Throwable’s (to differentiate cancellations from errors).

But for those I bet a utility function would work just fine.

You still use async which is unnessacary in your code, and introduce the astonishing behavior.

If I translate your first (blocking) version of showOrder in an (non-blocking) equivalent with Kotlin coroutines it will be:

suspend fun showOrder(accountId: String, orderId: OrderId) = withContext(Dispatchers.IO) { // <-- Let's use IO by default here
    val account = try {
        getAccount(accountId) // <-- It's fine, we are on IO.
    } catch (x: Execption) {
        // Network error, whatever, inform the user

        withContext(Dispatchers.Main) {
            // This has to run on UI I presume...
            accountView = "Error getting account info: " + x.toString()
        }

        return@withContext
    }

    // No need to check cancellation state here. Any suspending function like `withContext` would throw a cancellation exception

    withContext(Dispatchers.Main) {
        // This has to run on UI I presume...
        accountView = account.formatAsText()
    }

    val order = try {
        account.getOrder(orderId) // <-- It's fine, we are on IO.
    } catch (x: Exception) {
        // Error, inform the user

        withContext(Dispatchers.Main) {
            // This has to run on UI I presume...
            orderView = "Error geting order info: " + x.toString()
        }

        return@withContext
    }

    // No need to check cancellation state here. Any suspending function like `withContext` would throw a cancellation exception
    
    withContext(Dispatchers.Main) {
        // This has to run on UI I presume...
        orderView = order.formatAsText()
    }
}

You see that it is very much the same. The only difference are the suspend keyword for the function and the use of withContext wherever needed. This make the code as easy to read as if it was blocking.

Of course you need to adapt the usage, since you can only call a suspending function from a coroutine:

fun handleUserAction()  {
  job = launch { showOrder(accountId, orderId) }
}

fun cancelAction() {
  job?.cancel()
}

Hi @a-kari, Both async and launch are designed for parallel decomposition, but async returns a result, whereas in launch the result is irrelevant.

However this isn’t a library issue, for this kind or discussion I suggest you to prefer different resources, like the official Kotlin forum.

I’m new to Kotlin coroutines, too. But as far as I understand, async coroutine builder is designed for parallel decomposition of some large task.

E.g. we have to combine results of two suspend functions:

suspend fun computeValue1(): Int {
    delay(1000) // Imitate some heavy computation.
    return 1
}

suspend fun computeValue2(): Int {
    delay(2000) // Imitate some heavy computation.
    return 2
}

If we just call the suspend functions to combine their results, they will be executed sequentially, so we will have to wait 3 seconds (computeValue1() execution time + computeValue2() execution time) to get a result:

val result = computeValue1() + computeValue1() // 3 seconds.

But if we wrap the suspend functions with async, then they will be executed concurrently. So the result time will be 2 seconds - i.e execution time of the longest function (computeValue2() in this case):

val value1Deferred = async { computeValue1() }
val value2Deferred = async { computeValue2() }
val result = value1Deferred.await() + value2Deferred.await() // 2 seconds.

The next question is async exception handling.

As it mentioned in #552, the parent coroutine shouldn’t wait all child asyncs to finish if one failed (waiting is makes no sense, because in this case the parent couldn’t combine children results anyway).

So, as far as I understand, structured concurrency has been introduced to async because of that (to immediately kill the parent if an exception occurs in a child async).

So I have created my solution to work with async and handle its exceptions:

JVM:

// Supervisor scope, which won't fail on some child failure.
// I have created a separate scope to have a possibility to cancel it when needed.
val supervisorScope = CoroutineScope(Dispatchers.Default + SupervisorJob())

supervisorScope.launch {...} // Child 1.
supervisorScope.launch {...} // Child 2.

// Child 3.
// If an exception occurs in some async, then it will be propagated to Child 3,
// cancel it and be consumed with Child 3's CoroutineExceptionHandler.
// Neither supervisorScope's Job, nor Child 1/ Child 2 Jobs won't be affected with
// Child 3 exception.
//
// Here we have a CoroutineExceptionHandler, because we don't want an exception
// to be propagated to Thread.uncaughtExceptionHandler
// (in JVM it will just print a log).
supervisorScope.launch(Dispatchers.IO + CoroutineExceptionHandler {...}) {
    val value1Deferred = async { computeValue1() }
    val value2Deferred = async { computeValue2() }
    val result = value1Deferred.await() + value2Deferred.await() // 2 seconds.
    // Do something with the result.
}

Android:

// viewModelScope == CoroutineScope(Dispatchers.Main + SupervisorJob())
viewModelScope.launch {...} // Child 1.
viewModelScope.launch {...} // Child 2.

// Child 3.
// If an exception occurs in some async, then it will be propagated to Child 3,
// cancel it and be consumed with Child 3's CoroutineExceptionHandler.
// Neither viewModelScope's Job, nor Child 1/ Child 2 Jobs won't be affected with
// Child 3 exception.
//
// Here we have a CoroutineExceptionHandler, because we don't want an exception
// to be propagated to Thread.uncaughtExceptionHandler
// (in Android it will kill the app).
viewModelScope.launch(Dispatchers.IO + CoroutineExceptionHandler {...}) {
    val value1Deferred = async { computeValue1() }
    val value2Deferred = async { computeValue2() }
    val result = value1Deferred.await() + value2Deferred.await() // 2 seconds.
    // Do something with the result.
}

Conclusion: async should be used only for parallel decomposition. For just doing something in other thread we should use classic launch/suspend features.

… but it just says that “async exposes exceptions to uses” and doesn’t explain why the introduction of withContext exposes them out of async{} and not from await(),

this is not about withContext. It is just the fact that async make the parent fail as soon as async is failed. That’s it. I aggree it can be astonishing when comming from other technologies. This problem is discussed in #763.

My point is “you don’t need it”. One need async only for parallel decomposition. For other uses-cases just use suspending function (without using async at all) and you will get simple code with a simple mental model, since it is the same than the one you use to write blocking code.

You probably also want to follow #763 which address the problem of async’s astonishing behavior.

Why do you need a “coroutine” to run on IO? Is it an actor? Is it supposed to have an independent life scope? Or is it just an async which do something and return a result?

An async to do something and return a result (or exception or have the whole thing cancel).

And I am expecting to find an an easy, language- and runtime- supported method of doing this “sequentially” in terms of syntax, but “asynchronously” in terms of “what really happens” - which fits what you wrote in #342

So, the whole point of Kotlin coroutines is to not use callback and future anymore letting us to write code like sequential code while keeping the benefits of future and callbacks.


Here’s an example:

Thanks, I’ll try it. Like I said, it seems that most tutorials (that I’ve seen) used launch / async so I initially started exploring in that direction.

Thank you for the clarification and for your time.

@kmansoft please consider this code, I hope this helps you.

fun main(args: Array<String>) = runBlocking {
    val job = launch {
        val deferred: Deferred<Unit> = GlobalScope.async { error("Out of scope error") }
        try {
            deferred.await()
        } catch (e: Exception) {
            println("Handling error...")
            e.printStackTrace()
        }
    }

    job.join()
    println("That's all")
}