question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

App exits after catching exception thrown by coroutine

See original GitHub issue

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

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:23 (12 by maintainers)

github_iconTop GitHub Comments

3reactions
kmansoftcommented, Oct 26, 2018

@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(),

3reactions
kmansoftcommented, Oct 23, 2018

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Throwing exception with coroutine crashes app instead of ...
when im throwing the exception in the CoroutineExceptionHandler my app is still crashing (with the same stack trace as before). I want to...
Read more >
Exceptions in coroutines. Cancellation and Exceptions in…
When a coroutine fails with an exception, it will propagate said exception up to its parent! Then, the parent will 1) cancel the...
Read more >
Coroutine exceptions handling | Kotlin
CoroutineExceptionHandler is invoked only on uncaught exceptions — exceptions that were not handled in any other way.
Read more >
Coroutine Exception Handling & Observability with Firebase
When an uncaught exception is thrown, its current coroutine breaks with CancellationException. By default, with the mechanism of structured concurrency, the ...
Read more >
Are You Handling Exceptions in Kotlin Coroutines Properly?
If you are a Kotlin developer, you most probably know that coroutines communicate errors in execution by throwing exceptions.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Hashnode Post

No results found