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()
}