Ruby на Linux PTY уходит без EOF, поднимает Errno:: EIO
Я пишу код, который берет файл, передает этот файл в один из нескольких исполняемых файлов для обработки и отслеживает процесс преобразования для ошибок. Я написал и протестировал следующую процедуру на OSX, но linux не удалось по причинам, о которых я не знаю.
#run the command, capture the output so it doesn't display
PTY.spawn(command) {|r,w,pid|
until r.eof? do
##mark
puts r.readline
end
}
Выполняемая команда сильно варьируется, и код в значке ## был упрощен в локальное эхо в попытке отладить проблему. Выполняется команда, а script печатает ожидаемый вывод в терминале и затем выдает исключение.
Ошибка, которую он производит в системах Debian, составляет: Errno::EIO (Input/output error - /dev/pts/0):
Во всех командных строках я могу придумать эту ошибку, и когда я запускаю код без локального блока эха, он работает просто отлично:
PTY.spawn(command) {|r,w,pid|}
В любом случае сама команда выполняется отлично, но похоже, что debian linux не отправляет eof pty. Страницы документа для PTY и IO на ruby-doc, похоже, здесь не помогают.
Любые предложения? Спасибо.
-vox -
Ответы
Ответ 1
Таким образом, мне нужно было дойти до чтения источника C для библиотеки PTY, чтобы получить удовлетворение от того, что происходит здесь.
Документ Ruby PTY на самом деле не говорит, что говорят в исходном коде.
Мое решение состояло в том, чтобы собрать метод оболочки и вызвать его из моего script там, где это необходимо. Я также включил метод ожидания, ожидающий завершения процесса, и доступ к статусу выхода из $?
:
# file: lib/safe_pty.rb
require 'pty'
module SafePty
def self.spawn command, &block
PTY.spawn(command) do |r,w,p|
begin
yield r,w,p
rescue Errno::EIO
ensure
Process.wait p
end
end
$?.exitstatus
end
end
Это используется в основном так же, как PTY.spawn:
require 'safe_pty'
exit_status = SafePty.spawn(command) do |r,w,pid|
until r.eof? do
logger.debug r.readline
end
end
#test exit_status for zeroness
Я был немного расстроен, чтобы узнать, что это действительный ответ, поскольку он был полностью недокументирован в ruby-doc.
Ответ 2
Кажется, что для Errno:: EIO здесь может быть поднят (это просто означает, что дочерний процесс завершил и закрыл поток), поэтому вы должны ожидать этого и поймать его.
Например, см. выбранный ответ в Непрерывно читайте STDOUT внешнего процесса в Ruby и http://www.shanison.com/2010/09/11/ptychildexited-exception-and-ptys-exit-status/
Кстати, я провел некоторое тестирование. На Ruby 1.8.7 на Ubuntu 10.04 я не получаю сообщение об ошибке. С Ruby 1.9.3, я делаю. С JRuby 1.6.4 на Ubuntu в режимах 1.8 и 1.9 я не получаю сообщение об ошибке. В OS X, с 1.8.7, 1.9.2 и 1.9.3, я не получаю сообщение об ошибке. Поведение, очевидно, зависит от вашей версии Ruby и платформы.
Ответ 3
ruby-doc.org говорит это начиная с ruby 1.9:
# The result of read operation when pty slave is closed is platform
# dependent.
ret = begin
m.gets # FreeBSD returns nil.
rescue Errno::EIO # GNU/Linux raises EIO.
nil
end
Итак, теперь я понимаю, что это поведение "нормально" в Linux, но это означает, что немного сложно получить вывод PTY. Если вы делаете m.read
он читает все, а затем выбрасывает и вызывает Errno :: EIO. Вам действительно нужно читать фрагмент содержимого по фрагменту с помощью m.readline
. И даже в этом случае вы рискуете потерять последнюю строку, если по какой-либо причине она не заканчивается на \n. Чтобы быть в большей безопасности, вам нужно прочитать содержимое побайтно с помощью m.read(1)
Дополнительное примечание о влиянии tty и pty на буферизацию: это не то же самое, что STDOUT.sync = true
(небуферизованный вывод) в дочернем процессе, а скорее запускает буферизацию строки, где вывод сбрасывается на "\n"