Kotlinx.coroutines: Help newbies to handle exceptions in coroutines

Created on 19 May 2017  Â·  25Comments  Â·  Source: Kotlin/kotlinx.coroutines

I can't figure how to catch the exceptions thrown from a launch block. I don't understand how to use the CoroutineExceptionHandler. I think that the Try pattern is quite heavy (https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/Try.kt). The CompletableFuture wrapper works as expected (simply), but it requires the jdk8 lib.
Thanks for this masterpiece !

import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.future.await
import kotlinx.coroutines.experimental.future.future
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking
import org.junit.Ignore
import org.junit.Test
import java.util.concurrent.atomic.AtomicBoolean

class CoroutinesTest {

    class TestException: Exception("test exception")

    @Test
    @Ignore("not that way...")
    fun testExceptionOnLaunch_tryCatch() {
        runBlocking {
            val caught = AtomicBoolean(false)
            val job = launch(context) {
                delay(10)
                throw TestException()
            }
            try {
                job.join()
            } catch (e: TestException) {
                caught.set(true)
            } finally {
                assert(caught.get())
            }
        }
    }

    @Test
    @Ignore("not that way...")
    fun testExceptionOnLaunch_getCompletionException() {
        runBlocking {
            val job = launch(context) {
                delay(10)
                throw TestException()
            }
            try {
                job.join()
            } finally {
                val exc = job.getCompletionException()
                assert(exc is TestException)
            }
        }
    }

    @Test
    fun testExceptionOnFuture() {
        System.setProperty("kotlinx.coroutines.debug", "off")
        runBlocking {
            val caught = AtomicBoolean(false)
            val job = future(context) {
                delay(10)
                throw TestException()
            }
            try {
                job.await()
            } catch (e: TestException) {
                caught.set(true)
            } finally {
                assert(caught.get())
            }
        }
    }
}
docs question

Most helpful comment

You can switch to async instead of launch and use await instead of join. This way exceptions will percolated to the functions that await. I'll keep this issue open, because there is definitely a need for a more detailed explanation in docs.

All 25 comments

You can switch to async instead of launch and use await instead of join. This way exceptions will percolated to the functions that await. I'll keep this issue open, because there is definitely a need for a more detailed explanation in docs.

Hello @elizarov,
Can you provide a quick example on how to use the handleCoroutineException(coroutineContext, e) method ? Because I think it can helps with my problem :

I have a couple of coroutines running in parallels and if in one of them there is an exception being thrown, I want to retrieve the error somewhere, but I want all the coroutines to finish their jobs.
Here is an example without the error handling :

fun doStuff(someList: List<Int>) {
    try {
        doAllThings(someList)
    } catch (e: MyException) {
        // if one of the long operation throw, this should be called once, even if multiple long operations failed.
        print("something went wrong")
    }
}

fun doAllThings(someList: List<Int>) {
    runBlocking {
        someList
            .map { someValue ->
                launch(CommonPool) {
                    // they should all finish their jobs even though one of them throws
                    doSomethingLongThatCanThrow(someValue)
                }
            }
            .forEach { it.join() }
    }
}

@pdegand Just replace launch with async in the above above code and you'll get what you are looking for:

@elizarov

analyze which ones had failed

Like so?

deferred.invokeOnCompletion { it?.apply { LOGGER.error(this.message, this) } }

Much easier it just using deferred.await(). It will throw on failure and the exception (and failure) will get logged. You can do .forEach { it.await() } to get a test failure if any of them fails

What's the recommended way to continue on exception, something like a resumeWith?

The recommended way, for now, is to use try { ... } catch { ... } in the code of the coroutine. We might consider adding helper function to restart coroutine on exception in the future. Please, create a separate issue if you have any specific ideas on how this kind of API might look like.

Will do, thanks for your response.

@elizarov
to restart coroutine on exception in the future. Please, create a separate issue if you have any specific ideas on how this kind of API might look like.
~~ maybe the Akka supervision is a good candidate:
https://doc.akka.io/docs/akka/2.5/general/supervision.html

@elizarov the following code does just crash without logging anything (NetworkOnMainThreadException). We stumble over the exception handling in the coroutines all the time and it would be nice to have a more convenient solution. Or am I doing something wrong there?

private fun check() = launch(UI) {
    async(UI) {
        InetAddress.getByName(hostName).isReachable(1000)
    }.await()
}

This doesn't log anything either:

private fun check() {
    launch(UI) {
        InetAddress.getByName(hostName).isReachable(1000)
    }.invokeOnCompletion { e -> e?.let { throw e } }
}

@MariusBoepple Uncaught exceptions in launch are designed to crash your app. If you want to handle them different, you should either wrap your code with try { ... } catch { ... } or create your own context with CoroutineExceptionHandler like this:

// define your own UI context
val UI = kotlinx.coroutines.experimental.android.UI + CoroutineExceptionHandler { _, e -> 
    println(e) // don't crash, just print exception -> DON'T REALLY DO IT LIKE THIS
}

private fun check() {
    launch(UI) {
        InetAddress.getByName(hostName).isReachable(1000)
    }
}

I'm curious at what exactly you are trying to achieve? What do you want to do on exception?

@elizarov I've investigated some more and there are some interesting things happening. For testing I've set up the following Activity in an newly created project:

class MainActivity : AppCompatActivity() {

    val UI = kotlinx.coroutines.experimental.android.UI

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

        testButton.setOnClickListener {
            launch(UI) {
                crashingFunction()
            }
        }
    }

    private fun crashingFunction(): Int {
        return 23.div(0)
    }
}

