How does one combine `Job` 's ? ```fun allJobs():...
# coroutines
d
How does one combine
Job
's ?
Copy code
fun allJobs(): Job {
  val job1: Job
  val job2: Job
  val job3: Job
  return combine(job1, job2, job3) // HOW?
}

fun main() {
  allJobs().cancel() // cancels all 3 jobs
}
Is it possible or do I have to use some pattern which is similar to this?
s
If the jobs have a common parent, you can cancel the parent job to cancel all of its children.
You can only add a parent when the jobs are first created though. This method wouldn’t work for jobs that already exist or were created elsewhere.
For existing jobs, a simple extension function like this would be fine:
Copy code
fun Iterable<Job>.cancelAll() = forEach(Job::cancel)
b
for jobs that have a parent, you can do smth like
Copy code
return scope.launch { // scope here is up to you
   job1.join()
   job2.join()
   job3.join()
}
But note, cancelling that job won't cancel the "children" because they're not children. For that purpose in our codebase we have Job.joinOrCancel function that allows you to "attach" job from another Job tree to your current execution context:
Copy code
suspend fun Job.joinOrCancel() {
    try {
        join()
    } catch (e: CancellationException) {
        withContext(NonCancellable) {
            cancelAndJoin()
        }
    }
}
Similarly it can be done for
Iterable<Job>
, then you can do what I initially suggested:
Copy code
scope.launch {
   listOf(job1, job2, job3).joinOrCancel() 
}
But overall, these tricks are hacky, so as @Sam said, try to properly structure your jobs first
d
Yeah, jobs were created elsewhere and the thing with iterable is that I need to also return a
Job
instance, i.e. in my example I couldn't do this:
Copy code
fun allJobs(): Job = listOf(job1,job2,job3).forEach(::cancel())
because this wouldn't return a new Job
and no scope is available...
so yeah, perhaps I need to restructure this somehow
s
You could try this:
Copy code
return Job().also { j -> j.invokeOnCompletion { e -> otherJobs.forEach { it.cancel(e) } } }
d
oh, sorry my last example above is wrong, but maybe you got the idea
Oh, I thought about invokeOnCompletion, but didn't put it like this. thanks! But I understand that this is quite hacky, yes 🙂
j
I guess the best would be to properly use structured concurrency, which is exactly the kind of hell it's designed to avoid 😜
d
I use it, but this is a sort of API which deals with custom job scheduling, so having to use jobs is kinda inevitable.
g
Would be interesting to see what you are trying to achieve with custom job scheduling Your example really looks as a thing which goes against core principals of structured concurrency, it doesn’t make it wrong by itself, but curios where it may not work
d
My case would require a minimal example which I don't have at this point. But there was a kinda similar question here recently which got no answers, I wonder how would you approach this: https://kotlinlang.slack.com/archives/C1CFAFJSK/p1668774909525059
My case is maybe closer to where you allow an api user to combine downloads into groups and cancelling them as one.
g
you allow an api user to combine downloads into groups and cancelling them as one
It looks as a very broad task, but my first idea is do not expose Job at all and expose specialised API for user, not allow to mangle with low level stuff like job
d
Agreed. Actually all other parts of the API hide many of its internals, but this is one part where "abstraction leak" happened and I guess I'm paying for it now. Fortunately this is an internal lib and changing it will not be painful 🙂
g
BTW answered on the thread which you shared above, I think it’s quite different case for you