-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
Steps to reproduce:
// Add this test to JobChildStressTest
@Test
fun testFailingChildIsAddedWhenJobFinalizesItsState() {
// All exceptions should get aggregated here
repeat(N_ITERATIONS) {
runBlocking {
val rogueJob = AtomicReference<Job?>()
println(it)
val deferred = CompletableDeferred<Unit>()
launch(pool + deferred) {
deferred.complete(Unit) // Transition deferred into "completing" state waiting for current child
// **Asynchronously** submit task that launches a child so it races with completion
pool.executor.execute {
rogueJob.set(launch(pool + deferred) {
println("isCancelled: " + coroutineContext.job.isCancelled)
throw TestException()
})
}
}
deferred.join()
if (rogueJob.get()?.isActive ?: false) {
val rogue = rogueJob.get()!!
println("Rogue job with parent " + rogue.parent + " and children list: " + rogue.parent?.children?.toList())
}
}
}
}
What happens here:
Deferredis completing, waiting for the firstlaunch(1)ChildCompletionhandler to finalize its stateChildCompletioninvokescontinueCompleting- In parallel, the second
launch(2) is attached to the deferred
-
Happy path:
2successfully attaches to the parent,1detects that incontinueCompletingand starts waiting for it. This situation is indistinguishable fromdeferredhaving two children -
Unhappy path
#1:1detects there are no children and invokesfinalizeFinishingState.
Then2attaches itself to the parent.finalizeFinishingStatereachescompleteStateFinalization -> notifyCompletionand cancels the child, which might have been running for some time already.
This is an observable and counter-intuitive (because nothing actually failed or was cancelled explicitly) behaviour.
Also, if2fails with an exception, it gets reported to the global exception handler. -
Unhappy path
#2: the same as above, but2attaches itself to the parent after it completely finalizes its state.
Meaning that we have a completeddeferredwith no children and active non-cancelled coroutine with a parent pointing to thedeferred
Note that 2) kind of emulates the behaviour "attempt to attach as a child to already completed job immediately cancels current job"