Обсуждение с подпроцессом, используя Ruby с IO и поточностью - PullRequest
2 голосов
/ 02 марта 2010

Я пытаюсь использовать IO.popen для того, чтобы помещать (с помощью метода .puts) и получать (с помощью метода .gets) сообщения из процесса в его подпроцесс.

Я не очень экспериментировал, и у меня есть вопрос. Имея следующий код, у меня ошибка, потому что невозможно писать в закрытом потоке.

class Interface
  def initialize(path)
    @sub_process = IO.popen(path, 'w+')
  end

  def start!
    if ok?
      @sub_process.puts 'Hello', 'my name is ...'
      # and more...
    end
  end

  protected

  def ok?
    is_ready?(@sub_process) && is_cool?(@sub_process)
  end

  def is_ready?(sub_process)
    reply = process_command(sub_process, 'are u ready?')
    reply.chomp.match(/yes_i_am_ready$/)
  end

  def is_cool?(sub_process)
    reply = process_command(sub_process, 'are u cool?')
    reply.chomp.match(/yes_i_am_cool$/)
  end

  def process_command(sub_process, command)
    rdr = Thread.new { sub_process.read } # alternative: io.readlines
    sub_process.puts "#{command}"
    sub_process.close_write
    rdr.value # joins and fetches the result
  end
end

a = Interface.new("./program")
a.start!

(...) в `write ': не открыт для записи (IOError)

Как мы видим, эта ошибка возникает во время is_cool? тест (как объяснено по адресу: http://ruby -doc.org / core / classes / IO.html # M002289 ).

Но если я попытаюсь прокомментировать в методе process_command строку:

# sub_process.close_write

сценарий, кажется, спит ... бесконечно: s

Я считаю, что невозможно снова открыть закрытый поток. И я не могу создать другой экземпляр IO.popen моей программы "./program", потому что его нужно инициализировать с помощью какой-то команды (например, "ты готов?" И "Ты классный?") В начале, до Я использую его (отправляя и получая сообщения, как простое обсуждение).

Как можно изменить текущий код для решения этой проблемы?

Редактировать : другими словами, я хотел бы установить такое сообщение (согласно заданному протоколу):

Parent message:                Child answer:
--------------                 ------------

'are u ready?'                 'yes_i_am_ready'
'are u cool?'                  'yes_i_am_cool'
'Hello'                        'foo'
'my name is ...'               'bar'

Большое спасибо за любую помощь.

Ответы [ 3 ]

2 голосов
/ 02 марта 2010

Возможно, это поможет иметь рабочий пример. Вот один, проверенный и известный для работы в MRI 1.8.7 на Linux.

bar.rb

#!/usr/bin/ruby1.8

begin
  loop do
    puts "You said: #{gets}"
    $stdout.flush
  end
rescue Errno::EPIPE
end

foo.rb

#!/usr/bin/ruby1.8

class Parent

  def initialize
    @pipe = IO.popen(CHILD_COMMAND, 'w+')
  end

  def talk(message)
    @pipe.puts(message)
    response = @pipe.gets
    if response.nil?
      $stderr.puts "Failed: #{CHILD_COMMAND}"
      exit(1)
    end
    response.chomp
  end

  private

  CHILD_COMMAND = './bar.rb'

end

parent = Parent.new
puts parent.talk('blah blah blah')
puts parent.talk('foo bar baz')

foo.rb output

You said: blah blah blah
You said: foo bar baz
0 голосов
/ 02 марта 2010

Помогает ли использовать эту форму Thread.new?

rdr = Thread.new(sub_process) {|x| x.readlines }
0 голосов
/ 02 марта 2010

Закрытый IO больше не может быть использован. Вам не следует закрывать IO, если вы все еще собираетесь его использовать.

Если вы удалите IO#close_write, проблема с вашим кодом по-прежнему остается в следующей строке.

rdr = Thread.new { sub_process.read }

IO#read читать до EOF. Поэтому, пока поток не закроется, он никогда не завершится. Вы упомянули IO#readline в своем коде, это будет лучшей альтернативой. При использовании IO#readline ваша программа зависнет, только если процесс popend никогда не отправит новую строку.

Другая проблема с popen заключается в следующем. IO#popen создать новый процесс. Процесс может быть убит вами, другими пользователями, нехваткой памяти,…. Не ожидайте, что ваш процесс всегда будет запущен. Если процесс завершится, IO#readline выдаст EOFError, IO#read вернет имидиатлей. Вы можете определить причину прекращения с помощью следующего кода.

Process::wait(io.pid)
status= $?
status.class # => Process::Status
status.signaled? # killed by signal?
status.stopsig # the signal which killed it
status.exited # terminated normal
status.exitstatus # the return value
status.ki
...