question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Flow.collects receives items after it was cancelled on a single thread

See original GitHub issue

Could anyone explain to me why this code throws an exception (on Android)? The collect and cancel functions are both called on the main thread. I tried this with both version 1.2.1 and 1.3.0-M1

var cancelled = false
val job = GlobalScope.launch(Dispatchers.Main) {
    val flow = flow {
        while (true) {
            emit(Unit)
        }
    }
    flow.flowOn(Dispatchers.IO)
        .collect {
            if (cancelled) { // main thread
                throw IllegalStateException() // this exception is thrown
            }
        }
}

GlobalScope.launch(Dispatchers.Main) {
    delay(1000)
    job.cancel() // main thread
    cancelled = true // set to true after the Flow was cancelled.
}

Here is an Android sample project, just run the app and it will crash.

I’m not sure if this is intended behaviour or a bug. I need to make sure all Flows are cancelled in the Android onDestroy so they do not try to update the view if it is already gone. So if this is not a bug I am not sure how I would need to handle this otherwise.

There is a long discussion about this with more code examples on Slack: https://kotlinlang.slack.com/archives/C1CFAFJSK/p1559908916083600

We had some trouble trying to reproduce this in a unit test (it did not throw the exception), so that’s why I attached an Android project.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:2
  • Comments:13 (8 by maintainers)

github_iconTop GitHub Comments

19reactions
PaulWoitaschekcommented, Dec 21, 2019

I have replaced every single collect with a safeCollect function in my project:

/**
 * Only proceed with the given action if the coroutine has not been cancelled.
 * Necessary because Flow.collect receives items even after coroutine was cancelled
 * https://github.com/Kotlin/kotlinx.coroutines/issues/1265
 */
suspend inline fun <T> Flow<T>.safeCollect(crossinline action: suspend (T) -> Unit) {
  collect {
    coroutineContext.ensureActive()
    action(it)
  }
}

It would be great if this could be implemented as a default as it’s really unexpected behavior and its error prone to always have to remember to not use specific library functions. On Android my collect functions are always right before they touch the views so they must not get invoked after the view is gone.

5reactions
ansmancommented, Dec 23, 2019

This is pretty unexpected to me but explains the issue with flows. This does feel like a very dangerous behavior on Android where objects tend to be nulled out in response to lifecycle methods. This would definitely not happen with RX since like @nickallendev said RX checks for cancellation both in the producer and consumer sides

Read more comments on GitHub >

github_iconTop Results From Across the Web

Asynchronous Flow - Kotlin
The following example shows how the flow gets cancelled on a timeout when running in a withTimeoutOrNull block and stops executing its code:....
Read more >
Kotlin flows on Android - Android Developers
In coroutines, a flow is a type that can emit multiple values sequentially, as opposed to suspend functions that return only a single...
Read more >
Kotlin Flows in Android summary - ProAndroidDev
The launchIn operator returns a Job that can be used to cancel() the ... If you only need to collect a single flow,...
Read more >
A safer way to collect flows from Android UIs | by Manuel Vivo
Note: A cold flow is a type of flow that executes the producer block of code on-demand when a new subscriber collects. For...
Read more >
How to collect items from a Flow until a particular condition is ...
I know there is a function Flow.takeWhile but it doesn't emit the value where predicate returns false. In my case I want that...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found