2009-10-18 2 views
3

У меня проблема с использованием монитора io_add_watch в python (через gobject). Я хочу сделать неблокирующее чтение всего буфера после каждого уведомления. Вот код (сокращенный немного):gobject io monitoring + nonblocking reads

class SomeApp(object): 

    def __init__(self): 
     # some other init that does a lot of stderr debug writes 
     fl = fcntl.fcntl(0, fcntl.F_GETFL, 0) 
     fcntl.fcntl(0, fcntl.F_SETFL, fl | os.O_NONBLOCK) 
     print "hooked", gobject.io_add_watch(0, gobject.IO_IN | gobject.IO_PRI, self.got_message, [""]) 
     self.app = gobject.MainLoop() 

    def run(self): 
     print "ready" 
     self.app.run() 

    def got_message(self, fd, condition, data): 
     print "reading now" 
     data[0] += os.read(0, 1024) 
     print "got something", fd, condition, data 
     return True 

gobject.threads_init() 
SomeApp().run() 

Вот трюк - когда я запустить программу без отладочного активированного, я не получаю got_message звонков. Когда я пишу много материала для stderr, проблема исчезает. Если я не пишу ничего кроме отпечатков, видимых в этом коде, я не получаю сигналы stdin messsage. Еще одна интересная вещь заключается в том, что когда я пытаюсь запустить одно и то же приложение с отладкой stderr, но через strace (чтобы проверить, есть ли какие-либо вызовы fcntl/ioctl, которые я пропустил), проблема появляется снова.

Итак, вкратце: если я пишу много для stderr первым без strace, io_watch работ. Если я пишу много с strace, или вообще не пишу, io_watch не работает.

Часть «некоторые другие функции» занимает некоторое время, поэтому, если я наберу текст до того, как увижу вывод «подключен 2», а затем нажмите «ctrl + c» после «ready», вызывается обратный вызов get_message, но read call выбрасывает EAGAIN, поэтому буфер кажется пустым.

журнал Strace связан с стандартного ввода:

ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 
fcntl(0, F_GETFL)      = 0xa002 (flags O_RDWR|O_ASYNC|O_LARGEFILE) 
fcntl(0, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC|O_LARGEFILE) = 0 
fcntl(0, F_GETFL)      = 0xa802 (flags O_RDWR|O_NONBLOCK|O_ASYNC|O_LARGEFILE) 

Кто-нибудь есть какие-то идеи о том, что происходит здесь?


EDIT: Еще одна подсказка. Я попытался реорганизовать приложение, чтобы сделать чтение в другом потоке и передать его обратно через канал. Это «вид» работает:

... 
     rpipe, wpipe = os.pipe() 
     stopped = threading.Event() 
     self.stdreader = threading.Thread(name = "reader", target = self.std_read_loop, args = (wpipe, stopped)) 
     self.stdreader.start() 
     new_data = "" 
     print "hooked", gobject.io_add_watch(rpipe, gobject.IO_IN | gobject.IO_PRI, self.got_message, [new_data]) 

    def std_read_loop(self, wpipe, stop_event): 
     while True: 
     try: 
      new_data = os.read(0, 1024) 
      while len(new_data) > 0: 
       l = os.write(wpipe, new_data) 
       new_data = new_data[l:] 
     except OSError, e: 
      if stop_event.isSet(): 
       break 
      time.sleep(0.1) 
... 

Удивительно, что, если я просто поставить один и тот же текст в новой трубе, все начинает работать. Проблема заключается в том, что:

  • первая строка не «заметил» на всех - я получаю только второй и последующие строки
  • это Fugly

Может быть, даст кому-то еще ключ на почему это происходит?

ответ

0

Сообщается, что вы должны вернуть TRUE из обратного вызова или он будет удален из списка источников событий.

+0

Он возвращает True в реальном коде. Неважно, если это не называется, это вообще не называется - даже один раз. – viraptor

2

Это звучит как условие гонки, в котором есть некоторая задержка для установки вашего обратного вызова, или же есть изменение в среде, которая влияет на то, можете ли вы установить обратный вызов.

Я бы внимательно посмотрел, что произойдет до того, как вы позвоните по телефону io_add_watch(). Например в FCNTL документы Python сказать:

Все функции в этом модуле взять дескриптор файла FD в качестве первого аргумента . Это может быть целочисленный файл дескриптор, например, возвращенное sys.stdin.fileno(), или объект файла, такие как sys.stdin сам по себе, который обеспечивает fileno(), который возвращает подлинный дескриптор файла.

Очевидно, что это не то, что вы делаете, если предположите, что STDIN будет иметь FD == 0. Я бы изменил это первым и повторил попытку.

Другое дело, что если FD уже заблокирован, тогда ваш процесс может ждать, пока выполняются другие незаблокированные процессы, поэтому существует разница во времени в зависимости от того, что вы делаете в первую очередь. Что произойдет, если вы реорганизуете материал fcntl так, чтобы это было сделано вскоре после запуска программы, даже до импорта модулей GTK?

Я не уверен, что я понимаю, почему программа, использующая GTK GUI, захочет читать со стандартного ввода в первую очередь. Если вы на самом деле пытаетесь захватить выход другого процесса, вы должны использовать модуль подпроцесса для установки трубы, затем io_add_watch() на трубе так:

proc = subprocess.Popen(command, stdout = subprocess.PIPE) 
gobject.io_add_watch(proc.stdout, glib.IO_IN, self.write_to_buffer) 

Опять же, в этом примере мы убеждаемся, что мы иметь действительный открытый FD до вызова io_add_watch().

Обычно, когда используется gobject.io_add_watch(), он называется как раз перед gobject.MainLoop(). Например, вот некоторый рабочий код, используя io_add_watch, чтобы поймать IO_IN.

+0

В таком случае дескриптор, в котором я нуждаюсь, должен быть 0. Мне нужно обработать его как дескриптор/это не подпроцесс. Перемещение fnctl не работало, к сожалению. Я не использую gtk, просто интеграцию gobject/gst. Но хорошие предложения в любом случае. К сожалению, мой код все еще не работает. – viraptor

+0

Я не уверен, почему вы бросаете потоки в микс, потому что это просто увеличивает сложность и создает больше возможностей для условий гонки. Взгляните на фрагменты здесь, особенно тот, который реализует игрока с помощью GStreamer http://pyneo.org/documentation/snippets/ –

+0

Вот еще один пример игрока GStreamer http://de.pastebin.ca/raw/ 933910 Я действительно думаю, что вам нужно упростить это для чего-то, что работает, и строить оттуда, а не наоборот. –

0

Что произойдет, если вы сначала подключите обратный вызов, до любого выхода stderr? Он по-прежнему вызывается при включении отладочного вывода?

Кроме того, предположим, что вы, вероятно, должны многократно называть os.read() в вашем обработчике, пока он не даст никаких данных, если> 1024 байта станут готовыми между вызовами.

Вы пробовали использовать модуль select в фоновом потоке для эмуляции функции gio? Это работает? На какой платформе это и с каким FD вы имеете дело? (file? socket? pipe?)

+0

Изменение порядка инициализации не помогло. Я попытался сначала промыть дескрипторы (с флешем и чтением), и это не помогло. Я запускаю это на linux, а fd - это канал, открытый программой, которая запускает мое приложение. Он работает с потоками + блокировки чтения в фоновом режиме, но я уже использую glib io для других fds в одной программе, поэтому я не хочу использовать другой метод для каждого ввода. – viraptor