我在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)
...
令人惊讶的是,如果我将相同的文本放入新管道中,一切都会开始起作用。问题是:
也许这会让别人知道为什么会发生这种情况?
答案 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?)