Arst
Arst

Reputation: 3297

CoroutineScope cannot be reused after an exception thrown

I have a class that implements CoroutineScope, and it has a function run, which has one parameter for me to test if I want to throw an exception. In the example, I found the code block inside the launch in the second run call is not actually executed. Is this intended behavior? It took me some time to find out the problem in my original code because the log does not say anything until I wrote the sample code to test. If that is intended, what is the best practice to fix the problem? What I want to achieve is to reuse the run function, and be able to catch the exception inside that function.

package coroutine.exceptions

import kotlinx.coroutines.*

fun log(msg: String) = println("$msg (${Thread.currentThread().name})")
val exceptionHandler = CoroutineExceptionHandler { _, e ->
    log(e.localizedMessage)
}

fun main() = runBlocking {

    val test1 = TestReuseCoroutineAfterException("test1")
    test1.run(true)
    delay(2000)
    test1.run(false)
    delay(2000)

    log("finished")
}


class TestReuseCoroutineAfterException(private val testName: String) :
    CoroutineScope by CoroutineScope(Dispatchers.Default) {

    fun run(throwException: Boolean) {
        log("$testName: call - started")
        launch(exceptionHandler) {
            if (throwException)
                throw Exception("$testName: call - throw exception")
            else
                log("$testName: call - done")
        }
        log("$testName: call - ended")
    }

}

Output:

test1: call - started (main)
test1: call - ended (main)
test1: call - throw exception (DefaultDispatcher-worker-1)
test1: call - started (main)
test1: call - ended (main)
finished (main)

Process finished with exit code 0

Upvotes: 0

Views: 632

Answers (1)

ardenit
ardenit

Reputation: 3890

When you create a CoroutineScope like this:

CoroutineScope(Dispatchers.Default)

It uses Job() as its Job, and Job() cancels the scope if an exception was throws in child coroutine. If you don't want to cancel the whole scope if a child fails, use SupervisorJob:

CoroutineScope(Dispatchers.Default + SupervisorJob())

After this change your code prints:

test1: call - started (main)
test1: call - ended (main)
test1: call - throw exception (DefaultDispatcher-worker-1)
test1: call - started (main)
test1: call - ended (main)
test1: call - done (DefaultDispatcher-worker-2)
finished (main)

Upvotes: 4

Related Questions