kotlinx.coroutines icon indicating copy to clipboard operation
kotlinx.coroutines copied to clipboard

scope with job in completing state can still launch new coroutines

Open btwilk opened this issue 2 years ago • 4 comments

Version: 1.6

I was surprised to see that while a job is completing (i.e., waiting for all of its children to complete) new children can still be launched. In contrast, when a job is complete, new children get canceled automatically.

This forces me to keep track myself whether I'm draining a job and avoid calling launch if so.

Here is the repro:

fun main() = runBlocking {
    val job = Job()
    val scope = CoroutineScope(job)
    val sema = Semaphore(1, 1)
    scope.launch {
        sema.acquire()
    }
    job.complete() // transitions to completing until the above coroutine completes
    val c1 = scope.launch {
        println("don't think this should run") // this runs
    }
    c1.join()

    // cleanup
    sema.release()
    job.join()
}

btwilk avatar Jun 15 '22 21:06 btwilk

We initially thought about alternatives to the current behaviour.

The biggest problem with the proposed one is that it's racy and may yield the unexpected result: everything is completed normally, but some children are cancelled for some reason, though there were neither failures nor explicit cancellations.

Racy-ness can also lead to unexpected behaviour when the parent job is not something to call .complete() on, but have its own body to execute non-deterministically

qwwdfsad avatar Jun 28 '22 13:06 qwwdfsad

unexpected result: everything is completed normally, but some children are cancelled for some reason

The Java analogue: when you shutdown() an executor, new tasks are rejected despite no failure having occurred. FYI my use-case is a server that launches a coroutine per request. While shutting down the server I need a way to drain requests without incoming requests preventing a full drain. With an executor, a call to shutdown() was all I needed. With coroutines for some reason I need to implement my own draining logic.

unexpected behaviour when the parent job is not something to call .complete() on, but have its own body to execute non-deterministically

I can't think of an example that isn't an abuse of structured concurrency. When the body of a parent job is finished and the job is completing, when is it useful to allow a new child to run? The existing children may need to launch new coroutines but they would create them as their own children, not as siblings.

btwilk avatar Jun 29 '22 18:06 btwilk

A related issue reported via YouTrack: https://youtrack.jetbrains.com/issue/KT-60182

elizarov avatar Jul 07 '23 08:07 elizarov

Note to self: above-linked YouTrack issue is my own

mgroth0 avatar Dec 28 '23 03:12 mgroth0