Description
Describe the bug
Firstly, it's possible this is expected behavior but it certainly doesn't seem that way.
We have a flow that we collect using first()
. This flow is a combination of multiple upstream flows but they all emit their items synchronously. The downstream flow limits the number of emissions to a single element then uses awaitCompletion()
to avoid terminating the flow. This is the flow we call first()
on.
Under some circumstances, we are seeing the first()
call resuming after the scope has been cancelled which is causing a crash for us.
If either the flatMapLatest
or combine
is removed, the code behaves as expected, although first()
never resumes.
I know there are no real guarantees that code won't be running while canceled. But there are few things that makes me think that this doesn't apply in this case:
- There is no non-cooperative suspension as far as I know.
- Only a single thread is involved.
Provide a Reproducer
This reproduces it on an Android device, but it's possible it can reproed on the JVM too.
// This must be run on the main thread
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
println("Before launch")
scope.launch {
flowOf(listOf(1, 2, 3))
.flatMapLatest { numbers ->
combine(numbers.map { flowOf(it) }) { it.sum() }
}
.onEach { println("Got item") }
.first()
println("After first, scope is active: ${scope.isActive}")
}
println("After launch")
scope.cancel()
I expect this to print:
Before launch
Got item
After first, scope is active: true
After launch
Instead I'm seeing this:
Before launch
Got item
After launch
After first, scope is active: false