Как исправить висячий popen3 в Ruby?

Я получаю неожиданное поведение, используя 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"

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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

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

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

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

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

Conlusion

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

Используйте Open3.capture3.