Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarifications on the documented behavior of supervisorScope W.R.T. its parent. #3725

Closed
psteiger opened this issue Apr 25, 2023 · 2 comments
Labels
docs KDoc and API reference structured concurrency

Comments

@psteiger
Copy link

The KDoc for supervisorScope states that:

A failure of the scope itself (exception thrown in the block or external cancellation) fails the scope with all its children, but does not cancel parent job.

I might be missing something obvious, but how can an exception thrown at supervisorScope block not cancel the parent Job?

  1. If we throw inside supervisorScope block, this exception is rethrown, so it obviously cancels the parent:
fun main() = runBlocking<Unit> {
   coroutineContext.job.invokeOnCompletion { println("Parent job cancelling with $it") }
   supervisorScope {
       error("Some error")
   }
}

Output:

Parent job cancelling with java.lang.IllegalStateException: Some error
Exception in thread "main" java.lang.IllegalStateException: Some error
	at Main2Kt$main$1$2.invokeSuspend(Main2.kt:19)
	at Main2Kt$main$1$2.invoke(Main2.kt)
	at Main2Kt$main$1$2.invoke(Main2.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
	at kotlinx.coroutines.SupervisorKt.supervisorScope(Supervisor.kt:61)
        ...
  1. If we cancel the supervisorScope job with an exception, it also fails the parent:
fun main() = runBlocking<Unit> {
    coroutineContext.job.invokeOnCompletion { println("Parent job cancelling with $it") }
    supervisorScope {
        cancel("Cancelling supervisor scope", IllegalStateException("Some error"))
    }
}

Output:

Parent job cancelling with java.util.concurrent.CancellationException: Cancelling supervisor scope
Exception in thread "main" java.util.concurrent.CancellationException: Cancelling supervisor scope
	at kotlinx.coroutines.ExceptionsKt.CancellationException(Exceptions.kt:22)
	at kotlinx.coroutines.CoroutineScopeKt.cancel(CoroutineScope.kt:295)
	...
Caused by: java.lang.IllegalStateException: Some error
	... 16 more
  1. The behavior outlined above is exactly the same with the coroutineScope builder.

So I'm wondering what the docs really mean. If documentation is indeed correct, is it possible to provide a small code example in the docs to exemplify the stated behavior (and how it differs from coroutineScope)?

Please note that this is not regarding SupervisorJob behavior regarding children cancellation and failure, which I think is clear. But the SupervisorJob behavior WRT its parent, and also specifically the behavior of the supervisorScope builder WRT exceptions thrown in the scope block (not on children coroutines).

@psteiger psteiger added the bug label Apr 25, 2023
@dkhalanskyjb
Copy link
Contributor

If we throw inside supervisorScope block, this exception is rethrown, so it obviously cancels the parent:

The failure of supervisorScope does not cancel the parent, a thrown exception does. If we catch the exception, the parent is not cancelled:

fun main() = runBlocking<Unit> {
    coroutineContext.job.invokeOnCompletion { println("Parent job cancelling with $it") }
    try {
        supervisorScope {
            error("Some error")
        }
    } catch (e: Exception) {

    }
}

@psteiger
Copy link
Author

psteiger commented Apr 26, 2023

@dkhalanskyjb Got it! Thanks for your response.

Still, I feel this piece of doc is not super clear and could be improved, and I wonder why it is in supervisorScope since it applies to coroutineScope (and withContext) as well (no SupervisorJob). Not sure what the intention was, but in current state of docs it feels like it's something particular to supervision.

In fact, it seems to be a property of all scoped coroutines (created by scope functions). From JobSupport:

private fun cancelParent(cause: Throwable): Boolean {
    // Is scoped coroutine -- don't propagate, will be rethrown
    if (isScopedCoroutine) return true
    // ....
}

While on normal hierarchies we can expect parent cancellation:

fun main() = runBlocking<Unit> {
    val job0 = coroutineContext.job
    val job1 = Job(job0)
    val job2 = SupervisorJob(job1)
    job0.invokeOnCompletion { println("Parent job cancelling with $it") }
    job2.completeExceptionally(IllegalStateException("Some error"))
}

(Output):

Parent job cancelling with java.lang.IllegalStateException: Some error
Exception in thread "main" java.lang.IllegalStateException: Some error
	at Main2Kt$main$1.invokeSuspend(Main2.kt:21)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        ....

@dkhalanskyjb dkhalanskyjb added docs KDoc and API reference and removed bug labels Apr 26, 2023
dkhalanskyjb added a commit that referenced this issue Feb 12, 2024
Seems like a lot of the information was outdated.

Fixes #3725
dkhalanskyjb added a commit that referenced this issue Feb 13, 2024
Seems like a lot of the information was outdated.

Fixes #3725
dkhalanskyjb added a commit that referenced this issue Feb 14, 2024
Seems like a lot of the information was outdated.

Fixes #3725
dkhalanskyjb added a commit that referenced this issue Feb 14, 2024
Seems like a lot of the information was outdated.

Fixes #3725
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs KDoc and API reference structured concurrency
Projects
None yet
Development

No branches or pull requests

3 participants