我有一个长期运行的程序,它使用File::Temp::tempdir
创建临时文件,有时会通过^C
中断它。
下面的程序打印它创建的临时目录的名称和其中文件的名称
#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];
my $dir = tempdir(CLEANUP => 1);
print "$dirn";
print "$dir/temp.txtn";
`touch $dir/temp.txt`;
exit;
在OS X上,这会在/var/folders
中创建一个目录
如果最后一行是exit;
或die;
,则文件夹将被清理,其中的临时文件将被删除。
但是,如果我们用sleep 20;
替换最后一行,然后通过^C
中断perl程序,那么临时目录仍然存在。
% 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
%
使用只调用exit;
的信号处理程序确实会清理目录。例如
#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];
$SIG{INT} = sub { exit; };
my $dir = tempdir(CLEANUP => 1);
print "$dirn";
print "$dir/temp.txtn";
`touch $dir/temp.txt`;
sleep 20;
就像使用"琐碎"的信号处理程序一样
#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];
$SIG{INT} = sub { };
my $dir = tempdir(CLEANUP => 1);
print "$dirn";
print "$dir/temp.txtn";
`touch $dir/temp.txt`;
sleep 20;
我试着浏览源代码(https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm)以确定tempdir
如何注册清理动作
这是出口处理程序安装
https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L1716
它调用CCD_ 10
https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L948
它修改了全局散列%dirs_to_unlink
和%files_to_unlink
,但出于某种原因使用pid $$
作为密钥(可能是在Perl解释器分叉的情况下?不确定为什么这是必要的,因为删除目录似乎是一个幂等运算。)
清理文件的实际逻辑在END
块中。
https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L878
一个快速的实验表明,当perl正常或异常退出时,END
块确实会运行。
sleep 20;
END {
print "5n";
}
# does not print 5 when interrupted
在这里运行
$SIG{INT} = sub {};
sleep 20;
END {
print "5n";
}
# does print 5 when interrupted
所以。。。为什么END
块在SIGINT之后被跳过,除非有一个信号处理程序,即使是看起来什么都不应该做的信号处理程序?
默认情况下,SIGINT会终止进程[1]。所谓kill,我的意思是进程立即被内核终止。该进程无法执行任何清理。
通过设置SIGINT的处理程序,可以覆盖这种行为。调用信号处理程序而不是终止进程。它可能什么都不会做,但它的存在本身就阻止了这个过程被扼杀。在这种情况下,程序不会因为信号而退出,除非它选择退出(通过在处理程序中调用die
或exit
)。如果退出,它将有机会正常清理。
请注意,如果在系统调用期间传入了定义了处理程序的信号,则系统调用将退出并返回错误EINTR
,以便程序能够安全地处理该信号。这就是sleep
在收到SIGINT后立即返回的原因。
如果您使用$SIG{INT} = 'IGNORE';
,则该信号将被完全忽略。任何正在进行的系统调用都不会被中断。
- 在我的系统中,
man 1 kill
列出了信号的默认动作
您的信号处理程序$SIG{INT} = sub {}
并没有无所事事,它捕获信号并阻止程序退出。
但为了回答你最初的问题,END
会阻止,正如perlmod
所说:
尽可能晚地执行,也就是说,在perl完成程序运行之后,就在解释器退出之前,即使它是由于die()函数而退出的。(但如果它通过exec变形为另一个程序,或者被一个信号吹出水面,就不是这样了——你必须自己设置陷阱(如果可以的话)。)
也就是说,一个致命的信号,如果没有被捕获,就会绕过Perl的全局破坏,并且不会调用END
块。