Как мне заблокировать чтение именованного канала в Ruby? - PullRequest
5 голосов
/ 21 марта 2012

Я пытаюсь настроить скрипт Ruby, который читает из именованного канала в цикле, блокируя до тех пор, пока входные данные не будут доступны в канале.

У меня есть процесс, который периодически помещает события отладки в именованный канал:

# Open the logging pipe
log = File.open("log_pipe", "w+") #'log_pipe' created in shell using mkfifo
...
# An interesting event happens
log.puts "Interesting event #4291 occurred"
log.flush
...

Затем я хочу отдельный процесс, который будет читать из этого канала и выводить события на консоль по мере их возникновения. Я пытался использовать код, подобный этому:

input = File.open("log_pipe", "r+") 
while true
  puts input.gets  #I expect this to block and wait for input
end
# Kill loop with ctrl+c when done

Я хочу, чтобы input.gets блокировался, терпеливо ожидая, пока новый вход не поступит в fifo; но вместо этого он сразу читает nil и снова зацикливается, прокручивая верхнюю часть окна консоли.

Две вещи, которые я пробовал:

  1. Я открыл ввод fifo и с «r», и с «r +» - у меня та же проблема в любом случае;

  2. Я пытался определить, отправляет ли мой процесс записи EOF (что, как я слышал, приведет к закрытию чтения fifo) - AFAIK это не так.

НЕКОТОРЫЙ КОНТЕКСТ:

Если это поможет, вот «общее представление» о том, что я пытаюсь сделать:

Я работаю над игрой, которая работает в RGSS, игровом движке на основе Ruby. Поскольку он не имеет хорошей интегрированной отладки, я хочу настроить журнал в реальном времени по ходу игры - когда события происходят в игре, я хочу, чтобы сообщения отображались в окне консоли на стороне. Я могу отправлять события в игровом коде Ruby в именованный канал, используя код, похожий на код писателя выше; Сейчас я пытаюсь настроить отдельный процесс, который будет ожидать появления событий в конвейере и показывать их на консоли по мере их поступления. Я даже не уверен, что мне нужен Ruby, но это было первое решение, о котором я мог подумать.

Обратите внимание, что я использую mkfifo от Cygwin, который я случайно установил; Интересно, может ли это быть источником моей проблемы?

Если это кому-нибудь поможет, вот что я вижу в irb с моим процессом 'reader':

irb(main):001:0> input = File.open("mypipe", "r")
=> #<File:mypipe>
irb(main):002:0> x = input.gets
=> nil
irb(main):003:0> x = input.gets
=> nil

Я не ожидаю, что input.gets в 002 и 003 немедленно вернутся - я ожидаю, что они заблокируют.

Ответы [ 2 ]

3 голосов
/ 11 апреля 2012

Я нашел решение, которое полностью исключает ненадежную реализацию именованных каналов Cygwin. У Windows есть собственное средство именованных каналов, и есть даже Ruby Gem под названием win32-pipe , который использует его.

К сожалению, похоже, нет способа использовать Ruby Gems в сценарии RGSS; но, разобрав гем win32-pipe, я смог внедрить ту же идею в игру RGSS. Этот код - необходимый минимум для записи игровых событий в реальном времени на обратный канал, но он может быть очень полезен для глубокой отладки.

Я добавил новую страницу сценария прямо перед «Основным» и добавил:

