-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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