structured-parallelization
From manually-with-scope with child parallelization launched co-routines
Go to text ā
Here we manually setup scope and wait on it for launched actions to finish - a bit cumbersome but works.
Code
package com.glassthought.sandbox
import gt.sandbox.util.output.Out
import kotlinx.coroutines.*
import kotlin.coroutines.coroutineContext
import kotlin.system.exitProcess
import kotlin.time.Duration.Companion.seconds
private val out = Out.standard()
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")
}
private suspend fun mainImpl(out: Out) {
coroutineScope {
foo("msg-1")
}
}
private suspend fun foo(msg: String) {
out.actionWithMsg("fooImpl", { fooImpl(msg) })
}
private suspend fun fooImpl(msg: String) {
val job = Job()
val scope = CoroutineScope(coroutineContext + job)
scope.launch {
out.delayNamed(3.seconds, "delayed([${msg}])")
}
scope.launch {
out.delayNamed(2.seconds, "delayed([${msg}])")
}
scope.launch {
out.delayNamed(1.seconds, "delayed([${msg}])")
}
out.actionWithMsg("job.complete()", { job.complete() })
out.actionWithMsg("job.join()", { job.join() })
}
Command to reproduce:
gt.sandbox.checkout.commit 800315e25e0cc39077c2 \
&& 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: 37ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] START - ON MAIN
[INFO][elapsed: 52ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [>] Starting action=[fooImpl]
[INFO][elapsed: 54ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [>] Starting action=[job.complete()]
[INFO][elapsed: 55ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [<] Finished action=[job.complete()].
[INFO][elapsed: 55ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [>] Starting action=[job.join()]
[INFO][elapsed: 59ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] Delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 60ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] Delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 61ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Delaying for 1s what_for=[delayed([msg-1])]
[INFO][elapsed: 1062ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Done delaying for 1s what_for=[delayed([msg-1])]
[INFO][elapsed: 2061ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] Done delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 3060ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] Done delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 3061ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [<] Finished action=[job.join()].
[INFO][elapsed: 3061ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [<] Finished action=[fooImpl].
[INFO][elapsed: 3061ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] DONE - WITHOUT errors on MAIN
Instead of manually setting up scope and waiting on it we can use coroutineScope
From coroutineScope-happy-case
Go to text ā
Here we use coroutineScope with 3 parallel children, coroutineScope will auto wait for them to finish.
Code
package com.glassthought.sandbox
import gt.sandbox.util.output.Out
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.system.exitProcess
import kotlin.time.Duration.Companion.seconds
private val out = Out.standard()
private suspend fun mainImpl(out: Out) {
coroutineScope {
foo("msg-1")
}
}
private suspend fun foo(msg: String) {
out.actionWithMsg("fooImpl", { fooImpl(msg) })
}
private suspend fun fooImpl(msg: String) {
coroutineScope {
launch {
out.delayNamed(3.seconds, "delayed([${msg}])")
}
launch {
out.delayNamed(2.seconds, "delayed([${msg}])")
}
launch {
out.delayNamed(1.seconds, "delayed([${msg}])")
}
}
}
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 e09d077f5c59925b2881 \
&& 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: 39ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] START - ON MAIN
[INFO][elapsed: 54ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [>] Starting action=[fooImpl]
[INFO][elapsed: 60ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] Delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 62ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] Delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 62ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Delaying for 1s what_for=[delayed([msg-1])]
[INFO][elapsed: 1064ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Done delaying for 1s what_for=[delayed([msg-1])]
[INFO][elapsed: 2062ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] Done delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 3061ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] Done delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 3062ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [<] Finished action=[fooImpl].
[INFO][elapsed: 3062ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] DONE - WITHOUT errors on MAIN
Next let's see how the cancellations are handled, we expect any single failure to cancel all others.
From throw-exception-in-one-which-cancels-others
Go to text ā
Throw exception in one child co-routine which cancels others (as well as cancels parent)
Code
package com.glassthought.sandbox
import gt.sandbox.util.output.Out
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.system.exitProcess
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
private val out = Out.standard()
private suspend fun mainImpl(out: Out) {
coroutineScope {
foo("msg-1")
}
}
private suspend fun foo(msg: String) {
out.actionWithMsg("fooImpl", { fooImpl(msg) })
}
private suspend fun fooImpl(msg: String) {
coroutineScope {
launch {
out.delayNamed(3.seconds, "delayed([${msg}])")
}
launch {
out.delayNamed(2.seconds, "delayed([${msg}])")
}
launch {
out.delayNamed(1.seconds, "delayed([${msg}])")
out.actionWithMsg(
"throw-exception",
{ throw MyRuntimeException.create("from-launch-with-1sec-delay", out) })
}
}
}
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 115eb01d02706b26722e \
&& 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: 38ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] START - ON MAIN
[INFO][elapsed: 53ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [>] Starting action=[fooImpl]
[INFO][elapsed: 58ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] Delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 61ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] Delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 61ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Delaying for 1s what_for=[delayed([msg-1])]
[INFO][elapsed: 1062ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Done delaying for 1s what_for=[delayed([msg-1])]
[INFO][elapsed: 1063ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] [>] Starting action=[throw-exception]
[WARN][elapsed: 1102ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] š„ throwing exception=[MyRuntimeException] with msg=[from-launch-with-1sec-delay]
[WARN][elapsed: 1102ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] [<][š„] Finished action=[throw-exception], threw exception of type=[MyRuntimeException].
[WARN][elapsed: 1123ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] š«” I have caught [JobCancellationException/Parent job is Cancelling], and rethrowing it š«”
[WARN][elapsed: 1124ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] š«” I have caught [JobCancellationException/Parent job is Cancelling], and rethrowing it š«”
[WARN][elapsed: 1124ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [<][š„] Finished action=[fooImpl], threw exception of type=[MyRuntimeException].
[ERROR][elapsed: 1125ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] back at MAIN got an exception! of type=[MyRuntimeException] with msg=[from-launch-with-1sec-delay] 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 1s
Now let's have one of co-routines cancel itself, which should not disturb other co-routines.
From throw-cancellation-exception
Go to text ā
One child throws cancellation exception which just cancels itself, allowing other SIBLING co-routines to finish processing without issues. Parent also finishes successfully in this case.
Related
Code
package com.glassthought.sandbox
import gt.sandbox.util.output.Out
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.system.exitProcess
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
private val out = Out.standard()
private suspend fun mainImpl(out: Out) {
coroutineScope {
foo("msg-1")
}
}
private suspend fun foo(msg: String) {
out.actionWithMsg("fooImpl", { fooImpl(msg) })
}
private suspend fun fooImpl(msg: String) {
coroutineScope {
launch {
out.delayNamed(3.seconds, "delayed([${msg}])")
}
launch {
out.delayNamed(2.seconds, "delayed([${msg}])")
}
launch {
out.delayNamed(1.seconds, "delayed([${msg}])")
out.info("Throwing cancellation exception")
throw CancellationException("cancelled-1")
}
}
}
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 a9eae6cf8babc917214c \
&& 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: 39ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] START - ON MAIN
[INFO][elapsed: 55ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [>] Starting action=[fooImpl]
[INFO][elapsed: 59ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] Delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 62ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] Delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 62ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Delaying for 1s what_for=[delayed([msg-1])]
[INFO][elapsed: 1063ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Done delaying for 1s what_for=[delayed([msg-1])]
[INFO][elapsed: 1063ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Throwing cancellation exception
[INFO][elapsed: 2063ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] Done delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 3061ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] Done delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 3062ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [>] Finished action=[fooImpl].
[INFO][elapsed: 3062ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] DONE - WITHOUT errors on MAIN
With await:
From happy-case
Go to text ā
happy case we spawn 3 child awaits and get their results.
Code
package com.glassthought.sandbox
import gt.sandbox.util.output.Out
import kotlinx.coroutines.CancellationException
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.milliseconds
import kotlin.time.Duration.Companion.seconds
private val out = Out.standard()
private suspend fun mainImpl(out: Out) {
coroutineScope {
foo("msg-1")
}
}
private suspend fun foo(msg: String) {
out.actionWithMsg("fooImpl", { fooImpl(msg) })
}
private suspend fun fooImpl(msg: String) {
coroutineScope {
val deferred3 = async {
out.delayNamed(3.seconds, "delayed([${msg}])")
"res-3"
}
val deferred2 = async {
out.delayNamed(2.seconds, "delayed([${msg}])")
"res-2"
}
val deferred1 = async {
out.delayNamed(1.seconds, "delayed([${msg}])")
"res-1"
}
out.info("Just launched co-routines")
out.info("deferred-1.result=" + deferred1.await())
out.info("deferred-2.result=" + deferred2.await())
out.info("deferred-3.result=" + deferred3.await())
}
}
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 fedf1c8aed4a6059c3ee \
&& 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: 37ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] START - ON MAIN
[INFO][elapsed: 52ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [>] Starting action=[fooImpl]
[INFO][elapsed: 54ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] Just launched co-routines
[INFO][elapsed: 58ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] Delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 59ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] Delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 59ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Delaying for 1s what_for=[delayed([msg-1])]
[INFO][elapsed: 1061ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Done delaying for 1s what_for=[delayed([msg-1])]
[INFO][elapsed: 1062ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] deferred-1.result=res-1
[INFO][elapsed: 2060ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] Done delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 2060ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] deferred-2.result=res-2
[INFO][elapsed: 3059ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] Done delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 3059ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] deferred-3.result=res-3
[INFO][elapsed: 3060ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [>] Finished action=[fooImpl].
[INFO][elapsed: 3060ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] DONE - WITHOUT errors on MAIN
From ā ļø Unhandled exception from async can go to parent scope without going through await() ā ļø
Go to text ā
Uncaught exception behavior with async
:
- IF
await()
is called before theasync
coroutine fails.- THEN: the exception goes through
await()
- THEN: the exception goes through
- IF the
async
coroutine fails beforeawait()
is called.- THEN: the exception propagates up the Job hierarchy immediately.
As we would expect example: exception goes through await()
Here we observe await() receiving exception
Code
package com.glassthought.sandbox
import com.glassthought.sandbox.util.out.impl.out
import gt.sandbox.util.output.Emojis
import gt.sandbox.util.output.Out
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.milliseconds
import kotlin.time.Duration.Companion.seconds
private suspend fun mainImpl(out: Out) {
coroutineScope {
foo("msg-1")
}
}
private suspend fun foo(msg: String) {
out.actionWithMsg("fooImpl", { fooImpl(msg) })
}
private suspend fun fooImpl(msg: String) {
coroutineScope {
launch {
out.delayNamed(3.seconds, "delayed([${msg}])")
}
launch {
out.delayNamed(2.seconds, "delayed([${msg}])")
}
val deferred = async {
out.delayNamed(500.milliseconds, "delayed([${msg}])")
throw MyRuntimeException.create("exception-from-async", out)
}
out.info("Just launched co-routines. Going into deferred.await()")
// This code is unreachable since async exception will go to the parent instead of
// going to the await()
try {
deferred.await()
} catch (e: Exception) {
out.error("[RETHROWING][${Emojis.CAUGHT_EXCEPTION}] Caught exception on [deferred.await()] from async: ${e.message} of type [${e::class.simpleName}]")
throw e
}
}
}
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 fa1ebcc936a485d23912 \
&& 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=[fooImpl]
[INFO][elapsed: 34ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] Just launched co-routines. Going into deferred.await()
[INFO][elapsed: 38ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] Delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 39ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] Delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 39ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Delaying for 500ms what_for=[delayed([msg-1])]
[INFO][elapsed: 541ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Done delaying for 500ms what_for=[delayed([msg-1])]
[WARN][elapsed: 571ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] š„ throwing exception=[MyRuntimeException] with msg=[exception-from-async]
[WARN][elapsed: 591ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] š«” I have caught [JobCancellationException/Parent job is Cancelling], and rethrowing it š«”
[WARN][elapsed: 592ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] š«” I have caught [JobCancellationException/Parent job is Cancelling], and rethrowing it š«”
[ERROR][elapsed: 593ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [RETHROWING][ļæ½ Caught exception on [deferred.await()] from async: exception-from-async of type [MyRuntimeException]
[WARN][elapsed: 593ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [<][š„] Finished action=[fooImpl], threw exception of type=[MyRuntimeException].
[ERROR][elapsed: 594ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] back at MAIN got an exception! of type=[MyRuntimeException] with msg=[exception-from-async] 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 1s
Newer behavior: exception propagates through Job link to parent scope WITHOUT going through await()
ā ļø Here we add delay() such that exception is thrown from async BEFORE we call await() on deferred object. Result: Exception goes directly to parent scope without going through await() ā ļø
Code
package com.glassthought.sandbox
import com.glassthought.sandbox.util.out.impl.out
import gt.sandbox.util.output.Emojis
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.milliseconds
import kotlin.time.Duration.Companion.seconds
private suspend fun mainImpl(out: Out) {
coroutineScope {
foo("msg-1")
}
}
private suspend fun foo(msg: String) {
out.actionWithMsg("fooImpl", { fooImpl(msg) })
}
private suspend fun fooImpl(msg: String) {
coroutineScope {
launch(CoroutineName("IJustDelay-3Sec-AndPrint")) {
out.delayNamed(3.seconds, "delayed([${msg}])")
}
launch(CoroutineName("IJustDelay-2Sec-AndPrint")) {
out.delayNamed(2.seconds, "delayed([${msg}])")
}
val deferred = async(CoroutineName("IAmGoingToThrow")) {
out.delayNamed(500.milliseconds, "delayed([${msg}])")
throw MyRuntimeException.create("exception-from-async", out)
}
out.info("Just launched co-routines.")
out.delayNamed(1.seconds, "Going into DELAY before calling [deferred.await()]")
// This code is unreachable since async exception will go to the parent instead of
// going to the await()
try {
out.info("About to call [deferred.await()] (WE ARE NEVER GOING TO REACH THIS LINE)")
deferred.await()
} catch (e: Exception) {
out.error("[RETHROWING][${Emojis.CAUGHT_EXCEPTION}] Caught exception on [deferred.await()] from async: ${e.message} of type [${e::class.simpleName}]")
throw e
}
}
}
fun main(): Unit = runBlocking(CoroutineName("RunBlocking-At-Main")) {
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 172df689c3cb592bc3eb \
&& 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:@RunBlocking-At-Main#1][tname:main/tid:1] START - ON MAIN
[INFO][elapsed: 32ms][š„][ā ][coroutname:@RunBlocking-At-Main#1][tname:main/tid:1] [>] Starting action=[fooImpl]
[INFO][elapsed: 35ms][š„][ā ][coroutname:@RunBlocking-At-Main#1][tname:main/tid:1] Just launched co-routines.
[INFO][elapsed: 36ms][š„][ā ][coroutname:@RunBlocking-At-Main#1][tname:main/tid:1] Delaying for 1s what_for=[Going into DELAY before calling [deferred.await()]]
[INFO][elapsed: 39ms][š„][ā¶][coroutname:@IJustDelay-3Sec-AndPrint#2][tname:main/tid:1] Delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 39ms][š„][ā·][coroutname:@IJustDelay-2Sec-AndPrint#3][tname:main/tid:1] Delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 40ms][š„][āø][coroutname:@IAmGoingToThrow#4][tname:main/tid:1] Delaying for 500ms what_for=[delayed([msg-1])]
[INFO][elapsed: 541ms][š„][āø][coroutname:@IAmGoingToThrow#4][tname:main/tid:1] Done delaying for 500ms what_for=[delayed([msg-1])]
[WARN][elapsed: 572ms][š„][āø][coroutname:@IAmGoingToThrow#4][tname:main/tid:1] š„ throwing exception=[MyRuntimeException] with msg=[exception-from-async]
[WARN][elapsed: 591ms][š„][ā¶][coroutname:@IJustDelay-3Sec-AndPrint#2][tname:main/tid:1] š«” I have caught [JobCancellationException/Parent job is Cancelling], and rethrowing it š«”
[WARN][elapsed: 592ms][š„][ā·][coroutname:@IJustDelay-2Sec-AndPrint#3][tname:main/tid:1] š«” I have caught [JobCancellationException/Parent job is Cancelling], and rethrowing it š«”
[WARN][elapsed: 593ms][š„][ā ][coroutname:@RunBlocking-At-Main#1][tname:main/tid:1] š«” I have caught [JobCancellationException/ScopeCoroutine is cancelling], and rethrowing it š«”
[WARN][elapsed: 594ms][š„][ā ][coroutname:@RunBlocking-At-Main#1][tname:main/tid:1] [<][š„] Finished action=[fooImpl], threw exception of type=[MyRuntimeException].
[ERROR][elapsed: 594ms][š„][ā ][coroutname:@RunBlocking-At-Main#1][tname:main/tid:1] back at MAIN got an exception! of type=[MyRuntimeException] with msg=[exception-from-async] 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 1s
Cancellation of Parent Example
From cancel-parent-cancel-children
Go to text ā
Here we cancel the parent scope and that automatically cancels children.
Code
package com.glassthought.sandbox
import gt.sandbox.util.output.Out
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.system.exitProcess
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
private val out = Out.standard()
private suspend fun mainImpl(out: Out) {
coroutineScope {
foo("msg-1")
}
}
private suspend fun foo(msg: String) {
out.actionWithMsg("fooImpl", { fooImpl(msg) })
}
private suspend fun fooImpl(msg: String) {
coroutineScope {
val deferred3 = async {
out.delayNamed(3.seconds, "delayed([${msg}])")
"res-3"
}
val deferred2 = async {
out.delayNamed(2.seconds, "delayed([${msg}])")
"res-2"
}
val deferred1 = async {
out.delayNamed(1.seconds, "delayed([${msg}])")
"res-1"
}
out.info("Just launched co-routines")
out.delayNamed(500.milliseconds, "delay before scope cancel")
this.cancel()
out.info("deferred-1.result=" + deferred1.await())
out.info("deferred-2.result=" + deferred2.await())
out.info("deferred-3.result=" + deferred3.await())
}
}
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 a2df237045869c0a8e1b \
&& 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: 39ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] START - ON MAIN
[INFO][elapsed: 56ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [>] Starting action=[fooImpl]
[INFO][elapsed: 58ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] Just launched co-routines
[INFO][elapsed: 60ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] Delaying for 500ms what_for=[delay before scope cancel]
[INFO][elapsed: 63ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] Delaying for 3s what_for=[delayed([msg-1])]
[INFO][elapsed: 63ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] Delaying for 2s what_for=[delayed([msg-1])]
[INFO][elapsed: 63ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] Delaying for 1s what_for=[delayed([msg-1])]
[INFO][elapsed: 562ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] Done delaying for 500ms what_for=[delay before scope cancel]
[WARN][elapsed: 615ms][š„][ā¶][coroutname:@coroutine#2][tname:main/tid:1] š«” I have caught [JobCancellationException/ScopeCoroutine was cancelled], and rethrowing it š«”
[WARN][elapsed: 615ms][š„][ā·][coroutname:@coroutine#3][tname:main/tid:1] š«” I have caught [JobCancellationException/ScopeCoroutine was cancelled], and rethrowing it š«”
[WARN][elapsed: 616ms][š„][āø][coroutname:@coroutine#4][tname:main/tid:1] š«” I have caught [JobCancellationException/ScopeCoroutine was cancelled], and rethrowing it š«”
[INFO][elapsed: 616ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] [>][š«”] Cancellation Exception - rethrowing.
[ERROR][elapsed: 617ms][š„][ā ][coroutname:@coroutine#1][tname:main/tid:1] back at MAIN got an exception! of type=[JobCancellationException] with msg=[ScopeCoroutine was cancelled] cause=[kotlinx.coroutines.JobCancellationException: ScopeCoroutine was cancelled; job="coroutine#1":ScopeCoroutine{Cancelled}@768b970c]. 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 1s
Children
- coroutineScope-happy-case
- happy-case
- manually-with-scope with child parallelization launched co-routines
- throw-cancellation-exception
- throw-exception-in-one-which-cancels-others
- ā ļø Unhandled exception from async can go to parent scope without going through await() ā ļø
Backlinks