Как исправить висячий 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
.