class SslHandshakingTransport(BaseSocketTransport): __slots__ = ["connected_dfr"] def __init__(self, reactor, sslsock): BaseSocketTransport.__init__(self, reactor, sslsock) self.connected_dfr = ReactorDeferred(self.reactor) def handshake(self): if not self.connected_dfr.is_set(): self._handshake() return self.connected_dfr def _handshake(self): try: self.sock.do_handshake() except ssl.SSLError as ex: if ex.errno == ssl.SSL_ERROR_WANT_READ: self.reactor.register_read(self) elif ex.errno == ssl.SSL_ERROR_WANT_WRITE: self.reactor.register_write(self) else: self.connected_dfr.throw(ex) else: sock = self.sock self.detach() trns = SslStreamTransport(self.reactor, sock) self.connected_dfr.set(trns) def on_read(self): self.reactor.unregister_read(self) self._handshake() def on_write(self): self.reactor.unregister_write(self) self._handshake()
class Process(object): def __init__(self, reactor, proc, cmdline): self.reactor = reactor self.cmdline = cmdline self._proc = proc self.stdin = BufferedTransport(self.reactor.io._wrap_pipe(proc.stdin, "w")) self.stdout = BufferedTransport(self.reactor.io._wrap_pipe(proc.stdout, "r")) self.stderr = BufferedTransport(self.reactor.io._wrap_pipe(proc.stderr, "r")) self.pid = proc.pid self.wait_dfr = ReactorDeferred(self.reactor) def __repr__(self): return "<Process %r: %r (%s)>" % (self.pid, self.cmdline, "alive" if self.is_alive() else "dead") def on_termination(self): self.wait_dfr.set(self.returncode) @property def returncode(self): return self._proc.returncode def is_alive(self): return self._proc.poll() is None def signal(self, sig): self._proc.send_signal(sig) @reactive def terminate(self): self._proc.terminate() def wait(self): return self.wait_dfr
def read(self, count): if self._read_req: raise OverlappingRequestError("overlapping reads") dfr = ReactorDeferred(self.reactor) if self._eof: dfr.set(None) elif count <= 0: dfr.set("") else: self._read_req = (dfr, count) self.reactor.register_read(self) return dfr
class PipeTransport(StreamTransport): #__slots__ = ["mode", "name", "_flush_dfr", "auto_flush"] def __init__(self, reactor, fileobj, mode, auto_flush=True): if mode not in ("r", "w", "rw"): raise ValueError("invalid mode") StreamTransport.__init__(self, reactor, fileobj) self.name = getattr(self.fileobj, "name", None) self.mode = mode self._flush_dfr = None self.auto_flush = auto_flush if "r" not in mode: self.read = self._wrong_mode self.properties.pop("readable", None) if "w" not in mode: self.flush = self._wrong_mode self.write = self._wrong_mode self.properties.pop("writable", None) if fcntl: self._unblock(self.fileno()) @staticmethod def _wrong_mode(*args): raise IOError("wrong mode for operation") @staticmethod def _unblock(fd): flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0) fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) def isatty(self): return self.fileobj.isatty() def flush(self): if not self._flush_dfr: self._flush_dfr = ReactorDeferred(self.reactor) self.reactor.register_write(self) return self._flush_dfr def _flush(self): self.fileobj.flush() os.fsync(self.fileno()) if self._flush_dfr: self._flush_dfr.set() self._flush_dfr = None def on_write(self): StreamTransport.on_write(self) if self.auto_flush or self._flush_dfr: self._flush()
def read(self, count): # XXX: # The ReadFile function may fail with ERROR_INVALID_USER_BUFFER or # ERROR_NOT_ENOUGH_MEMORY whenever there are too many outstanding # asynchronous I/O requests. # # XXX: # http://support.microsoft.com/kb/156932 -- ReadFile may return # immediately, need to check that condition if self._ongoing_read: raise OverlappingRequestError("overlapping reads") self._ongoing_read = True def read_finished(size, exc): if size == 0: data = None # EOF else: data = bytes(buf[:size]) self._ongoing_read = False if exc: dfr.throw(exc) else: dfr.set(data) dfr = ReactorDeferred(self.reactor) count = min(count, self.MAX_READ_SIZE) if count <= 0: self._ongoing_read = False dfr.set("") return dfr overlapped = self._get_read_overlapped(read_finished) try: buf = win32file.AllocateReadBuffer(count) win32file.ReadFile(self.fileno(), buf, overlapped) except Exception as ex: self.reactor._discard_overlapped(overlapped) self._ongoing_read = False if isinstance(ex, pywintypes.error ) and ex.winerror in win32iocp.IGNORED_ERRORS: # why can't windows be just a little consistent?! # why can't a set of APIs share the same semantics for all kinds # of handles? grrrrr dfr.set(None) else: dfr.throw(ex) return dfr
class PipeTransport(StreamTransport): #__slots__ = ["mode", "name", "_flush_dfr", "auto_flush"] def __init__(self, reactor, fileobj, mode, auto_flush = True): if mode not in ("r", "w", "rw"): raise ValueError("invalid mode") StreamTransport.__init__(self, reactor, fileobj) self.name = getattr(self.fileobj, "name", None) self.mode = mode self._flush_dfr = None self.auto_flush = auto_flush if "r" not in mode: self.read = self._wrong_mode self.properties.pop("readable", None) if "w" not in mode: self.flush = self._wrong_mode self.write = self._wrong_mode self.properties.pop("writable", None) if fcntl: self._unblock(self.fileno()) @staticmethod def _wrong_mode(*args): raise IOError("wrong mode for operation") @staticmethod def _unblock(fd): flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0) fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) def isatty(self): return self.fileobj.isatty() def flush(self): if not self._flush_dfr: self._flush_dfr = ReactorDeferred(self.reactor) self.reactor.register_write(self) return self._flush_dfr def _flush(self): self.fileobj.flush() os.fsync(self.fileno()) if self._flush_dfr: self._flush_dfr.set() self._flush_dfr = None def on_write(self): StreamTransport.on_write(self) if self.auto_flush or self._flush_dfr: self._flush()
def read(self, count): # XXX: # The ReadFile function may fail with ERROR_INVALID_USER_BUFFER or # ERROR_NOT_ENOUGH_MEMORY whenever there are too many outstanding # asynchronous I/O requests. # # XXX: # http://support.microsoft.com/kb/156932 -- ReadFile may return # immediately, need to check that condition if self._ongoing_read: raise OverlappingRequestError("overlapping reads") self._ongoing_read = True def read_finished(size, exc): if size == 0: data = None # EOF else: data = bytes(buf[:size]) self._ongoing_read = False if exc: dfr.throw(exc) else: dfr.set(data) dfr = ReactorDeferred(self.reactor) count = min(count, self.MAX_READ_SIZE) if count <= 0: self._ongoing_read = False dfr.set("") return dfr overlapped = self._get_read_overlapped(read_finished) try: buf = win32file.AllocateReadBuffer(count) win32file.ReadFile(self.fileno(), buf, overlapped) except Exception as ex: self.reactor._discard_overlapped(overlapped) self._ongoing_read = False if isinstance(ex, pywintypes.error) and ex.winerror in win32iocp.IGNORED_ERRORS: # why can't windows be just a little consistent?! # why can't a set of APIs share the same semantics for all kinds # of handles? grrrrr dfr.set(None) else: dfr.throw(ex) return dfr
class ConnectingSocketTransport(BaseSocketTransport): __slots__ = ["addr", "connected_dfr", "_connecting"] def __init__(self, reactor, sock, addr): BaseSocketTransport.__init__(self, reactor, sock) self.addr = addr self.connected_dfr = ReactorDeferred(self.reactor) self._connecting = False def connect(self, timeout=None): if self._connecting: raise OverlappingRequestError("already connecting") self._connecting = True if timeout is not None: self.reactor.jobs.schedule(timeout, self._cancel) self.reactor.register_write(self) self._attempt_connect() return self.connected_dfr def on_write(self): self._attempt_connect() def _attempt_connect(self): if self.connected_dfr.is_set(): self.detach() return err = self.sock.connect_ex(self.addr) if err in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): return if err == errno.EINVAL and sys.platform == "win32": return sock = self.sock self.detach() if err in (0, errno.EISCONN): self.connected_dfr.set(SocketStreamTransport(self.reactor, sock)) else: self.connected_dfr.throw(socket.error(err, errno.errorcode[err])) def _cancel(self): if self.connected_dfr.is_set(): return self.close() self.connected_dfr.throw(socket.timeout("connection timed out"))
class ConnectingSocketTransport(BaseSocketTransport): __slots__ = ["addr", "connected_dfr", "_connecting"] def __init__(self, reactor, sock, addr): BaseSocketTransport.__init__(self, reactor, sock) self.addr = addr self.connected_dfr = ReactorDeferred(self.reactor) self._connecting = False def connect(self, timeout = None): if self._connecting: raise OverlappingRequestError("already connecting") self._connecting = True if timeout is not None: self.reactor.jobs.schedule(timeout, self._cancel) self.reactor.register_write(self) self._attempt_connect() return self.connected_dfr def on_write(self): self._attempt_connect() def _attempt_connect(self): if self.connected_dfr.is_set(): self.detach() return err = self.sock.connect_ex(self.addr) if err in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): return if err == errno.EINVAL and sys.platform == "win32": return sock = self.sock self.detach() if err in (0, errno.EISCONN): self.connected_dfr.set(SocketStreamTransport(self.reactor, sock)) else: self.connected_dfr.throw(socket.error(err, errno.errorcode[err])) def _cancel(self): if self.connected_dfr.is_set(): return self.close() self.connected_dfr.throw(socket.timeout("connection timed out"))
class Process(object): def __init__(self, reactor, proc, cmdline): self.reactor = reactor self.cmdline = cmdline self._proc = proc self.stdin = BufferedTransport( self.reactor.io._wrap_pipe(proc.stdin, "w")) self.stdout = BufferedTransport( self.reactor.io._wrap_pipe(proc.stdout, "r")) self.stderr = BufferedTransport( self.reactor.io._wrap_pipe(proc.stderr, "r")) self.pid = proc.pid self.wait_dfr = ReactorDeferred(self.reactor) def __repr__(self): return "<Process %r: %r (%s)>" % (self.pid, self.cmdline, "alive" if self.is_alive() else "dead") def on_termination(self): self.wait_dfr.set(self.returncode) @property def returncode(self): return self._proc.returncode def is_alive(self): return self._proc.poll() is None def signal(self, sig): self._proc.send_signal(sig) @reactive def terminate(self): self._proc.terminate() def wait(self): return self.wait_dfr
class IOSubsystem(Subsystem): NAME = "io" def _init(self): # self._console_thd = None # if winconsole.Console.is_attached(): # print "console attached" # self.console = winconsole.Console() # self._console_buffer = "" # self._console_input_dfr = None # self._console_thd = threading.Thread(target = self._console_input_thread) # self._console_thd.daemon = True # self._console_thd_started = False # else: # self.console = None # # check if stdin has FLAG_FILE_FLAG_OVERLAPPED by trying to register # # it with the IOCP # handle = msvcrt.get_osfhandle(sys.stdin.fileno()) # try: # self.reactor._port.register(handle) # except win32file.error: # print "no OVERLAPPED" # self._console_buffer = "" # self._console_input_dfr = None # self._console_thd = threading.Thread(target = self._console_input_thread) # self._console_thd.daemon = True # self._console_thd_started = False # else: # print "OVERLAPPED enabled" # # successfully registered with IOCP -- PipeTransport will work # # just fine # pass self._stdin = None self._stdout = None self._stderr = None #def _unload(self): # if self.console: # self.console.close() # self.console = None @property def stdin(self): if not self._stdin: #if getattr(self, "_console_thd", False): # self._stdin = ConsoleInputTransport(self.reactor) #else: self._stdin = PipeTransport(self.reactor, sys.stdin, "r") return self._stdin @property def stdout(self): if not self._stdout: #if getattr(self, "_console_thd", False): # self._stdout = BlockingStreamTransport(self, sys.stdout) #else: self._stdout = PipeTransport(self.reactor, sys.stdout, "w") return self._stdout @property def stderr(self): if not self._stderr: #if getattr(self, "_console_thd", False): # self._stderr = BlockingStreamTransport(self, sys.stdout) #else: self._stderr = PipeTransport(self.reactor, sys.stderr, "w") return self._stderr def _assure_started(self): if getattr(self, "_console_thd", False) and not self._console_thd_started: self._console_thd.start() self._console_thd_started = True def _request_console_read(self): self._assure_started() if not self._console_input_dfr or self._console_input_dfr.is_set(): self._console_input_dfr = ReactorDeferred(self.reactor) return self._console_input_dfr def _console_input_thread(self): while self.reactor._active: data = os.read(sys.stdin.fileno(), 1000) self._console_buffer += data if self._console_input_dfr and not self._console_input_dfr.is_set( ): self._console_input_dfr.set(self._console_buffer) self._console_buffer = "" self._console_input_dfr = None self.reactor._wakeup() def _wrap_pipe(self, fileobj, mode): return PipeTransport(self.reactor, fileobj, mode) @reactive def open(self, filename, mode): yield self.reactor.started fobj, access = win32iocp.WinFile.open(filename, mode) rreturn(FileTransport(self.reactor, fobj, access)) @reactive def pipe(self): yield self.reactor.started rh, wh = win32iocp.create_overlapped_pipe() rtrns = PipeTransport(self.reactor, win32iocp.WinFile(rh), "r") wtrns = PipeTransport(self.reactor, win32iocp.WinFile(wh), "w") rreturn((rtrns, wtrns))
class BaseReactor(object): if sys.platform == "win32": MAX_TIMEOUT = 0.2 # to process Ctrl+C else: MAX_TIMEOUT = 1 SUBSYSTEMS = GENERIC_SUBSYSTEMS def __init__(self): self._active = False self._jobs = MinHeap() self._callbacks = [] #self._rcallbacks = [] self._subsystems = [] self.started = ReactorDeferred(weakref.proxy(self)) @classmethod def supported(cls): return False #=========================================================================== # Core #=========================================================================== def install_subsystem(self, factory): subs = factory(weakref.proxy(self)) if hasattr(self, subs.NAME): raise ValueError("attribute %r already exists" % (subs.NAME,)) setattr(self, subs.NAME, subs) subs._init() self._subsystems.append(subs.NAME) def uninstall_subsystem(self, name): subs = getattr(self, name) subs._unload() #subs.reactor = None def _install_builtin_subsystems(self): for factory in self.SUBSYSTEMS: self.install_subsystem(factory) def start(self): if self._active: raise ReactorError("reactor already running") self._active = True self.started.set() try: while self._active: self._work() except KeyboardInterrupt: pass finally: self._active = False def stop(self): if not self._active: return self._active = False self.started.cancel() for name in self._subsystems: self.call(self.uninstall_subsystem, name) self._wakeup() def run(self, func): self.call(func, self) self.start() #=========================================================================== # Internal #=========================================================================== def _wakeup(self): raise NotImplementedError() def _work(self): now = time.time() timeout = self._process_jobs(now) if self._callbacks: timeout = 0 self._handle_transports(min(timeout, self.MAX_TIMEOUT)) self._process_callbacks() def _process_jobs(self, now): while self._jobs: ts, func, args, kwargs = self._jobs.peek() if now < ts: return ts - now self._jobs.pop() self._callbacks.append((func, args, kwargs)) return self.MAX_TIMEOUT def _process_callbacks(self): callbacks = self._callbacks self._callbacks = [] for cb, args, kwargs in callbacks: cb(*args, **kwargs) #=========================================================================== # Callbacks #=========================================================================== def call(self, func, *args, **kwargs): self._callbacks.append((func, args, kwargs)) def call_at(self, ts, func, *args, **kwargs): self._jobs.push((ts, func, args, kwargs))
class IOSubsystem(Subsystem): NAME = "io" def _init(self): # self._console_thd = None # if winconsole.Console.is_attached(): # print "console attached" # self.console = winconsole.Console() # self._console_buffer = "" # self._console_input_dfr = None # self._console_thd = threading.Thread(target = self._console_input_thread) # self._console_thd.daemon = True # self._console_thd_started = False # else: # self.console = None # # check if stdin has FLAG_FILE_FLAG_OVERLAPPED by trying to register # # it with the IOCP # handle = msvcrt.get_osfhandle(sys.stdin.fileno()) # try: # self.reactor._port.register(handle) # except win32file.error: # print "no OVERLAPPED" # self._console_buffer = "" # self._console_input_dfr = None # self._console_thd = threading.Thread(target = self._console_input_thread) # self._console_thd.daemon = True # self._console_thd_started = False # else: # print "OVERLAPPED enabled" # # successfully registered with IOCP -- PipeTransport will work # # just fine # pass self._stdin = None self._stdout = None self._stderr = None #def _unload(self): # if self.console: # self.console.close() # self.console = None @property def stdin(self): if not self._stdin: #if getattr(self, "_console_thd", False): # self._stdin = ConsoleInputTransport(self.reactor) #else: self._stdin = PipeTransport(self.reactor, sys.stdin, "r") return self._stdin @property def stdout(self): if not self._stdout: #if getattr(self, "_console_thd", False): # self._stdout = BlockingStreamTransport(self, sys.stdout) #else: self._stdout = PipeTransport(self.reactor, sys.stdout, "w") return self._stdout @property def stderr(self): if not self._stderr: #if getattr(self, "_console_thd", False): # self._stderr = BlockingStreamTransport(self, sys.stdout) #else: self._stderr = PipeTransport(self.reactor, sys.stderr, "w") return self._stderr def _assure_started(self): if getattr(self, "_console_thd", False) and not self._console_thd_started: self._console_thd.start() self._console_thd_started = True def _request_console_read(self): self._assure_started() if not self._console_input_dfr or self._console_input_dfr.is_set(): self._console_input_dfr = ReactorDeferred(self.reactor) return self._console_input_dfr def _console_input_thread(self): while self.reactor._active: data = os.read(sys.stdin.fileno(), 1000) self._console_buffer += data if self._console_input_dfr and not self._console_input_dfr.is_set(): self._console_input_dfr.set(self._console_buffer) self._console_buffer = "" self._console_input_dfr = None self.reactor._wakeup() def _wrap_pipe(self, fileobj, mode): return PipeTransport(self.reactor, fileobj, mode) @reactive def open(self, filename, mode): yield self.reactor.started fobj, access = win32iocp.WinFile.open(filename, mode) rreturn(FileTransport(self.reactor, fobj, access)) @reactive def pipe(self): yield self.reactor.started rh, wh = win32iocp.create_overlapped_pipe() rtrns = PipeTransport(self.reactor, win32iocp.WinFile(rh), "r") wtrns = PipeTransport(self.reactor, win32iocp.WinFile(wh), "w") rreturn((rtrns, wtrns))
class BaseReactor(object): if sys.platform == "win32": MAX_TIMEOUT = 0.2 # to process Ctrl+C else: MAX_TIMEOUT = 1 SUBSYSTEMS = GENERIC_SUBSYSTEMS def __init__(self): self._active = False self._jobs = MinHeap() self._callbacks = [] #self._rcallbacks = [] self._subsystems = [] self.started = ReactorDeferred(weakref.proxy(self)) @classmethod def supported(cls): return False #=========================================================================== # Core #=========================================================================== def install_subsystem(self, factory): subs = factory(weakref.proxy(self)) if hasattr(self, subs.NAME): raise ValueError("attribute %r already exists" % (subs.NAME, )) setattr(self, subs.NAME, subs) subs._init() self._subsystems.append(subs.NAME) def uninstall_subsystem(self, name): subs = getattr(self, name) subs._unload() #subs.reactor = None def _install_builtin_subsystems(self): for factory in self.SUBSYSTEMS: self.install_subsystem(factory) def start(self): if self._active: raise ReactorError("reactor already running") self._active = True self.started.set() try: while self._active: self._work() except KeyboardInterrupt: pass finally: self._active = False def stop(self): if not self._active: return self._active = False self.started.cancel() for name in self._subsystems: self.call(self.uninstall_subsystem, name) self._wakeup() def run(self, func): self.call(func, self) self.start() #=========================================================================== # Internal #=========================================================================== def _wakeup(self): raise NotImplementedError() def _work(self): now = time.time() timeout = self._process_jobs(now) if self._callbacks: timeout = 0 self._handle_transports(min(timeout, self.MAX_TIMEOUT)) self._process_callbacks() def _process_jobs(self, now): while self._jobs: ts, func, args, kwargs = self._jobs.peek() if now < ts: return ts - now self._jobs.pop() self._callbacks.append((func, args, kwargs)) return self.MAX_TIMEOUT def _process_callbacks(self): callbacks = self._callbacks self._callbacks = [] for cb, args, kwargs in callbacks: cb(*args, **kwargs) #=========================================================================== # Callbacks #=========================================================================== def call(self, func, *args, **kwargs): self._callbacks.append((func, args, kwargs)) def call_at(self, ts, func, *args, **kwargs): self._jobs.push((ts, func, args, kwargs))