10

open3のドキュメントを確認しましたが、理解できなかった部分は次のとおりです。

子のstdoutライターとそのstderrライターから読み取ろうとすると、ブロックに問題が発生します。つまり、select()またはIO :: Selectを使用する必要があります。これは、sysread(を使用するのが最適であることを意味します。 )通常のもののreadline()の代わりに。

永久にブロックする可能性があるため、これは非常に危険です。bcのようなものと通信し、書き込みと読み取りの両方を行うことを前提としています。bcのようなコマンドが一度に1行を読み取り、一度に1行を出力することを「知っている」ので、これはおそらく安全です。ただし、最初に入力ストリーム全体を読み取るsortのようなプログラムは、デッドロックを引き起こす傾向があります。

open3だから私はそれをもっとよく知ることを望んで試してみました。これが最初の試みです:

sub hung_execute {
    my($cmd) = @_;
    print "[COMMAND]: $cmd\n";
    my $pid = open3(my $in, my $out, my $err = gensym(), $cmd);
    print "[PID]: $pid\n";
    waitpid($pid, 0);
    if(<$err>) {
        print "[ERROR] : $_" while(<$err>);
        die;
    }
    print "[OUTPUT]: $_" while (<$out>);
}

ここで初期化する必要があることに注意して$errください。

とにかく、これは4096文字(私のマシンの制限)を超えるテキストファイルであるとexecute("sort $some_file");指定するとハングします。$some_file

次に、このFAQを調べました。以下は、私の新しいバージョンのexecuteです。

sub good_execute {
    my($cmd) = @_;
    print "[COMMAND]: $cmd\n";
    my $in = gensym();
    #---------------------------------------------------
    # using $in, $out doesn't work. it expects a glob?
    local *OUT = IO::File->new_tmpfile;
    local *ERR = IO::File->new_tmpfile;
    my $pid = open3($in, ">&OUT", ">&ERR", $cmd);
    print "[PID]: $pid\n";
    waitpid($pid, 0);
    seek $_, 0, 0 for \*OUT, \*ERR;
    if(<ERR>) {
        print "[ERROR] : $_" while(<ERR>);
        die;
    }
    print "[OUTPUT]: $_" while (<OUT>);
}

sortコマンドは正常に実行されますが、理由がわかりません。

[更新] @tchristの答えを読んだ後、私は読んだ、そしてもう少しグーグルした後、このバージョンの:IO::Selectを思いついた。execute

sub good_execute {
    my($cmd) = @_;
    print "[COMMAND]: $cmd\n";
    my $pid = open3(my $in, my $out, my $err = gensym(), $cmd);
    print "[PID]: $pid\n";
    my $sel = new IO::Select;
    $sel->add($out, $err);
    while(my @fhs = $sel->can_read) {
        foreach my $fh (@fhs) {
            my $line = <$fh>;
            unless(defined $line) {
                $sel->remove($fh);
                next;
            }
            if($fh == $out) {
                print "[OUTPUT]: $line";
            }elsif($fh == $err) {
                print "[ERROR] : $line";
            }else{
                die "[ERROR]: This should never execute!";
            }
        }
    }
    waitpid($pid, 0);
}

これは正常に機能しており、いくつかのことが明らかになりました。しかし、全体像はまだ少しぼんやりしています。

だから私の質問は:

  1. 何が問題なのhung_executeですか?
  2. open3呼び出しのおかげでうまくいくと思いgood_executeます。>&しかし、なぜそしてどのように?
  3. また、ファイルハンドルに(の代わりに)good_execute字句変数を使用した場合は機能しませんでした。このエラーが発生しました:。なぜそうなのか?my $outOUTopen3: open(GLOB(0x610920), >&main::OUT) failed: Invalid argument
  4. 一度に書き込むことができるファイルハンドルは1つだけのようです。リソースを保持しているハンドルを破棄すると、他のハンドルは待機し続けます。以前は、STDERRとSTDOUTは独立したストリームであり、リソースを共有していないと考えていました。私の理解はここでは少し欠陥があると思います。これについてもいくつかの指針を教えてください。
4

2 に答える 2

13

あなたは私がドキュメンテーションで書いたまさにその問題に遭遇しました、そしてそれからいくつか。あなたがそれから読む前にあなたが子供が出るのを待っているのであなたは行き​​詰まっています。出力のパイプバッファ以上のものがある場合は、ブロックして次に終了します。さらに、ハンドルの端を閉じていません。

他にもエラーがあります。ブロッキングreadlineを実行し、その結果を破棄しただけなので、その方法でハンドルの出力をテストすることはできません。さらに、stdoutの前にすべてのstderrを読み取ろうとし、stdoutに出力のパイプバッファー以上がある場合、stderrからの読み取りをブロックしている間、子供はstdoutへの書き込みをブロックします。

これを正しく行うには、実際には、、selectまたはを使用する必要があります。IO::Selectハンドルで使用可能な出力がある場合にのみハンドルから読み取る必要があります。またselect、非常に幸運でない限り、バッファリングされた呼び出しをと混合してはなりません。

于 2012-04-05T13:42:19.450 に答える
8

hung_execute

 Parent                     Child
 ------------------------   ------------------------
 Waits for child to exit
                            Writes to STDOUT
                            Writes to STDOUT
                            ...
                            Writes to STDOUT
                            Tries to write to STDOUT
                              but the pipe is full,
                              so it blocks until the
                              pipe is emptied some.

デッドロック!


good_execute

 Parent                     Child
 ------------------------   ------------------------
 Waits for data
                            Writes to STDOUT
 Reads the data
 Waits for data
                            Writes to STDOUT
 Reads the data
 Waits for data
 ...                        ...
                            Writes to STDOUT
 Reads the data
 Waits for data
                            Exits, closing STDOUT
 Reads EOF
 Waits for child to exit

パイプがいっぱいになり、子供をブロックする可能性があります。しかし、親はすぐにそれを空にするためにやって来て、子のブロックを解除します。デッドロックはありません。


">&OUT"に評価され>&OUTます。(補間する変数はありません)

">&$OUT"に評価され>&GLOB(0x########)ます。(補間し$OUTました。)

字句ファイルハンドル(またはその記述子)を渡す方法はありますが、それらに関するバグがあるため、私は常にパッケージ変数を。で使用しますopen3


STDOUTとSTDERRは独立しています(のようなことをしない限り2>&1、それでも、それらは別々のフラグとバッファーを持ちます)。彼らがそうではないことを発見した場合、あなたは間違った結論に達しました。

于 2012-04-05T20:04:12.777 に答える