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:
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.
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.
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: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.