Search code examples
androidkotlin-coroutinesandroid-workmanager

WorkManager CoroutineWorker's stream cancellation


I have the following setup:

override suspend fun doWork(): Result = coroutineScope {
    flowOfEvents
        .onEach(eventChannel::send)
        .launchIn(this)  

    if (isQueueEmpty()) return@coroutineScope Result.success()

    ... 
}

What I'm seeing is the following: when isQueueEmpty() is true, I return Result.success() and I'd expect the flowOfEvents...launchIn(this) stream to be disposed/cancelled as well, but I keep receiving events from that stream.

Am I doing something wrong?

Capturing the Job from launchIn(this) and explicitly calling job.cancel() before each return statement works, but feels unnecessary/wrong.


Solution

  • After a bit of research, it was made clear that this is a Kotlin coroutines behavior and isn't related to WorkManager at all.

    • Listening to a Flow<Event> is a never-ending job
    • By definition, parent coroutines wait for the child coroutines to finish

    Given these 2 points, it is now apparent why I kept receiving Events even after I returned from the parent coroutine.

    The solution: call coroutineContext[Job]?.cancelChildren() before returning from the parent coroutine.

    override suspend fun doWork(): Result = coroutineScope {
        flowOfEvents
            .onEach(eventChannel::send)
            .launchIn(this)  
    
        if (isQueueEmpty()) {
            coroutineContext[Job]?.cancelChildren()
            return@coroutineScope Result.success()
        }
    
        ... 
    }
    

    Note: calling parent's cancel() throws a CancellationException, which in the context of a WorkManager meant that the Worker#doWork method would actually result in a failed job.