-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Calling Flow.stateIn
with a cancelled scope suspends forever
#4322
Comments
Flow.stateIn
with a cancelled scope suspends forever.Flow.stateIn
with a cancelled scope suspends forever
I agree both that this is a bug and that your fix is the way to go. Would you like to make a PR? If so, could you also make This was a surprisingly difficult decision. Below are my thoughts. There's one more bug/feature/behavior of the same form: flow { }.stateIn(CoroutineScope(EmptyCoroutineContext)) // hangs We can think of If the scope is cancelled when we attempt to wait for the initial value, it hangs: val scope = CoroutineScope(EmptyCoroutineScope).apply { cancel() }
val flow = flow {
emit(1)
yield()
emit(2)
}
val result = MutableStateFlow<Int>()
val sharedFlow = flow.shareIn(scope, SharingStarted.Eagerly)
result.value = sharedFlow.first() // hangs If the original flow is empty, attempting to wait for the first value hangs: val scope = CoroutineScope(EmptyCoroutineScope)
val flow = flow<Int> { }
val result = MutableStateFlow<Int>()
val sharedFlow = flow.shareIn(scope, SharingStarted.Eagerly)
result.value = sharedFlow.first() // hangs From this point of view, it makes sense that This is certainly unintuitive, but can be explained in terms of the provided abstractions. However, there is a crucial detail: val scope = CoroutineScope(EmptyCoroutineScope)
val flow = flow<Int> { error("this flow can't be collected") }
val sharedFlow = flow.shareIn(scope, SharingStarted.Eagerly)
sharedFlow.first() // cancels `scope` and hangs! val scope = CoroutineScope(EmptyCoroutineScope)
val flow = flow<Int> { error("this flow can't be collected") }
flow.stateIn(scope) // throws an exception This behavior was introduced in #2329 without much fanfare, but it already breaks the analogy between Then the fix you're proposing does seem reasonable:
|
Good point, I completely overlooked the empty case, but completely agree with what you wrote. |
Describe the bug
Calling
Flow.stateIn
with a cancelled scope suspends forever. The expected behavior IMHO is forstateIn
to rethrow the cancellation exception of the scope, similar to howscope.async { }.await()
andCompleteableDeferred(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 withstateIn
.The cause of the issue is that
stateIn
awaits aCompleteableDeferred
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 (withCompleteableDeferred(scope.coroutineContext[Job])
).Provide a Reproducer
The text was updated successfully, but these errors were encountered: