Gregory Nisbet Gregory Nisbet - 4 months ago 7
Perl Question

Under what circumstances are END blocks skipped in Perl?

I have a long-running program that used

File::Temp::tempdir
to create a temporary file and sometimes interrupted it via
^C
.

The following program prints the name of the temporary directory it creates and the name of a file in it.

#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];

my $dir = tempdir(CLEANUP => 1);
print "$dir\n";
print "$dir/temp.txt\n";

`touch $dir/temp.txt`;
exit;


On OS X, this creates a directory inside
/var/folders


If the last line is
exit;
or
die;
, then the folder will get cleaned up and the temporary file inside it will get deleted.

However, if we replace the last line with
sleep 20;
and then interrupt the perl program via
^C
, the temporary directory remains.

% perl maketemp.pl
/var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6
/var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6/temp.txt
^C
% stat /var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6/temp.txt
16777220 6589054 -rw-r--r-- 1 <name> staff 0 0 "Aug 1 20:46:27 2016" "Aug 1 20:46:27 2016" "Aug 1 20:46:27 2016" "Aug 1 20:46:27 2016" 4096 0 0
/var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6/temp.txt
%


using a signal handler that just calls
exit;
does clean up the directory. E.g.

#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];

$SIG{INT} = sub { exit; };

my $dir = tempdir(CLEANUP => 1);
print "$dir\n";
print "$dir/temp.txt\n";

`touch $dir/temp.txt`;
sleep 20;


As does using a "trivial" signal handler

#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];

$SIG{INT} = sub { };

my $dir = tempdir(CLEANUP => 1);
print "$dir\n";
print "$dir/temp.txt\n";

`touch $dir/temp.txt`;
sleep 20;


I tried looking through the source code (https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm) to determine how
tempdir
is registering a cleanup action

Here's the exit handler installation

https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L1716

which calls
_deferred_unlink


https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L948

which modified the global hashes
%dirs_to_unlink
and
%files_to_unlink
, but uses the pid
$$
as a key for some reason (probably in case the Perl interpreter forks? Not sure why that's necessary though since removing a directory seems like it would be an idempotent operation.)

The actual logic to clean up the files is here, in the
END
block.

https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L878

A quick experiment shows that
END
blocks are indeed run when perl has exited normally or abnormally.

sleep 20;

END {
print "5\n";
}

# does not print 5 when interrupted


And are run here

$SIG{INT} = sub {};
sleep 20;

END {
print "5\n";
}

# does print 5 when interrupted


So ... why does the
END
block get skipped after a SIGINT unless there's a signal handler, even one that seems like it should do nothing?

Answer

By default, SIGINT kills the process[1]. By kill, I mean the process is immediately terminated by the kernel. The process doesn't get to perform any cleanup.

By setting a handler for SIGINT, you override this behaviour. Instead of killing the process, the signal handler is called. It might not do anything, but it's very existence prevented the process from being killed. The program won't exit as a result of the signal unless it chooses to exit (by calling die or exit in the handler), in which case, it would get a chance to cleanup as normal.

Note that if a signal for which a handler was defined comes in during a system call, the system call exits with error EINTR in order to allow the program to safely handle the signal. This is why sleep returns as soon as SIGINT is received.

If instead you had used $SIG{INT} = 'IGNORE';, the signal would have been completely ignored. Any systems calls in progress won't be interrupted.


  1. On my system, man 1 kill lists the default actions of signals.