Search code examples
perltimeout

How to interrupt a read of a lexical filehandle in Perl?


As far as I know, the pattern for interrupting something after some time in Perl, is as follows:

#!/usr/bin/perl
local $SIG{ALRM} = sub { die "timed out\n"; };
my $pid;
eval {
    alarm 10;
    # do stuff
    alarm 0;
};
print "done\n";
kill(9, $pid) if $pid;

So one example of a "something" is opening a subprocess for reading which might randomly hang. Simulating it for testing purposes works as expected:

# replace "# do stuff" with:
    $pid = open FH, "sleep 120|";
    my $line = <FH>;
    close FH;

However, these globally-named things can't be passed to other subroutines, so I really want to use the following implementation:

# replace "# do stuff" with:
    $pid = open my $fh, "sleep 120|";
    my $line = <$fh>;
    close $fh;

... but when I run this, the Perl script hangs indefinitely! Why?


Solution

  • When there are no references left to a variable (perhaps as a result of it going out of scope), Perl cleans up the resources associated with that variable. Destructors are called, memory is freed, file handles are closed, etc. This is the case even if the variable ceases to be referenced due to an exception.

    When a file handle created using open -| or open |- is closed, Perl waits for the child process to end. This happens not just for explicit calls to close, but for implicit closes that happen as a result of the destroying an open file handle.

    In your scenario, Perl is waiting for the child to complete as a result of the implicit close of the file handle. It doesn't hang indefinitely, but for the remainder of the 120 seconds it takes for the child to end. This is the behaviour one should observe for both versions of the program, and it is the behaviour one observes for both versions unless one is using an old version of Perl. Up until the release of 5.18 9 years ago, a bug skipped closing of file handles during global destruction.