Как исправить зависание popen3 в Ruby? - PullRequest
14 голосов
/ 21 января 2012

Я получаю неожиданное поведение, используя popen3, который я хочу использовать для запуска такой команды, как инструмент ala cmd < file1 > file2Приведенный ниже пример зависает, так что stdout done никогда не достигается.Использование инструментов, отличных от cat, может привести к зависанию, поэтому stdin done никогда не будет достигнуто.Я подозреваю, что я страдаю от буферизации, но как мне это исправить?

#!/usr/bin/env ruby

require 'open3'

Open3.popen3("cat") do |stdin, stdout, stderr, wait_thr|
  stdin.puts "foobar"

  puts "stdin done"

  stdout.each_line { |line| puts line }

  puts "stdout done"

  puts wait_thr.value
end

puts "all done"

Ответы [ 3 ]

14 голосов
/ 21 января 2012

stdout.each_line ожидает дальнейшего вывода из cat, поскольку поток вывода cat все еще открыт.Он все еще открыт, потому что cat все еще ожидает ввода от пользователя, потому что его поток ввода еще не закрыт (вы заметите, что когда вы откроете cat в терминале и введете foobar, он все равно будетработать и ждать ввода, пока вы не нажмете ^d, чтобы закрыть поток).

Так что, чтобы это исправить, просто наберите stdin.close, прежде чем печатать вывод.

7 голосов
/ 15 мая 2013

Ваш код висит, потому что stdin все еще открыт!

Вам нужно закрыть его с помощью IO#close или IO#close_write, если вы используете popen3.

Если вы используете popen, вам нужно использовать IO#close_write, потому что он использует только один дескриптор файла.

 #!/usr/bin/env ruby
 require 'open3'

 Open3.popen3("cat") do |stdin, stdout, stderr, wait_thr|
   stdin.puts "foobar"

   stdin.close   # close stdin like this!  or with stdin.close_write

   stdout.each_line { |line| puts line }

   puts wait_thr.value
 end

Смотри также:

Ruby 1.8.7 IO # close_write

Ruby 1.9.2 IO # close_write

Ruby 2.3.1 IO # close_write

5 голосов
/ 02 ноября 2014

Ответы Тило и sepp2k верны: если вы закроете stdin, ваш простой тест закончится.Проблема решена.

Хотя в своем комментарии к ответу sepp2k вы указываете, что у вас все еще возникают зависания.Ну, есть некоторые ловушки, которые вы могли не заметить.

Застрял в полном буфере для stderr

Если вы вызываете программу, которая печатает в stderr больше, чем может вместить буфер анонимного канала (64 КБ для текущих Linux), программа приостанавливается.Приостановленная программа не выходит и не закрывает стандартный вывод.Следовательно, чтение из его стандартного вывода будет зависать.Поэтому, если вы хотите сделать это правильно, вы должны использовать потоки или IO.select, неблокирующее, небуферизованное чтение, чтобы читать как из stdout, так и из stderr параллельно или по очереди, не застревая.

Застрялна полном буфере для stdin

Если вы попытаетесь передать в вашу программу больше (намного больше), чем "foobar" (cat), буфер анонимного канала для stdout заполнится.ОС приостановит cat.Если вы напишете еще больше в stdin, буфер анонимного канала для stdin заполнится.Тогда ваш звонок на stdin.write застрянет.Это означает: вам нужно писать в stdin, читать из stdout и читать из stderr параллельно или по очереди.

Заключение

Прочитайте хорошую книгу (Ричардс Стивенс, «Сетевое программирование в UNIX: межпроцессное взаимодействие») и используйте хорошие библиотечные функции.IPC (межпроцессное взаимодействие) слишком сложен и подвержен неопределенному поведению во время выполнения.Слишком много хлопот, чтобы попытаться сделать это правильно методом проб и ошибок.

Использование Open3.capture3.

...