【问题标题】:Under what circumstances are END blocks skipped in Perl?Perl 在什么情况下会跳过 END 块?
【发布时间】:2016-08-02 04:21:23
【问题描述】:

我有一个长时间运行的程序,它使用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; 的信号处理程序确实会清理目录。例如

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

与使用“微不足道”的信号处理程序一样

#!/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 解释器分叉的情况下?不知道为什么这是必要的,因为删除目录看起来像这将是一个幂等操作。)

清理文件的实际逻辑在这里,在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

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

【问题讨论】:

  • 尝试用sleep(20) or warn($!); print("done\n");替换sleep(20);

标签: perl


【解决方案1】:

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

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

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

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


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

【讨论】:

    【解决方案2】:

    您的信号处理程序$SIG{INT} = sub {} 没有做任何事情,它正在捕获信号并阻止程序退出。

    但要回答您最初的问题,END 块,正如perlmod 所说:

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

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

    【讨论】:

      猜你喜欢
      • 2012-09-07
      • 2021-06-08
      • 2011-01-13
      • 1970-01-01
      • 2012-04-23
      • 2011-10-16
      • 1970-01-01
      相关资源
      最近更新 更多