在什么情况下在Perl中跳过END块?

时间:2016-08-02 04:21:24

标签: perl

我有一个长期运行的程序,使用File::Temp::tempdir创建临时文件,有时会通过^C中断它。

以下程序打印它创建的临时目录的名称以及其中的文件名。

#!/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;

在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;的信号处理程序会清理目录。 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;

使用&#34;琐事&#34;信号处理程序

#!/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;

我尝试查看源代码(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

调用_deferred_unlink

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

修改了全局哈希值%dirs_to_unlink%files_to_unlink,但出于某种原因使用pid $$作为键(可能是在Perl解释器分叉的情况下?不知道为什么会这样做&#39 ;必要的,因为删除一个目录似乎是一个幂等操作。)

清理文件的实际逻辑在END块中。

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

快速实验表明,当perl正常或异常退出时,END块确实会运行。

sleep 20;

END {
    print "5\n";
}

# does not print 5 when interrupted

并且在这里运行

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

END {
    print "5\n";
}

# does print 5 when interrupted

那么......为什么在SIGINT之后会跳过END块,除非有一个信号处理程序,即使它看起来应该什么都不做?

2 个答案:

答案 0 :(得分:6)

默认情况下,SIGINT会终止进程 [1] 。通过kill,我的意思是该进程立即被内核终止。该过程无法进行任何清理。

通过为SIGINT设置处理程序,您可以覆盖此行为。而不是杀死进程,而是调用信号处理程序。它可能没有做任何事情,但它的存在阻止了这个过程被杀死。在这种情况下,程序不会因信号而退出,除非它选择退出(通过调用处理程序中的dieexit。如果确实如此,它将有机会正常清理。

请注意,如果在系统调用期间输入了定义了处理程序的信号,则系统调用将以错误EINTR退出,以便程序可以安全地处理该信号。这就是收到SIGINT后sleep返回的原因。

如果您使用$SIG{INT} = 'IGNORE';,则信号将被完全忽略。任何正在进行的系统调用都不会被中断。

  1. 在我的系统上,man 1 kill列出了信号的默认操作。

答案 1 :(得分:5)

您的信号处理程序No message body reader has been found for class DocumentApiModel 没有做任何事情,它正在捕获信号并阻止程序退出。

但要回答原始问题,$SIG{INT} = sub {}阻止,perlmod说:

  

尽可能晚地执行,也就是说,在perl完成程序运行之后并且在退出解释器之前,即使它是由于die()函数而退出。 (但如果它通过执行器变成另一个程序,或者被信号吹出水面则不会 - 你必须自己陷阱(如果可以的话)。)

也就是说,一个致命的信号,如果没有被困,会绕过Perl的全局破坏,并且不会调用END块。