2017-08-09 6 views
3

私はマスターホストからスレーブホストへのZFSリモートレプリケーションを行います。ここでは、マスターホスト上で動作するPerlスクリプトがあります。<defunct>の処理を回避する方法はありますか?

ファイルシステムごとに、リモートホストにsshし、リスニングモードでmbufferを開始してから、スクリプトを継続してからデータを送信します。成功すると、mbufferは単独で終了する必要があります。

問題

SSH経由でリモートホスト上mbuffer開始し、スクリプトを継続できるようにするには非常に困難でした。私はあなたが下で見ることができることをやりました。

問題は、スクリプトが終了するまで、<defunct>が各ファイルシステムに1つずつ処理されることです。

<defunct>プロセスを避けることが可能である

質問?

sub mbuffer { 
    my ($id, $zfsPath) = @_; 

    my $m = join(' ', $mbuffer, '-I', $::c{port}); 
    my $z = join(' ', $zfs, 'receive', , $zfsPath); 
    my $c = shellQuote($ssh, $::c{slaves}{$id}, join('|', $m, $z)); 

    my $pm = Parallel::ForkManager->new(1); 
    my $pid = $pm->start; 
    if (!$pid) { 
     no warnings; # fixes "exec" not working 
     exec($c); 
     $pm->finish; 
    } 

    sleep 3; # wait for mbuffer to listen 

    return $pid; 
} 
+5

親プロセスは、終了した子プロセスをクリーンアップできることをカーネルに知らせるために、子プロセスに対して常に 'wait'(またはそのバリエーションの1つ)を呼び出さなければなりません。 [この質問](https://stackoverflow.com/questions/9164316/c-fork-without-wait-defuncts-execl)には、正しい方向に向けるかもしれない答えがあります。 – Thomas

+3

最も簡単な修正は '$ SIG {CHLD} = 'IGNORE'を設定することです。 ['perldoc -f fork'](http://metacpan.org/pod/perlfunc#fork)を参照してください。 – mob

+2

無効なプロセスまたはゾンビプロセスは、親プロセスが' wait'を呼び出すことなく終了したプロセスです。このように、カーネルは終了した子プロセスのエントリを保持するので、親プロセスが 'wait'を呼び出すと、必要な情報が返されます。ゾンビを避けるために、親プロセスは子プロセスを待つ必要があります。 – direprobs

答えて

3

プロセスを作成すると、そのプロセスが親プロセスに戻るまでスティックされます。 (親が最初に出てくると、自動的に刈り取られます)。プロセスは子孫をwaitまたはwaitpidを使って収穫することができます。また、子を作成する前に、local $SIG{CHLD} = 'IGNORE';を使用して自動的に子どもを刈り取ることもできます。


注意:Parallel :: ForkManagerは、単一の子を起動するための適切なツールではありません。一人の労働者を育てるのは目的ではありません。

use String::ShellQuote qw(shell_quote); 

sub mbuffer { 
    my ($id, $zfsPath) = @_; 

    my $mbuffer_cmd = shell_quote($mbuffer, '-I', $::c{port}); 
    my $zfs_cmd  = shell_quote($zfs, 'receive', $zfsPath); 
    my $remote_cmd = "$mbuffer_cmd | $zfs_cmd"; 
    my $local_cmd = shell_quote($ssh, $::c{slaves}{$id}, $remote_cmd); 

    # open3 will close this handle. 
    # open3 doesn't deal well with lexical handles. 
    open(local *CHILD_STDIN, '<', '/dev/null') or die $!;  

    return open3('<&CHILD_STDIN', '>&STDOUT', '>&STDERR', $local_cmd); 
} 

IPC :: Open3はかなり低いレベルですが、既存のコードに最も近いです。プロセスの起動方法として、IPC :: Run3とIPC :: Runがあります。

1

1つでは、P::FMを1つのプロセスで使用する理由はありません。また、プロセス管理の細かい制御をあきらめてしまうため、ここでは不利になります。

ただし、ここでは直接エラーはexecです。この投稿はそれだけを扱っています。

execコールは、処理中のものを別のプログラムに置き換えます。は、を返しません。だから、子どもの中のコードがexecの後に実行されない(docsを参照)。したがって、$pm->finishがぶら下がっているため、子プロセスは決して収穫されず、OSはプロセステーブルにその情報を保持していますので、存在しない/ゾンビがあります。ここで

は、子が親から標準ストリームを継承ここで直接

my $cmd = '...'; 

my $pid = fork // die "Can't fork: $!"; 

if ($pid == 0) { 
    exec $cmd; 
    die "exec shouldn't return: $!"; 
} 
my $gone = waitpid $pid, 0; 

if ($gone > 0) { say "Child $gone exited with $?" } 
elsif ($gone < 0) { say "No $pid process ($gone), reaped already?" } 
else    { say "Process $pid still running?" } 

execを使用して別のプログラムをオフに発射する基本的な方法です。また、エラー報告が粗い(不正確な)場合があります。池上氏に感謝します。

ikegamiのanswerでは、あなたがしていることのより詳細で忠実な置き換えがあります。

+0

これは、 'exec'が失敗したときに子プロセスが正常に起動されたように見えます。だから私は代わりに 'open3'を推奨しています。 – ikegami

+0

@ikegamiこれは私が得意でないものです。フォークが失敗するとメッセージが表示され、「死ぬ」と答えた場合は子が正常に作成されたことがわかります。あるいは、「waitpid」がどこに問題があるのか​​わからないということですか?私はここに欠けているかもしれないと思う。 (Btw、これは完全で頑丈な方法だとは言いませんでしたが、私はその投稿が明確であることを願っています)ご意見ありがとうございました。 – zdim

+0

あなたのやり方では、 'exit(2)'を実行して 'ssh'と' ssh'を起動する失敗を区別することはできません。それはPerlの 'system'がどのように動作するかではありません。それはCの「システム」の仕事ではない。それは「バッシュ」の仕組みではありません。これらすべてのケースでは、 'exec'の失敗と' exec'を準備するエラーは、起動されたプログラムによってエラーが返されるのではなく、プログラムの起動に失敗したように見えます。 (Perlの 'system'の場合、' $?= -1'を返し、 '$!'を設定します。) – ikegami

関連する問題