Home > OS >  Kotlin Path.useLines { } - How not to get IOException("Stream closed")?
Kotlin Path.useLines { } - How not to get IOException("Stream closed")?

Time:11-04

Kotlin has nice wrappers and shortcuts, but sometimes I get caught not understanding them.

I have this simplified code:

class PipeSeparatedItemsReader (private val filePath: Path) : ItemsReader {
    override fun readItems(): Sequence<ItemEntry> {

        return filePath.useLines { lines -> 
            lines.map { ItemEntry("A","B","C","D",) } 
        }
}

And then I have:

    val itemsPath = Path(...).resolve()
    val itemsReader = PipeSeparatedItemsReader(itemsPath)

    for (itemEntry in itemsReader.readItems())
        updateItem(itemEntry)

    // I have also tried itemsReader.readItems().forEach { ... }

Which is quite straightforward - I expect this code to give me a sequence which opens a file and reads the lines, parses them, and gives ItemEntrys, and when used up, close the file.

What I get, however, is IOException("Stream closed").

Somehow, even before the first item is read (I have debugged), somewhere within Kotlin's wrappers, the reader.in becomes null, so this exception is thrown in hasNext().

I have seen a similar question here: Kotlin to chain multiple sequences from different InputStream?

That one includes a lot of Java boilerplate which I would like to avoid.

How should I code this sequence using Path.useLines()?

CodePudding user response:

Every Kotlin helper with "use" in the name closes the underlying resource at the end of the lambda you pass (at least that's a convention in the stdlib as far as I know). The most common example being AutoCloseable.use.

The Path.useLines extension is no exception:

Calls the block callback giving it a sequence of all the lines in this file and closes the reader once the processing is complete. [emphasis mine]

This means useLines closes the sequence of lines once the block is done, and thus you cannot return a lazy sequence out of it because you can't use it after the useLines function returns.

So, if you want to return a sequence of lines for later use, you cannot return a transformed sequence from that of useLines directly. Sequences actually cannot detect when someone is done using them, hence why useLines needs a lambda to give a "scope" or "lifetime" to the sequence and know when to close the underlying reader.

If you want to wrap this, you have 2 major options: either split the sequence operation and the close operation (make your PipeSeparatedItemsReader closeable), or use a lambda to process things in-place in readItems() the same way useLines does.

  • Related