Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin coroutine can't handle exception

I was playing around with coroutines and found some very strange behavior. I want to convert some asynchronous requests in my project using suspendCoroutine(). Here's piece of code showing this problem.

In first case, when suspend function is being called in runBlocking coroutine, exception from continuation goes to catch block, and then runBlocking finishes successfully. But in second case, when creating new async coroutine, exception goes through catch block and crashes the whole program.

package com.example.lib

import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

object Test {
    fun runSuccessfulCoroutine() {
        runBlocking {
            try {
                Repository.fail()
            } catch (ex: Throwable) {
                println("Catching ex in runSuccessfulCoroutine(): $ex")
            }
        }
    }

    fun runFailingCoroutine() {
        runBlocking {
            try {
                async { Repository.fail() }.await()
            } catch (ex: Throwable) {
                println("Catching ex in runFailingCoroutine(): $ex")
            }
        }
    }
}

object Repository {
    suspend fun fail(): Int = suspendCoroutine { cont ->
        cont.resumeWithException(RuntimeException("Exception at ${Thread.currentThread().name}"))
    }
}


fun main() {
    Test.runSuccessfulCoroutine()
    println()

    Test.runFailingCoroutine()

    println("We will never get here")
}

That's what is printed on console:

Catching ex in runSuccessfulCoroutine(): java.lang.RuntimeException: Exception at main

Catching ex in runFailingCoroutine(): java.lang.RuntimeException: Exception at main
Exception in thread "main" java.lang.RuntimeException: Exception at main
    at com.example.lib.Repository.fail(MyClass.kt:32)
    at com.example.lib.Test$runFailingCoroutine$1$1.invokeSuspend(MyClass.kt:22)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
    at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236)
    at kotlinx.coroutines.EventLoopBase.processNextEvent(EventLoop.kt:123)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:69)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:45)
    at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
    at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:35)
    at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
    at com.example.lib.Test.runFailingCoroutine(MyClass.kt:20)
    at com.example.lib.MyClassKt.main(MyClass.kt:41)
    at com.example.lib.MyClassKt.main(MyClass.kt)

Process finished with exit code 1

Any ideas why this is happening - is it a bug, or am i using coroutines the wrong way?

Update:

Using coroutineScope { ... } will mitigate problem in runFailingCoroutine()

fun runFailingCoroutine() = runBlocking {
    try {
        coroutineScope { async { fail() }.await()  }
    } catch (ex: Throwable) {
        println("Catching ex in runFailingCoroutine(): $ex")
    }
}
like image 336
Alexander Sitnikov Avatar asked Nov 10 '18 11:11

Alexander Sitnikov


People also ask

How do you handle exceptions in coroutine Kotlin?

A generic way to handle exception in kotlin is to use a try-catch block. Where we write our code which might throw an exception in the try block, and if there is any exception generated, then the exception is caught in the catch block.

How do you catch exceptions in Kotlin?

In Kotlin, we use try-catch block for exception handling in the program. The try block encloses the code which is responsible for throwing an exception and the catch block is used for handling the exception. This block must be written within the main or other methods.

Is Kotlin coroutine reactive?

Utilities for Reactive Streams. If these adapters are used along with kotlinx-coroutines-reactor in the classpath, then Reactor's Context is properly propagated as coroutine context element ( ReactorContext ) and vice versa.

What is withContext in Kotlin?

withContext is a scope function that allows us to create a new cancelable coroutine. If we pass a CoroutineContext arg, withContext merges the parent context and our arg to create a new CoroutineContext, then executes the coroutine within this merged context.


Video Answer


1 Answers

I got struck by this behavior just yesterday, here's my analysis.

In a nutshell, this behavior is desired because async does not have the same purpose as in other languages. In Kotlin you should use it sparingly, only when you have to decompose a task into several subtasks that run in parallel.

Whenever you just want to write

val result = async { work() }.await()

you should instead write

val result = withContext(Default) { work() }

and this will behave the expected way. Also, whenever you have the opportunity, you should move the withContext call into the work() function and make it a suspend fun.

like image 141
Marko Topolnik Avatar answered Sep 24 '22 17:09

Marko Topolnik