Kotlinx.coroutines: Flow toList could cause infinite suspension

Created on 27 Oct 2020  路  2Comments  路  Source: Kotlin/kotlinx.coroutines

In the rather heated discussion in a Telegram chat, there were complaints that calling toList() function on an infinite Flow (like the StateFlow for example) could cause an infinite suspension, therefore, producing a rather hard to catch bug.

For example, this code will block indefinitely:

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() {
    var flow = MutableStateFlow(0)
    runBlocking{
      val list = flow.toList()
    }
}

There are several solutions to that problem:

  • Introduce a default large limit to this operation and throw an error if the limit is exceeded.
  • Throw error immediately for specific types of Flow which are known to be infinite.
  • Update documentation to warn people that using terminal operations on infinite flows could lead to infinite suspensions.

All those solutions could be applied together.

The positive idea from the same discussion. It is possible to introduce something like

inline class LimitedFlow(val flow: Flow): Flow by flow

And define dangerous terminal operations on it. In this case operations like limit would provide type-safe wrappers. And the developer could explicitly declare the Flow as limited manually via something like Flow::asLimited.

flow

Most helpful comment

The property of a flow of being "limited" is non-computable in the general cases and calling a terminal operation like toList() would hang. For example, the following code hangs:

fun main() {
    var flow = flow { awaitCancellation() }
    runBlocking {
      val list = flow.toList()
    }
}

In general, it is indeed a clear programmer's mistake to call toList() on a flow that is known to be never-completing, however, I don't find any of the proposed runtime solutions to be compelling enough and worth effort for a problem like that.

It looks like a "beginner's mistake" to me. This kind of problem would be best prevented by some heuristical inspection in IDE that could flag this kind of toList() calls in simple cases (where IDE can easily prove that the flow cannot complete) with a warning, just like there is a warning on infinite loops that cannot complete without exception. This would help developers who are using shared flows for the first time. Until there is evidence to the contrary, I don't expect this to be a problem in a mature codebase and so it should not require any kind of complex global or runtime analysis.

All 2 comments

It is very easy to make this kind of mistake.

import kotlinx.coroutines.flow.*

suspend fun foo(flow: Flow<Int>) = flow.toList()

suspend fun main() {
    println(foo(flowOf(1, 2, 3))) // [1, 2, 3]
    println(foo(MutableStateFlow(0))) // suspended forever
}

The property of a flow of being "limited" is non-computable in the general cases and calling a terminal operation like toList() would hang. For example, the following code hangs:

fun main() {
    var flow = flow { awaitCancellation() }
    runBlocking {
      val list = flow.toList()
    }
}

In general, it is indeed a clear programmer's mistake to call toList() on a flow that is known to be never-completing, however, I don't find any of the proposed runtime solutions to be compelling enough and worth effort for a problem like that.

It looks like a "beginner's mistake" to me. This kind of problem would be best prevented by some heuristical inspection in IDE that could flag this kind of toList() calls in simple cases (where IDE can easily prove that the flow cannot complete) with a warning, just like there is a warning on infinite loops that cannot complete without exception. This would help developers who are using shared flows for the first time. Until there is evidence to the contrary, I don't expect this to be a problem in a mature codebase and so it should not require any kind of complex global or runtime analysis.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mttmllns picture mttmllns  路  3Comments

jaozinfs picture jaozinfs  路  3Comments

mhernand40 picture mhernand40  路  3Comments

elizarov picture elizarov  路  3Comments

mariusstaicu picture mariusstaicu  路  3Comments