Skip to content

Calling Flow.stateIn with a cancelled scope suspends forever #4322

Open
@francescotescari

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

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions