Как исправить зависание popen3 в Ruby?

Я получаю неожиданное поведение, используя popen3, который я хочу использовать для запуска команды, как инструмент ала 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 ответа

Решение

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

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

Ваш код висит, потому что 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

Ответы Тило и Сеппка верны: если вы закроете stdinтвой простой тест закончится. Задача решена.

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

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

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

Застрял на полный буфер для стандартного ввода

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

Conlusion

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

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

Другие вопросы по тегам