gobject io监控+非阻塞读取

时间:2009-10-18 23:38:03

标签: python input glib gobject

我在python中使用io_add_watch监视器时遇到了问题(通过gobject)。我想在每次通知后对整个缓冲区进行非阻塞读取。这是代码(缩短了一点):

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消息信号。另一个有趣的事情是,当我尝试运行相同的应用程序并启用stderr debug但通过strace(检查是否有我错过的任何fcntl / ioctl调用)时,问题再次出现。

简而言之:如果我首先在没有strace的情况下向stderr写了很多东西,io_watch就可以了。如果我用strace写了很多东西,或者根本不写,io_watch不起作用。

“其他一些init”部分需要一些时间,所以如果我在看到“hooked 2”输出之前键入一些文本然后在“ready”之后按“ctrl + c”,则会调用get_message回调,但是read调用抛出了EAGAIN,所以缓冲区似乎是空的。

与stdin相关的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)

有没有人对这里发生的事情有所了解?


编辑:另一条线索。我尝试重构应用程序以在另一个线程中进行读取并通过管道将其传回。它“有点”有效:

...
      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)
...

令人惊讶的是,如果我将相同的文本放入新管道中,一切都会开始起作用。问题是:

  • 第一行根本没有“注意到” - 我只获得第二行及以下行
  • 很难看

也许这会让别人知道为什么会发生这种情况?

3 个答案:

答案 0 :(得分:2)

这听起来像竞争条件,其中设置回调有一些延迟,或者环境发生变化会影响您是否可以设置回调。

在你致电io_add_watch()之前,我会仔细研究一下会发生什么。例如,Python fcntl文档说:

  

本模块中的所有功能都需要   文件描述符fd作为他们的第一个   论点。这可以是整数文件   描述符,如返回的   sys.stdin.fileno()或文件对象,   比如sys.stdin本身,哪个   提供一个返回a的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 )

同样,在此示例中,我们确保在调用io_add_watch(之前我们有一个有效的已打开的FD。)

通常情况下,使用gobject.io_add_watch()时,会在gobject.MainLoop()之前调用它。例如,以下是一些使用io_add_watch来捕获IO_IN的工作代码。

答案 1 :(得分:0)

documentation表示您应该从回调中返回TRUE,否则它将从事件源列表中删除。

答案 2 :(得分:0)

如果在任何stderr输出之前首先挂钩回调会发生什么?启用调试输出后是否仍会调用它?

另外,我想你应该在你的处理程序中重复调用os.read(),直到它没有提供任何数据,以防万一> 1024字节在调用之间就绪。

您是否尝试在后台线程中使用select模块来模拟gio功能?那样有用吗?这是什么平台,你在处理什么样的FD? (文件?socket?pipe?)