예제 #1
0
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()
예제 #2
0
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
예제 #3
0
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()
예제 #4
0
 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
예제 #5
0
 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
예제 #6
0
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()
예제 #7
0
    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
예제 #8
0
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()
예제 #9
0
    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
예제 #10
0
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"))
예제 #11
0
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"))
예제 #12
0
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
예제 #13
0
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))
예제 #14
0
파일: base.py 프로젝트: pombreda/microactor
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))
예제 #15
0
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))
예제 #16
0
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))