module PipeLogger
  # -- Change THIS to change the name of the pipe!
  PIPE_NAME = "RGSSPipe"

  # Constant Defines
  PIPE_DEFAULT_MODE        = 0            # Pipe operation mode
  PIPE_ACCESS_DUPLEX       = 0x00000003   # Pipe open mode
  PIPE_UNLIMITED_INSTANCES = 255          # Number of concurrent instances
  PIPE_BUFFER_SIZE         = 1024         # Size of I/O buffer (1K)
  PIPE_TIMEOUT             = 5000         # Wait time for buffer (5 secs)
  INVALID_HANDLE_VALUE     = 0xFFFFFFFF   # Retval for bad pipe handle

  #-----------------------------------------------------------------------
  # make_APIs
  #-----------------------------------------------------------------------
  def self.make_APIs
    $CreateNamedPipe     = Win32API.new('kernel32', 'CreateNamedPipe', 'PLLLLLLL', 'L')
    $FlushFileBuffers    = Win32API.new('kernel32', 'FlushFileBuffers', 'L', 'B')
    $DisconnectNamedPipe = Win32API.new('kernel32', 'DisconnectNamedPipe', 'L', 'B')
    $WriteFile           = Win32API.new('kernel32', 'WriteFile', 'LPLPP', 'B')
    $CloseHandle         = Win32API.new('kernel32', 'CloseHandle', 'L', 'B')
  end

  #-----------------------------------------------------------------------
  # setup_pipe
  #-----------------------------------------------------------------------
  def self.setup_pipe
    make_APIs
    @@name = "\\\\.\\pipe\\" + PIPE_NAME

    @@pipe_mode = PIPE_DEFAULT_MODE
    @@open_mode = PIPE_ACCESS_DUPLEX
    @@pipe         = nil
    @@buffer       = 0.chr * PIPE_BUFFER_SIZE
    @@size         = 0
    @@bytes        = [0].pack('L')

    @@pipe = $CreateNamedPipe.call(
      @@name,
      @@open_mode,
      @@pipe_mode,
      PIPE_UNLIMITED_INSTANCES,
      PIPE_BUFFER_SIZE,
      PIPE_BUFFER_SIZE,
      PIPE_TIMEOUT,
      0
    )

    if @@pipe == INVALID_HANDLE_VALUE
      # If we could not open the pipe, notify the user
      # and proceed quietly
      print "WARNING -- Unable to create named pipe: " + PIPE_NAME
      @@pipe = nil
    else
      # Prompt the user to open the pipe
      print "Please launch the RGSSMonitor.rb script"
    end
  end

  #-----------------------------------------------------------------------
  # write_to_pipe ('msg' must be a string)
  #-----------------------------------------------------------------------
  def self.write_to_pipe(msg)
    if @@pipe
      # Format data
      @@buffer = msg
      @@size   = msg.size

      $WriteFile.call(@@pipe, @@buffer, @@buffer.size, @@bytes, 0)
    end
  end

  #------------------------------------------------------------------------
  # close_pipe
  #------------------------------------------------------------------------
  def self.close_pipe
    if @@pipe
      # Send kill message to RGSSMonitor
      @@buffer = "!!GAMEOVER!!"
      @@size   = @@buffer.size
      $WriteFile.call(@@pipe, @@buffer, @@buffer.size, @@bytes, 0)

      # Close down the pipe
      $FlushFileBuffers.call(@@pipe)
      $DisconnectNamedPipe.call(@@pipe)
      $CloseHandle.call(@@pipe)
      @@pipe = nil
    end
  end
end

Чтобы использовать это, вам нужно только позвонить PipeLogger::setup_pipe перед тем, как написать событие; и позвоните PipeLogger::close_pipe перед выходом из игры. (Я помещаю вызов установки в начало «Main» и добавляю предложение ensure для вызова close_pipe.) После этого вы можете добавить вызов к PipeLogger::write_to_pipe("msg") в любой точке любого сценария с любой строкой для msg и напиши в трубу.

Я проверил этот код с помощью RPG Maker XP; он также должен работать с RPG Maker VX и более поздними версиями.

Вам также понадобится что-то, чтобы прочитать ОТ канала. Есть несколько способов сделать это, но самый простой - это использовать стандартную установку Ruby, Ruby Gem из win32-pipe и этот скрипт:

require 'rubygems'
require 'win32/pipe'
include Win32

# -- Change THIS to change the name of the pipe!
PIPE_NAME = "RGSSPipe"

Thread.new { loop { sleep 0.01 } } # Allow Ctrl+C

pipe = Pipe::Client.new(PIPE_NAME)
continue = true

while continue
  msg = pipe.read.to_s
  puts msg

  continue = false if msg.chomp == "!!GAMEOVER!!"
end

Я использую Ruby 1.8.7 для Windows и win32-pipe gem , упомянутые выше (см. здесь для хорошей ссылки по установке гемов). Сохраните вышеупомянутое как "RGSSMonitor.rb" и вызовите его из командной строки как ruby RGSSMonitor.rb.

ПРЕДОСТЕРЕЖЕНИЯ:

  1. Указанный выше код RGSS хрупок; в частности, он не обрабатывает сбой при открытии именованного канала. Обычно это не проблема на вашей собственной машине для разработки, но я бы не рекомендовал отправлять этот код.
  2. Я не проверял это, но я подозреваю, что у вас будут проблемы, если вы запишете много вещей в журнал без запуска процесса чтения канала (например, RGSSMonitor.rb). Именованный канал Windows имеет фиксированный размер (я установил здесь 1 КБ), и по умолчанию записи будут блокироваться после заполнения канала (поскольку ни один процесс не «снимает давление» путем чтения из него). К сожалению, движок RPGXP уничтожит скрипт Ruby, который остановился на 10 секунд. (Мне сказали, что RPGVX исключил эту сторожевую функцию - в этом случае игра будет зависать, а не прерываться.)
1 голос
/ 21 марта 2012

Вероятно, происходит то, что процесс записи завершается, и, поскольку других процессов записи нет, EOF отправляется в канал, что приводит к тому, что gets возвращает nil, и поэтому ваш код постоянно зацикливается.

Чтобы обойти это, вы можете просто открыть канал чтения-записи в конце считывателя.Это работает для меня (на Mac), но не работает для вас (вы пробовали "r" и "r+").Я предполагаю, что это связано с Cygwin ( POSIX говорит, что открытие FIFO для чтения-записи не определено ).

Альтернативой является открытие канала дважды ,только для чтения и только для записи.Вы не используете IO только для записи, просто для того, чтобы к каналу всегда был подключен активный модуль записи, чтобы он не закрывался.

input = File.open("log_pipe", "r")      # note 'r', not 'r+'
keep_open = File.open("log_pipe", "w")  # ensure there's always a writer
while true
  puts input.gets
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...