CLOVIS
07/29/2024, 12:15 PMfun main(): Unit = runBlocking {
// DON'T DO THAT!
launch(SupervisorJob()) { // 1
launch {
delay(1000)
throw Error("Some error")
}
}
delay(3000)
}
However, this isn't what I expected to happen. Since a CoroutineContext argument is specified, I would expect that the job would be used. Looking at the implementation of launch
;
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else // ← HERE
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
Since the coroutine is started with a parent of newContext
, which includes our job, I would expect that the launched coroutine is indeed a child of SupervisorJob()
. However, since SupervisorJob()
has no parent, I would expect the exception to be swallowed or logged one way or another, but the other code to continue executing, since there is no parent-child relationship between them.
Thus, I would have expected the following code to be correct:
fun main(): Unit = runBlocking {
// DON'T DO THAT!
launch(SupervisorJob(coroutineContext.job)) { // 1
launch {
delay(1000)
throw Error("Some error")
}
}
delay(3000)
}
since the SupervisorJob
is created as a child of runBlocking
, I would expect it to behave the same as supervisorScope { launch {} }
.
Why does it not behave that way, what did I misunderstand?Sam
07/29/2024, 12:34 PMCLOVIS
07/29/2024, 12:34 PMCLOVIS
07/29/2024, 12:34 PMSam
07/29/2024, 12:34 PMCLOVIS
07/29/2024, 12:34 PMSam
07/29/2024, 12:35 PMSam
07/29/2024, 12:35 PMI would expect the exception to be swallowed or logged one way or another, but the other code to continue executing, since there is no parent-child relationship between them.
Sam
07/29/2024, 12:37 PMCLOVIS
07/29/2024, 12:38 PMrunBlocking
SupervisorJob()
launch
…
So, if you start multiple jobs within the launch
, they are in a regular job and will cancel each other. However, the error won't bubble up to runBlocking
, right?Sam
07/29/2024, 12:39 PMSam
07/29/2024, 12:40 PMSupervisorJob()
except when making a CoroutineScope()
.CLOVIS
07/29/2024, 12:41 PMlaunch
will ignore the SupervisorJob
argument, which doesn't seem to be true; it will just create the child job that is a regular Job
. The SupervisorJob
will be part of the hierarchy.
The real bug in the example is that SupervisorJob()
should be replaced by SupervisorJob(coroutineContext.job)
, otherwise it breaks structured concurrency.Sam
07/29/2024, 12:46 PMJob
to launch
as a bug, regardless of whether it has a parent. But I do take your point; there are different degrees of brokenness at play.CLOVIS
07/29/2024, 12:50 PMlaunch
with a Job
argument, but it's basically the same thing, no?Sam
07/29/2024, 12:55 PMCLOVIS
07/29/2024, 12:55 PMmarcinmoskala
07/29/2024, 12:58 PMCLOVIS
07/29/2024, 12:58 PMmarcinmoskala
07/29/2024, 12:59 PMrunBlocking with no children
SupervisorJob
launch
launch
launch
I will try to clarify thatmarcinmoskala
07/29/2024, 1:01 PM