Description
Describe the bug
Calling Flow.stateIn
with a cancelled scope suspends forever. The expected behavior IMHO is for stateIn
to rethrow the cancellation exception of the scope, similar to how scope.async { }.await()
and CompleteableDeferred(scope.job)
behave on a cancelled scope. This behavior happens not only if the scope is already cancelled, but also if it gets cancelled concurrently with stateIn
.
The cause of the issue is that stateIn
awaits a CompleteableDeferred
that is completed exclusively by a coroutine launched (non-atomically) in the collecting (possibly cancelled) scope.
A possible fix is to bind the CompletableDeferred
here to the job of the collecting scope (with CompleteableDeferred(scope.coroutineContext[Job])
).
Provide a Reproducer
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.coroutines.*
suspend fun main() {
val flow = flowOf(1, 2, 3)
val cancelledScope = CoroutineScope(EmptyCoroutineContext).apply { cancel() }
println("Awaiting stateIn...")
val stateFlow = flow.stateIn(cancelledScope) // Suspends forever
println("Done!") // Never printed
}
// prints "Awaiting stateIn..." and hangs forever