(original) (raw)

# Demonstrate bug in asyncore.file_dispatcher (up to and including # Python 2.4 I think). For posix only. # # The story is that asyncore.file_dispatcher closes the file # descriptor behind the file object, and not the file object # itself. When another file gets opened, it gets the next available # fd, which on posix, is the one just released by the close. # # dhoulder@users.sourceforge.net import asyncore import os class MyDispatcher(asyncore.file_dispatcher): def handle_read(self): data=self.recv(100) print " (Read", repr(data), "from pipe)" def handle_close(self): print " (pipe closing. fd==", self._fileno,")" self.close() # At this point the fd gets closed, not the pipe object self.secondPipe=os.popen(secondPipecmd, "r") # ... and now the fd of self.secondPipe is the same as that # of self def closeBigFds(): # Close files above stderr. try: maxfd = os.sysconf("SC_OPEN_MAX") except (AttributeError, ValueError): maxfd = 256 # default maximum for fd in range(3, maxfd): try: os.close(fd) except OSError: # ERROR (ignore) pass if __name__ == "__main__": # Make sure we only have fd 0, 1, 2 open to start with. closeBigFds firstPipecmd="echo I am the first pipe" secondPipecmd="echo I am the second pipe" print "case 1:" firstPipe=os.popen(firstPipecmd, "r") dispatcher=MyDispatcher(firstPipe.fileno()) asyncore.loop() # At this point firstPipe.fileno() and # dispatcher.secondPipe._fileno are the same print "firstPipe.read() says", repr(firstPipe.read()) #... so you get "firstPipe.read() says 'I am the second pipe\n'" # Not good! print "firstPipe.fileno()==", firstPipe.fileno() print "secondPipe.fileno()==", dispatcher.secondPipe.fileno() print del firstPipe del dispatcher closeBigFds # Just to be sure # Or worse... print "case 2:" firstPipe=os.popen(firstPipecmd, "r") dispatcher=MyDispatcher(firstPipe.fileno()) asyncore.loop() # At this point dispatcher._fileno and # dispatcher.secondPipe._fileno are the same firstPipe=None # At this point firstPipe may be garbage collected, thus closing # it's fd. That fd is currently used by dispatcher.secondPipe, so # what's actually happened is that we've just closed # dispatcher.secondPipe's fd, not firstPipe. print "secondPipe.fileno()==", dispatcher.secondPipe.fileno() print "dispatcher.secondPipe.read() says", repr(dispatcher.secondPipe.read()) # ... which fails with this: # dispatcher.secondPipe.read() says # Traceback (most recent call last): # File "file_dispatcher_bug.py", line 72, in ? # print "dispatcher.secondPipe.read() says", repr(dispatcher.secondPipe.read()) # IOError: [Errno 9] Bad file descriptor # Here's a better idea, based on file_dispatcher #!/usr/bin/python # Id:pipedispatcher.py,v1.12004/02/1202:33:42djh900ExpId: pipedispatcher.py,v 1.1 2004/02/12 02:33:42 djh900 Exp Id:pipedispatcher.py,v1.12004/02/1202:33:42djh900Exp # Base on asyncore.file_wrapper and asyncore. file_dispatcher. They're # buggy, mainly because the close() method closes the fd behind the # pipe, not the pipe itself. We also keep a reference to the pipe # object itself so that it can't get garbage collected and leave a # dangling fd in the class instance. dhoulder@users.sourceforge.net import asyncore import os import fcntl class pipe_wrapper: # here we override just enough to make a pipe # look like a socket for the purposes of asyncore. def __init__(self, pipe): self.pipe=pipe self.fd = pipe.fileno() def recv(self, *args): return apply(os.read,(self.fd,)+args) def send(self, *args): return apply(os.write,(self.fd,)+args) read = recv write = send def close(self): self.fd=None return self.pipe.close() def fileno(self): return self.fd class PipeDispatcher(asyncore.dispatcher): def __init__(self, pipe): asyncore.dispatcher.__init__(self) self.connected = 1 fd=pipe.fileno() # set it to non-blocking mode flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0) flags = flags | os.O_NONBLOCK fcntl.fcntl(fd, fcntl.F_SETFL, flags) self.set_file(pipe) def set_file(self, pipe): self._fileno = pipe.fileno() self.socket = pipe_wrapper(pipe) self.add_channel() #end