coroutineScope: cancels other children when one throws Uncaught Exception (non cancellation exception)
When one child throws uncaught non-CancellationException then all other children of this scope are cancelled and coroutineScope
rethrows exception.
Uncaught will keep bubbling up
coroutineScope cancels all other children when one throws uncaught exception.
Code
package com.glassthought.sandbox
import com.glassthought.sandbox.util.out.impl.out
import gt.sandbox.util.output.Out
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.system.exitProcess
import kotlin.time.Duration.Companion.seconds
private suspend fun mainImpl(out: Out) {
coroutineScope {
foo()
}
}
private suspend fun foo() {
out.actionWithMsg("functionThatStartsCoRoutineScope()", { functionThatStartsCoRoutineScope() })
}
private suspend fun functionThatStartsCoRoutineScope() {
coroutineScope {
out.info("This coroutineScope suspends the calling function.")
async(CoroutineName("async-wait-3s")) {
out.delayNamed(3.seconds)
}
async(CoroutineName("async-throws-after-2s")) {
out.delayNamed(2.seconds, "delay before throwing")
throw MyRuntimeException.create("exc-from-async-2", out)
}
launch(CoroutineName("launch-waits-1s")) {
out.delayNamed(1.seconds)
}
out.info("Just launched co-routines")
}
}
fun main(): Unit = runBlocking {
out.info("START - ON MAIN")
try {
mainImpl(out)
} catch (e: Exception) {
out.error("back at MAIN got an exception! of type=[${e::class.simpleName}] with msg=[${e.message}] cause=[${e.cause}]. Exiting with error code 1")
exitProcess(1)
}
out.info("DONE - WITHOUT errors on MAIN")
}
Command to reproduce:
gt.sandbox.checkout.commit e80a3f12261b9204a088 \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./gradlew run --quiet"
Recorded output of command:
Picked up JAVA_TOOL_OPTIONS: -Dkotlinx.coroutines.debug
Picked up JAVA_TOOL_OPTIONS: -Dkotlinx.coroutines.debug
[INFO][elapsed: 14ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] START - ON MAIN
[INFO][elapsed: 30ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] [>] Starting action=[functionThatStartsCoRoutineScope()]
[INFO][elapsed: 31ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] This coroutineScope suspends the calling function.
[INFO][elapsed: 33ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] Just launched co-routines
[INFO][elapsed: 35ms][🥇][⓶][coroutname:@async-wait-3s#2][tname:main/tid:1] Delaying for 3s what_for=[]
[INFO][elapsed: 38ms][🥇][⓷][coroutname:@async-throws-after-2s#3][tname:main/tid:1] Delaying for 2s what_for=[delay before throwing]
[INFO][elapsed: 38ms][🥇][⓸][coroutname:@launch-waits-1s#4][tname:main/tid:1] Delaying for 1s what_for=[]
[INFO][elapsed: 1039ms][🥇][⓸][coroutname:@launch-waits-1s#4][tname:main/tid:1] Done delaying for 1s what_for=[]
[INFO][elapsed: 2038ms][🥇][⓷][coroutname:@async-throws-after-2s#3][tname:main/tid:1] Done delaying for 2s what_for=[delay before throwing]
[WARN][elapsed: 2070ms][🥇][⓷][coroutname:@async-throws-after-2s#3][tname:main/tid:1] 💥 throwing exception=[MyRuntimeException] with msg=[exc-from-async-2]
[WARN][elapsed: 2089ms][🥇][⓶][coroutname:@async-wait-3s#2][tname:main/tid:1] 🫡 I have caught [JobCancellationException/Parent job is Cancelling], and rethrowing it 🫡
[WARN][elapsed: 2090ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] [<][💥] Finished action=[functionThatStartsCoRoutineScope()], threw exception of type=[MyRuntimeException].
[ERROR][elapsed: 2090ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] back at MAIN got an exception! of type=[MyRuntimeException] with msg=[exc-from-async-2] cause=[null]. Exiting with error code 1
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:run'.
> Process 'command '/home/nickolaykondratyev/.jdks/corretto-21.0.7/bin/java'' finished with non-zero exit value 1
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
BUILD FAILED in 2s
We are able to surround scope with try catch and stop Job hierarchy shutdown
We are able to catch and process the exception from coroutineScope.
Code
package com.glassthought.sandbox
import com.glassthought.sandbox.util.out.impl.out
import gt.sandbox.util.output.Out
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.system.exitProcess
import kotlin.time.Duration.Companion.seconds
private suspend fun mainImpl(out: Out) {
coroutineScope {
try {
foo()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
out.info("foo() threw but we caught without knowledge that foo used co-routines, we caught: [${e::class.simpleName}] with msg=[${e.message}].")
}
}
}
private suspend fun foo() {
out.actionWithMsg("functionThatStartsCoRoutineScope()", { functionThatStartsCoRoutineScope() })
}
private suspend fun functionThatStartsCoRoutineScope() {
coroutineScope {
out.info("This coroutineScope suspends the calling function.")
async(CoroutineName("async-wait-3s")) {
out.delayNamed(3.seconds)
}
async(CoroutineName("async-throws-after-2s")) {
out.delayNamed(2.seconds, "delay before throwing")
throw MyRuntimeException.create("exc-from-async-2", out)
}
launch(CoroutineName("launch-waits-1s")) {
out.delayNamed(1.seconds)
}
out.info("Just launched co-routines")
}
}
fun main(): Unit = runBlocking {
out.info("START - ON MAIN")
try {
mainImpl(out)
} catch (e: Exception) {
out.error("back at MAIN got an exception! of type=[${e::class.simpleName}] with msg=[${e.message}] cause=[${e.cause}]. Exiting with error code 1")
exitProcess(1)
}
out.info("DONE - WITHOUT errors on MAIN")
}
Command to reproduce:
gt.sandbox.checkout.commit 56badf019542281e9ded \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./gradlew run --quiet"
Recorded output of command:
Picked up JAVA_TOOL_OPTIONS: -Dkotlinx.coroutines.debug
Picked up JAVA_TOOL_OPTIONS: -Dkotlinx.coroutines.debug
[INFO][elapsed: 15ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] START - ON MAIN
[INFO][elapsed: 31ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] [>] Starting action=[functionThatStartsCoRoutineScope()]
[INFO][elapsed: 31ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] This coroutineScope suspends the calling function.
[INFO][elapsed: 33ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] Just launched co-routines
[INFO][elapsed: 36ms][🥇][⓶][coroutname:@async-wait-3s#2][tname:main/tid:1] Delaying for 3s what_for=[]
[INFO][elapsed: 38ms][🥇][⓷][coroutname:@async-throws-after-2s#3][tname:main/tid:1] Delaying for 2s what_for=[delay before throwing]
[INFO][elapsed: 38ms][🥇][⓸][coroutname:@launch-waits-1s#4][tname:main/tid:1] Delaying for 1s what_for=[]
[INFO][elapsed: 1040ms][🥇][⓸][coroutname:@launch-waits-1s#4][tname:main/tid:1] Done delaying for 1s what_for=[]
[INFO][elapsed: 2039ms][🥇][⓷][coroutname:@async-throws-after-2s#3][tname:main/tid:1] Done delaying for 2s what_for=[delay before throwing]
[WARN][elapsed: 2071ms][🥇][⓷][coroutname:@async-throws-after-2s#3][tname:main/tid:1] 💥 throwing exception=[MyRuntimeException] with msg=[exc-from-async-2]
[WARN][elapsed: 2091ms][🥇][⓶][coroutname:@async-wait-3s#2][tname:main/tid:1] 🫡 I have caught [JobCancellationException/Parent job is Cancelling], and rethrowing it 🫡
[WARN][elapsed: 2092ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] [<][💥] Finished action=[functionThatStartsCoRoutineScope()], threw exception of type=[MyRuntimeException].
[INFO][elapsed: 2092ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] foo() threw but we caught without knowledge that foo used co-routines, we caught: [MyRuntimeException] with msg=[exc-from-async-2].
[INFO][elapsed: 2092ms][🥇][①][coroutname:@coroutine#1][tname:main/tid:1] DONE - WITHOUT errors on MAIN
Backlinks