September 20, 2022·4 min read·engineeringperlioerror-handling

Perl preserves read errors until a stream is closed

Perl 5.37.4 stops clearing the stream error flag after readline failures. Explicit close checks can now report errors that previously disappeared, and implicit close can produce new warnings.

A program can finish reading a stream without having read it successfully. Reaching the end of a loop and establishing that the input completed are different conditions. Perl 5.37.4 changes readline, including its angle-bracket spelling, so a read error no longer clears the handle's stream error flag. The release notes explain that preserving the flag allows errors to remain visible when the stream closes. The release history dates the development release to September 20.

This appears under incompatible changes because a corrected error path can change observable results. A close that previously appeared successful may now return failure. When code relies on implicit close, Perl may produce a warning where the older behavior was quiet. The change makes an existing stream failure observable later in the operation.

The stream retains information about a failed read

The function manual shipped with the release defines readline in scalar context as returning the next line, and eventually returning undef after end of file. Its definition of a line depends on the input record separator. It is therefore a record-reading operation whose stopping behavior must be interpreted alongside the handle's error state.

The same manual says an operating-system error sets $! to the corresponding error message. That provides a way to diagnose the immediate failure. The stream error flag is another part of the handle's state. Clearing it after the failure erased information needed by a later check, even though the read itself had already failed.

The release preserves that state in the ordinary reading path. It documents a narrow exception for reading from the child process used for globbing in miniperl. That exception should not be read as a general rule that applications need to clear stream errors themselves. The selected incompatible change concerns the interpreter's previous clearing behavior.

I still choose Perl for small programs that reshape text directly. For those programs, a loop that emits plausible output from only part of a file is a serious failure. Retaining the read error gives the program another opportunity to distinguish completed work from a truncated transformation.

Closing an input handle can report a read failure

The manual defines close as closing the associated file or pipe, flushing buffers, and closing the system descriptor. It returns true only when those operations succeed and no PerlIO layer reported an error. That last condition explains why an earlier read error can affect the result of closing an input stream.

use strict;
use warnings;

my $path = shift @ARGV;
defined $path or die "input path required\n";
open my $input, "<", $path or die "open failed: $!\n";

my $records = 0;
while (defined(my $line = readline $input)) {
    ++$records;
}

close $input or die "input stream failed on close: $!\n";
print "$records records\n";

This example deliberately reports its result after the close check. In 5.37.4, preserving the stream flag lets that check fail when a read error occurred. The loop condition alone establishes that no further defined record was returned. It does not make a complete success claim about the whole input operation.

An explicit close also gives application code a place to choose its failure policy. Printing a warning, exiting with an error, or discarding a partially assembled result are application decisions. The interpreter's contribution is to retain the evidence needed for those decisions. The release does not make previously emitted output disappear when a later close reveals a problem.

For a streaming transformation, that last point matters. Some output may already have reached its destination. Detecting the failure correctly cannot make the operation atomic after the fact. A program requiring all-or-nothing publication needs a separate output strategy. The stream flag improves failure reporting without changing the surrounding application's transaction boundaries.

New warnings may describe an older problem

The release notes explicitly warn that implicit close may now emit a warning. A program that has never checked close can therefore acquire a diagnostic without adding any new read operation. The diagnostic can indicate a failure that the older interpreter cleared instead of retaining.

This is why compatibility analysis needs to include unsuccessful operations. Normal input can produce the same records under both interpreters while a failed input changes the program's diagnostics or exit behavior. Calling that merely a warning change understates the effect for code whose success decision depends on the close result.

Piped handles have further documented close behavior. Closing a pipe waits for its process in the ordinary case, and can return false for a failed system call or a nonzero child status. The manual distinguishes a child's nonzero exit from an operating-system error through $!. A false close result consequently needs interpretation appropriate to the kind of handle involved.

My preference is to make the input handle's successful completion part of the program's result. The September change supports that policy by keeping the stream error available through close. It also gives programs that relied on silent cleanup an observable warning when their input operation failed.