Perlの`kill`は予想外の`$! == Errno::EINTR` を使用します。

Perlの`kill`は予想外の`$! == Errno::EINTR` を使用します。

TCP接続を処理するためにサブプロセスをフォークするネットワークデーモンを作成しました。SIGINT基本プロセスの各サブプロセスに対して1つずつトリガーして、いくつかのkill最終統計をクリーンアップして収集します。

ほとんどすべてのケースでうまく機能し、サブプロセスは非常に迅速に終了します。ただし、場合によっては、サブプロセスが短いタイムアウト(たとえば5秒)以内にシャットダウンを拒否することがあります。

当時何が起こったのかわからないので、状況を診断するために詳細な出力を追加しました。netcat接続を開いてプロセスを一時停止することがわかりましたnetcat時々原因効果。

効果を再現できるときのデバッグ出力は次のようになります。

REST-server(cleanup_queue): deleting children
REST-server(cleanup_queue): deleting PID 23344 handling localhost:48114
child_delete: Killing child 23344
child_delete: killed child with PID 23344
(r1, r2) = (1, Interrupted system call)
_limited_wait(PID 23344 terminated): waiting up to 5 seconds for condition
_limited_wait(PID 23344 terminated): waiting 0.02 (of 5 remaining) seconds
(r1, r2) = (1, Interrupted system call)
_limited_wait(PID 23344 terminated): waiting 0.04 (of 4.98 remaining) seconds
(r1, r2) = (1, Interrupted system call)
_limited_wait(PID 23344 terminated): waiting 0.08 (of 4.94 remaining) seconds
(r1, r2) = (1, Interrupted system call)
_limited_wait(PID 23344 terminated): waiting 0.16 (of 4.86 remaining) seconds
(r1, r2) = (1, Interrupted system call)
_limited_wait(PID 23344 terminated): waiting 0.32 (of 4.7 remaining) seconds
(r1, r2) = (1, Interrupted system call)
_limited_wait(PID 23344 terminated): waiting 0.64 (of 4.38 remaining) seconds
(r1, r2) = (1, Interrupted system call)
_limited_wait(PID 23344 terminated): waiting 1.28 (of 3.74 remaining) seconds
(r1, r2) = (1, Interrupted system call)
_limited_wait(PID 23344 terminated): waiting 2.46 (of 2.46 remaining) seconds
(r1, r2) = (1, Interrupted system call)
child_delete: PID 23344 refused to terminate within 5s
failed to delete child PID 23344

この場合、待たなければならない「条件」はこの終了の結果です。

sub {
    my $r1 = kill(0, $child_pid);
    my $r2 = $!;
    print "(r1, r2) = ($r1, $r2)\n";
    $r1 != 1 && $r2 == Errno::ESRCH;
}

したがって、予想される結果は、PIDがもはや存在しないため(「許可拒否」のためではなく)、基本プロセスがPIDを「終了」できないことです。

ただし、何らかの理由で「システムコールが中断されました」というメッセージが繰り返し表示されます。

基本的なプロセスは次のシグナルハンドラを使用します。

$SIG{'INT'} = $SIG{'TERM'} = sub ($) {
    my $signal = 'SIG' . $_[0];
    my $me = "signal handler[$$, $signal]";

    print "$me: cleaning up\n"
        if ($verbose > 0);
    cleanup();
    print "$me: executing default action\n"
        if ($verbose > 1);
    $SIG{$_[0]} = 'DEFAULT';
    kill($_[0], $$);                    # execute default action
};

サブプロセスが分岐したら、次のようにシグナルハンドラをリセットします。

sub child_create($)
{
    my ($child) = @_;
    my $pid;

    reaper(0);                          # disable for the child
    if ($pid = fork()) {                # parent
        reaper(1);                      # enable for the parent
    } elsif (defined($pid)) {           # child
        my ($child_fun, @child_param) = @$child;
        my $ret;

        # prevent double-cleanup
        $SIG{'INT'} = $SIG{'TERM'} = $SIG{'__DIE__'} = 'DEFAULT';
        $ret = $child_fun->(@child_param);
        exit($ret);                     # avoid returning from function call
    } else {                            # error
        print STDERR "child_create: fork(): $!\n";
    }
    return $pid;
}

reaper()ちょうどハンドルSIGCHLD

現れる効果の原因は何ですか?サブプロセスはデフォルトで実行while (defined(my $req = $conn->get_request)) {...}(使用HTTP::Daemon)されるため、入力を待つ必要がありますnetcat

追加情報

OS は、VMware で実行される SLES12 SP5 (Perl 5.18.2 を使用) です。

メインサーバーループのコードは次のとおりです。

while (defined(my $conn = $daemon->accept) || $! == Errno::EINTR) {
    my $errno = $!;

    if ($quit_flag != 0) {
        last;
    }
    if ($errno == Errno::EINTR) {
        next;
    }
    #... handle $req->uri->path()
}

関連情報