2017-08-09 114 views
3

我從主控主機到從屬主機執行ZFS遠程複製,其中我有一個在主控主機上運行的Perl腳本。如何避免<defunct>進程?

對於每個文件系統,ssh到遠程主機並在監聽模式下啓動mbuffer,然後腳本繼續併發送數據。成功時,mbuffer應該自行退出。

問題

這是相當困難的開始mbuffer通過ssh遠程主機上,然後可以繼續在腳本。我最終做了你可以在下面看到的內容。

問題是,在腳本退出之前,它會爲每個文件系統處理一個文件系統。

問題

有可能避免在<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」(或其一個變體),以便讓內核知道終止的子進程可以清理。 [這個問題](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

當你創建一個進程時,它會一直存在,直到它的父節點獲得它爲止。 (如果其父母先退出,它將自動獲得。)一個過程可以使用waitwaitpid收穫其子女。在創建孩子之前,它還可以通過使用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

其中之一,沒有理由使用P::FM與一個進程。此外,由於您放棄了對流程管理的更好控制,因此它在這裏是不利的。

但是這裏的直接錯誤是在使用exec;這篇文章僅解決這個問題。

exec調用將替換與另一個程序中的進程和永不返回。因此exec之後的子代碼不會運行(請參閱文檔)。因此$pm->finish被掛起,子進程永遠不會被獲得,操作系統將它的信息保存在進程表中,所以有一個不存在的/殭屍。

下面是使用exec直接

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?" } 

這裏的孩子繼承父標準流斷火另一個程序的基本途徑。此外,在某些情況下,錯誤報告很粗糙(不精確),請參閱ikegami的評論。

一個更詳細和忠實的替代品是你在ikegami的answer

+0

當'exec'失敗時,這看起來像是孩子成功啓動了。這就是爲什麼我推薦'open3'而不是(或更好的)。 – ikegami

+0

@ikegami這是我沒有得到 - 這樣我看到它是,如果'叉'失敗有一個消息,而如果我得到'死',那麼孩子_was_成功創建。或者你的意思是'waitpid'不會揭示問題出在哪裏?我懷疑我可能會在這裏失去東西。 (順便說一句,我不是故意說這是一個完整而強大的方法,我希望帖子明確)。謝謝你的評論。 – zdim

+0

你的方式無法區分無法啓動'ssh'和'ssh'做'exit(2)'。這不是Perl的「系統」工作原理。這不是C的「系統」工程。這不是'bash'的工作方式。在所有這些情況下,'exec'失敗和準備'exec'的錯誤被認爲是啓動程序失敗,而不是啓動程序返回的錯誤。 (對於Perl的'system',它會返回'$?= -1'並設置'$!'。) – ikegami