Cooperative cancellation limits for non-suspending coroutine work
0 reputation · 10 Aug 2024, 15:53 UTC
0 reputation · 10 Aug 2024, 15:53 UTC
Structured concurrency in Kotlin coroutines uses CoroutineScope and a Job hierarchy to bound the lifetime of asynchronous work. Cancelling a parent Job is documented to propagate cancellation to its children.
The goal is to clarify the limits of that propagation when child coroutines perform work that is not cooperative. Cancellation is cooperative and relies on suspension points or explicit checks. The uncertainty is how the documented guarantee behaves for CPU-bound loops without suspending calls, and what state the Job hierarchy reports while such work continues.
Does a cancelled parent Job immediately mark a non-cooperative child as cancelled, or only after the child reaches a suspension point? What is the documented support for observing that a child remains active after parent cancellation? Under what conditions is cascading cancellation considered complete?
35275 reputation · 10 Aug 2024, 17:18 UTC
Cancelling a parent Job immediately marks the child Job as cancelled, so isCancelled becomes true without waiting for a suspension point. It does not preemptively stop the child's running code. A CPU-bound loop that never suspends keeps executing until it finishes or voluntarily checks cancellation. Cascading cancellation is complete only when every child job reaches a terminal state.
parent.cancel() propagates the cancellation signal to child jobs immediately; the child Job reports isCancelled = true even while its coroutine body is still running on the CPU.yield(), delay(), withContext(), or join(), or through explicit checks such as isActive and ensureActive().isActive and isCancelled can both be true at the same time. Cancellation alone does not mean the coroutine has stopped.parent.join() does not return until all children are terminal. A non-cooperative child can therefore block join() indefinitely.Completed or Cancelled. For non-cooperative work, that requires the work to exit voluntarily or an external mechanism outside coroutine cancellation.job.isActive, job.isCancelled, and coroutineContext.isActive on each iteration.yield() or ensureActive() to the loop and confirm CancellationException is thrown at the first suspension point after cancellation.parent.join() and confirm it does not return while the non-cooperative loop continues.
val parent = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val child = parent.launch {
val job = coroutineContext[Job]!!
while (true) {
println("active=${job.isActive} cancelled=${job.isCancelled}")
}
}
parent.cancel()
child.join() // may not return while the loop runs
This assumes kotlinx.coroutines structured concurrency with a standard Job hierarchy. Custom Job implementations or unusual schedulers may behave differently; use the job-state logs above to verify.
Can you modify the child coroutine body to add suspension points or explicit cancellation checks? If not, coroutine cancellation alone cannot stop the work, and you need an external mechanism such as thread interruption or an isolated scope not tied to the cancelled parent.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.