Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Under what circumstances are END blocks skipped in Perl?

Tags:

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?

like image 819
Gregory Nisbet Avatar asked Aug 02 '16 04:08

Gregory Nisbet


2 Answers

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 its very existence prevented the process from being killed. In this situation, the program won't exit as a result of the signal unless it chooses to exit (by calling die or exit in the handler. If it does, 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.
like image 78
ikegami Avatar answered Oct 03 '22 22:10

ikegami


Your signal handler $SIG{INT} = sub {} isn't doing nothing, it is trapping the signal and preventing the program from exiting.

But to answer your original question, END blocks, as perlmod says:

is executed as late as possible, that is, after perl has finished running the program and just before the interpreter is being exited, even if it is exiting as a result of a die() function. (But not if it's morphing into another program via exec, or being blown out of the water by a signal--you have to trap that yourself (if you can).)

That is, a fatal signal, if not trapped, circumvents Perl's global destruction and does not call END blocks.

like image 44
mob Avatar answered Oct 03 '22 21:10

mob