2012-04-05 36 views
9

tôi đã đi qua các tài liệu của open3 và đây là phần mà tôi không thể hiểu:Tại sao IPC :: Open3 bị bế tắc?

If you try to read from the child's stdout writer and their stderr writer, you'll have problems with blocking, which means you'll want to use select() or the IO::Select, which means you'd best use sysread() instead of readline() for normal stuff.

This is very dangerous, as you may block forever. It assumes it's going to talk to something like bc, both writing to it and reading from it. This is presumably safe because you "know" that commands like bc will read a line at a time and output a line at a time. Programs like sort that read their entire input stream first, however, are quite apt to cause deadlock.

Vì vậy, tôi đã cố gắng ra open3 hy vọng sẽ biết nó tốt hơn. Đây là nỗ lực đầu tiên:

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

Thật thú vị khi lưu ý rằng tôi phải khởi tạo $err tại đây.

Dù sao, điều này chỉ bị treo khi tôi execute("sort $some_file"); cho rằng $some_file là tệp văn bản chứa hơn 4096 ký tự (giới hạn cho máy của tôi).

sau đó tôi nhìn vào this FAQ, và dưới đây là phiên bản mới của tôi thực hiện:

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

Lệnh sort thực hiện tốt bây giờ, nhưng tôi không thể tìm ra lý do tại sao.

[Cập nhật] Sau khi đọc câu trả lời @ tchrist, tôi đọc IO::Select, và sau khi một số googling hơn, đã đưa ra phiên bản này của 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); 
} 

này đang làm việc tốt, và một vài điều giờ đã trở nên rõ ràng hơn. Nhưng bức tranh tổng thể vẫn còn hơi mờ.

Vì vậy, câu hỏi của tôi là:

  1. Có gì sai với hung_execute?
  2. Tôi đoán good_execute hoạt động vì số >& trong cuộc gọi open3. Nhưng tại sao và như thế nào?
  3. Ngoài ra, good_execute không hoạt động khi tôi đã sử dụng các biến từ vựng (my $out thay vì OUT) cho tệp thủ công. Lỗi này đã xảy ra: open3: open(GLOB(0x610920), >&main::OUT) failed: Invalid argument. Tại sao như vậy?
  4. Dường như chỉ một trong các tay cầm tập tin có thể viết tại một thời điểm nhất định và nếu tôi loại bỏ tay cầm đang nắm giữ tài nguyên, tay kia sẽ tiếp tục chờ. Tôi từng nghĩ rằng STDERR và STDOUT là các luồng độc lập và không chia sẻ bất kỳ tài nguyên nào. Tôi đoán sự hiểu biết của tôi là một chút sai lầm ở đây. Vui lòng cho tôi một số gợi ý về điều này.

Trả lời

13

Bạn đã gặp phải rất nhiều vấn đề tôi đã viết trong tài liệu và sau đó là một số vấn đề. Bạn bế tắc vì bạn đang đợi đứa trẻ thoát ra trước khi bạn đọc nó. Nếu nó có nhiều hơn một bộ đệm ống của đầu ra, nó sẽ chặn và thoát tiếp theo. Ngoài ra, bạn chưa đóng đầu các tay cầm của mình.

Bạn cũng gặp các lỗi khác. Bạn không thể kiểm tra đầu ra trên một tay cầm theo cách đó, bởi vì bạn vừa thực hiện một đường dây chặn và loại bỏ các kết quả của nó. Hơn nữa, nếu bạn cố gắng đọc tất cả các stderr trước stdout, và nếu có nhiều hơn một bộ đệm ống đầu ra trên stdout, sau đó con bạn sẽ chặn viết để stdout trong khi bạn chặn đọc từ stderr của mình.

Bạn thực sự phải sử dụng select hoặc IO::Select, để thực hiện việc này một cách chính xác.Bạn chỉ được đọc từ một tay cầm khi có đầu ra có sẵn trên tay cầm đó, và bạn cũng không được trộn các cuộc gọi đệm với select, trừ khi bạn rất may mắn.

+0

Tôi đọc trên mô-đun 'IO :: Chọn' và đã cập nhật câu hỏi của tôi ... – Unos

+0

@Unos Bạn có rất nhiều câu hỏi. Bạn có nghĩa vụ phải hỏi chỉ một câu hỏi duy nhất. Tôi đã trả lời những cái ban đầu rồi, nhưng bạn lại hỏi những thứ tương tự như thể bạn không chú ý. Tôi đoán rằng việc trả lời tất cả các câu hỏi mới của bạn yêu cầu một đoạn hoặc ba cho gần mỗi dòng mã của bạn trong mọi chương trình. Đó là một công việc tuyệt vời để yêu cầu một người nào đó, chắc chắn hơn một giờ và hầu hết có thể xảy ra ba giờ làm việc miễn phí. Tôi không có thời gian đó hôm nay. Hãy nghiên cứu những gì tôi đã nói, bởi vì tôi không thấy nó chìm vào. – tchrist

+0

hi @tchrist, tôi hầu như không muốn làm bạn khó chịu. Tôi đã không loại bỏ các câu hỏi trước đó của tôi sau khi cập nhật như tôi nghĩ rằng các câu trả lời có thể mất bối cảnh nếu tôi đã làm. Tôi chắc chắn sẽ nghiên cứu chi tiết hơn. – Unos

7

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. 

Deadlock!


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 

Các ống có thể nhận được đầy đủ, ngăn chặn trẻ em; nhưng cha mẹ sẽ đi xung quanh để làm trống nó sớm đủ, bỏ chặn đứa trẻ. Không bế tắc.


">&OUT" đánh giá là >&OUT. (Không có biến nào để nội suy)

">&$OUT" đánh giá là >&GLOB(0x########). (Bạn đã nội suy $OUT.)

Có cách xử lý các tập tin từ vựng (hoặc đúng hơn là bộ mô tả), nhưng có lỗi liên quan đến chúng, vì vậy tôi luôn sử dụng các biến gói với open3.


stdout và stderr là độc lập (trừ khi bạn làm điều gì đó như 2>&1, và thậm chí sau đó, họ sẽ có lá cờ riêng biệt và bộ đệm). Bạn đã đi đến kết luận sai nếu bạn phát hiện ra rằng họ không.

+0

Cảm ơn bạn đã chụp ảnh, @ikegami. Tôi đã suy nghĩ theo một hướng hoàn toàn khác. Nó có ý nghĩa bây giờ mặc dù. – Unos

Các vấn đề liên quan