Try..finally in buildSequence, finally not called in some situations


#1

I have been experimenting with buildSequence and try…finally.

If I don’t iterate the full sequence it seems finally clause is not called.

Consider this

import kotlin.coroutines.experimental.buildSequence

fun test() = buildSequence {
    try {
        println("Yield 1")
        yield(1)
        println("Yield 2")
        yield(2)
        println("Yield 3")
        yield(3)
    } finally {
        println("Finally")
    }
}

fun main(argv: Array<String>) {
    for (v in test().take(2))
    {
        println("Received: $v")
    }
}

My expectation is that this program would print:

Yield 1
Received: 1
Yield 2
Received: 2
Finally

But it prints:

Yield 1
Received: 1
Yield 2
Received: 2

If I change to take(100) in order to iterate the full sequence finally seems to be invoked:

Yield 1
Received: 1
Yield 2
Received: 2
Yield 3
Received: 3
Finally

In a more realistic scenario this could mean that my code doesn’t call a database connection when I would expect it would.

Best Regards,
orange


#2

I can explain why it is not called, but I cannot offer a solution: Your sequence is frozen at yield(2). Kotlin has no way of knowing it must exit at that point (i.e. you don’t (and can’t?) send a signal to the sequence telling it to terminate), and invoke the finally block.


#3

The finally is not invoked on abandoned coroutines by design. More details about this particular design decision can be found in the corresponding design document: https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md#resource-management-and-gc


#4

I can understand abandoned coroutines should be avoided (like aborting threads) but a major feature of lazy sequences are that they can be “abandoned”.

This makes me wonder if try..finally should be forbidden in buildSequence to avoid confusion that may lead to file, mutex lock and connection leaks.


#5

How would you forbid it?

The yield() call could happen from a closure and the code calling the closure could wrap that in a try-finally. I don’t think there’s a way to statically analyse this all at compile-time.

Is it possible to dynamically check this at runtime, in the generated byte-code?