On a Sony Xperia Z3 API 23 everything works as intended. The app is crashing and the stacktrace is printed to Logcat.

But run the same code on a Samsung S8 API 26 and the app is crashing but there is no stacktrace in Logcat. This is 100% reproducable but I don't know where's the root error causing this. Maybe we should create a seperate issue for that?

To answer your question what I want to archieve: If there is an exception in launch, I want the app to crash and the stacktrace printed into Logcat, so that the other developers and I know what we've done wrong during development (the printing is even more important than the crashing).

For now, I'm using your approach which works on API 26, too:

val UI = kotlinx.coroutines.experimental.android.UI + CoroutineExceptionHandler { _, t ->
    launch(kotlinx.coroutines.experimental.android.UI) { throw t }
}

Or are there any drawbacks I'm not aware of with this solution?

PS: I'm a big fan of your work and I want to use them as an integral part of our application, but I also have to convince my superiors that they work better than conventional handlers and threads 🤖

EDIT: You must launch another coroutine with context kotlinx.coroutines.experimental.android.UIto actually throw the exception on the main thread and get a crash

Hi @MariusBoepple
It's worth creating a separate issue. Exception handling in Android works via a reflective call to exception pre-handler (see AndroidExceptionPreHandler) which is loaded by ServiceLoader.
It seems like it's either not found by reflection or not properly discovered by service loader.

Could you please check whether exception pre-handler is actually found, registered/discovered and called or on which stage failure occurs?

Honestly Kotlin seems to be a crap in the long term, more like wheel reinventing.
Didn't use beautiful features of C#, Java, just created a new way of doing everything things.

So kotlin is trying to say that, i cant handle error like the popular Promise way?
example Defered.onError(//pass a lambda here),

The learning curve is fucking high, Docs didnt explain things well. all i gain is

  1. Null Safety(Java is already doing well with that)
  2. Concise (Still difficult to read a Kotlin Code, coming from a static typing world)
  3. No Performance Gain
  4. No Domain Specific Feature(like Scala in Distributed Computing)

I really want to help in the Docs, fuck coroutines. (Not really happy with this kotlin, I feeling threatened my Java was getting obsolete and wasted my time learning kotlin)

I still Love The Challenge Kotlin is giving me though, good work guys :)

Hi @Zedonboy

So kotlin is trying to say that, i cant handle error like the popular Promise way?

fun <T> Deferred<T>.onError(block: suspend (Exception) -> Unit): Deferred<T> {
    launch {
        try {
            await()
        } catch (e: Exception) {
            block(e)
        }
    }
    return this
}

Have fun

Thanks @fvasco , sometimes it takes time to learn a new language and master. My comfort zone was Javascript. Now working on a production grade Android Software with Kotlin. (Finally am falling in love)

Keep up the good work guys.

Feel free to lift Arrow's extension functions for this: https://github.com/arrow-kt/arrow/blob/master/modules/effects/arrow-effects-kotlinx-coroutines/src/main/kotlin/arrow/effects/DeferredK.kt

Replace Try with try/catch and a function catch block and it should work the same.

@MariusBoepple why does your solution work? (using another launch builder with ui context to throw an exception)
When I use launch with UI context without an exception handler app crashes without logging anything, if I add CoroutineExceptionHandler app does not crash and error is logged, and if I use your solution app is crashing and logging the error.

I'm closing this, because new "Exceptions" section in coroutines guide should address all the basic newbie questions.

Hi @Zedonboy

So kotlin is trying to say that, i cant handle error like the popular Promise way?

fun <T> Deferred<T>.onError(block: suspend (Exception) -> Unit): Deferred<T> {
    launch {
        try {
            await()
        } catch (e: Exception) {
            block(e)
        }
    }
    return this
}

Have fun

can you give an example of this extension function in action?

Yeah i finally mastered Kotlin, i asked this when i was still wrapping my
head round Kotlin. And frustration came in :)

On Fri, Oct 26, 2018, 3:08 PM Ebenezer notifications@github.com wrote:

Hi @Zedonboy https://github.com/Zedonboy

So kotlin is trying to say that, i cant handle error like the popular
Promise way?

fun Deferred.onError(block: suspend (Exception) -> Unit): Deferred {
launch {
try {
await()
} catch (e: Exception) {
block(e)
}
}
return this
}

Have fun

can you give an example of this extension function in action?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Kotlin/kotlinx.coroutines/issues/61#issuecomment-433420117,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AVIoqJuDm2IRsj9sgrvk5qtQfWapkUrvks5uoxdLgaJpZM4NgskM
.

Yeah i finally mastered Kotlin, i asked this when i was still wrapping my head round Kotlin. And frustration came in :)
…
On Fri, Oct 26, 2018, 3:08 PM Ebenezer @.*> wrote: Hi @Zedonboy https://github.com/Zedonboy So kotlin is trying to say that, i cant handle error like the popular Promise way? fun Deferred.onError(block: suspend (Exception) -> Unit): Deferred { launch { try { await() } catch (e: Exception) { block(e) } } return this } Have fun can you give an example of this extension function in action? — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#61 (comment)>, or mute the thread https://github.com/notifications/unsubscribe-auth/AVIoqJuDm2IRsj9sgrvk5qtQfWapkUrvks5uoxdLgaJpZM4NgskM .

I don't know about you, but I try to be respectful of things I don't fully understand

Getting this on my Retrofit API call
normal 403.

kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}

But
Then other API calls are not calling from here hence forth ?

Was this page helpful?
0 / 5 - 0 ratings