Skip to content
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

flatMapLatest + combine + first leads to execution while cancelled #4034

Open
ansman opened this issue Feb 6, 2024 · 5 comments
Open

flatMapLatest + combine + first leads to execution while cancelled #4034

ansman opened this issue Feb 6, 2024 · 5 comments
Labels

Comments

@ansman
Copy link
Contributor

ansman commented Feb 6, 2024

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
@ansman ansman added the bug label Feb 6, 2024
@ansman ansman changed the title flatMapLatest + combine + first causes flatMapLatest + combine + first leads to execution while cancelled Feb 6, 2024
@dkhalanskyjb
Copy link
Collaborator

Could not reproduce with coroutines 1.7.3 using Android instrumented tests. The output for me is:

Before launch
Got item
After launch

Due to cancellation, the "After first" println didn't execute.

@ansman
Copy link
Contributor Author

ansman commented Feb 7, 2024

Yes, that's right. It does not repro in an instrumented test for us either. But it does when you put that code in Activity.onCreate it does. I'll attach a complete project.

@ansman
Copy link
Contributor Author

ansman commented Feb 7, 2024

Here is a complete example. Run it and see the output in logcat.
Coroutinesbug.zip

@dkhalanskyjb
Copy link
Collaborator

Ok, reproduced.

@ansman
Copy link
Contributor Author

ansman commented Feb 12, 2024

@dkhalanskyjb Can you remove the "waiting for clarification" label so it doesn't get auto closed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants