def _stopped(self): """ Invoked when the loop has been stopped """ self.running = False self.stopping = False ## remove all the timers and pollers for timer in self.timers: timer.destroy() for poller in self.pollers.values(): poller.destroy() for callback in self.callbacks: callback.destroy() self.timers = set() self.pollers = {} self.callbacks = set()
def _timer_canceled (self, timer): """ A timer has been canceled :param timer: the timer that has been canceled :return: nothing """ try: timer.destroy() except (AttributeError, TypeError): pass try: self.timers.remove(timer) except KeyError: pass
def _timer_canceled(self, timer): """ A timer has been canceled :param timer: the timer that has been canceled :return: nothing """ try: timer.destroy() except (AttributeError, TypeError): pass try: self.timers.remove(timer) except KeyError: pass
class Hub(object): """ Base hub class for easing the implementation of subclasses that are specific to a particular underlying event architecture. """ SYSTEM_EXCEPTIONS = (KeyboardInterrupt, SystemExit) SYSTEM_EXCEPTIONS_SIGNUMS = (signal.SIGINT, signal.SIGTERM) READ = READ WRITE = WRITE error_handler = None def __init__(self, clock=time.time, ptr=None, default=True): """ :param clock: :type clock: :param default: default loop :type default: boolean :param ptr: a pointer a an (optional) libuv loop :type ptr: a "uv_loop_t*" """ self.clock = clock self.greenlet = greenlet.greenlet(self.run) self.stopping = False self.running = False self.timers = set() self.pollers = {} self.callbacks = set() self.debug_exceptions = True self.debug_blocking = False self.debug_blocking_resolution = 1 self.interrupted = False if ptr: self.uv_loop = ptr else: if _default_loop_destroyed: default = False self.uv_sighandlers = set() self.uv_loop = pyuv.Loop.default_loop() if default: #self.uv_signal_checker = pyuv.SignalChecker(self.uv_loop) for signum in self.SYSTEM_EXCEPTIONS_SIGNUMS: handler = pyuv.Signal(self.uv_loop) handler.start(self.signal_received, int(signum)) self.uv_sighandlers.add(handler) #else: # self.uv_signal_checker = None if not self.uv_loop: raise SystemError("default_loop() failed") def block_detect_pre(self): # shortest alarm we can possibly raise is one second self.block_detect_handle = pyuv.Signal(self.uv_loop) self.block_detect_handle.start(alarm_handler, signal.SIGALRM) def block_detect_post(self): if hasattr(self, "block_detect_handle"): self.block_detect_handle.stop() del self.block_detect_handle def add(self, evtype, fileno, cb, persistent=False): """ Signals an intent to or write a particular file descriptor. :param evtype: either the constant READ or WRITE. :param fileno: the file number of the file of interest. :param cb: callback which will be called when the file is ready for reading/writing. """ if fileno in self.pollers: p = self.pollers[fileno] current_events = 0 ## check we do not have another callback on the same descriptor and event if p.notify_readable and evtype is READ: raise RuntimeError( 'there is already %s reading from descriptor %d' % (str(p), fileno)) if p.notify_writable and evtype is WRITE: raise RuntimeError( 'there is already %s writing to descriptor %d' % (str(p), fileno)) p.start(self, current_events | evtype, cb, fileno) else: p = poller.Poller(fileno, persistent=persistent) p.start(self, evtype, cb, fileno) ## register the poller self.pollers[fileno] = p return p def remove(self, p): """ Remove a listener :param listener: the listener to remove """ self._poller_canceled(p) def remove_descriptor(self, fileno, skip_callbacks=False): """ Completely remove all watchers for this *fileno*. For internal use only. """ try: p = self.pollers[fileno] except KeyError: return try: if not skip_callbacks: # invoke the callbacks in the poller and destroy it p(READ) p(WRITE) except self.SYSTEM_EXCEPTIONS: self.interrupted = True except: self.squelch_io_exception(fileno, sys.exc_info()) finally: self._poller_canceled(p) def set_timer_exceptions(self, value): """ Debug exceptions :param value: True if we want to debug exceptions :type value: boolean """ self.debug_exceptions = value def ensure_greenlet(self): if self.greenlet.dead: # create new greenlet sharing same parent as original new = greenlet.greenlet(self.run, self.greenlet.parent) # need to assign as parent of old greenlet # for those greenlets that are currently # children of the dead hub and may subsequently # exit without further switching to hub. self.greenlet.parent = new self.greenlet = new def switch(self): """ Switches to a different greenlet :return: :rtype: """ cur = greenlet.getcurrent() assert cur is not self.greenlet, 'Cannot switch to MAINLOOP from MAINLOOP' switch_out = getattr(cur, 'switch_out', None) if switch_out is not None: try: switch_out() except: self.squelch_generic_exception(sys.exc_info()) self.ensure_greenlet() try: if self.greenlet.parent is not cur: cur.parent = self.greenlet except ValueError: pass # gets raised if there is a greenlet parent cycle clear_sys_exc_info() return self.greenlet.switch() def cede(self): """ Switch temporarily to any other greenlet, and then return to the current greenlet :return: the greenlet that takes control """ current = greenlet.getcurrent() self.run_callback(current.switch) return self.switch() def wait(self, seconds=None): """ This timeout will cause us to return from the dispatch() call when we want to :param seconds: the amount of seconds to wait :type seconds: integer """ if not seconds: seconds = self.default_sleep() def empty_callback(handle): pass ## create a timer for avoiding exiting the loop for *seconds* timer = pyuv.Timer(empty_callback, seconds, 0) timer.start(None) try: status = self.loop(once=True) except self.SYSTEM_EXCEPTIONS: self.interrupted = True except: self.squelch_io_exception(-1, sys.exc_info()) # we are explicitly ignoring the status because in our experience it's # harmless and there's nothing meaningful we could do with it anyway timer.stop() # raise any signals that deserve raising if self.interrupted: self.interrupted = False raise KeyboardInterrupt() if self.debug_blocking: self.block_detect_post() def default_sleep(self): return 10.0 def sleep_until(self): t = self.timers if not t: return None return t[0][0] def run(self, *a, **kw): """ Run the loop until abort is called. """ # accept and discard variable arguments because they will be # supplied if other greenlets have run and exited before the # hub's greenlet gets a chance to run if self.running: raise RuntimeError("Already running!") try: self.running = True self.stopping = False while not self.stopping: if self.debug_blocking: self.block_detect_pre() try: more_events = self.loop(once=True) except self.SYSTEM_EXCEPTIONS: self.interrupted = True except: self.squelch_io_exception(-1, sys.exc_info()) if self.debug_blocking: self.block_detect_post() ## if there are no active events, just get out of here... if not more_events: self.stopping = True finally: self._stopped() def _stopped(self): """ Invoked when the loop has been stopped """ self.running = False self.stopping = False ## remove all the timers and pollers for timer in self.timers: timer.destroy() for poller in self.pollers.values(): poller.destroy() for callback in self.callbacks: callback.destroy() self.timers = set() self.pollers = {} self.callbacks = set() def loop(self, once=False): """ Loop the events :param once: if True, polls for new events once (and it blocks if there are no pending events) :return: 1 if more events are expected, 0 otherwise (when *once* is False, it always returns 0) """ #if self.uv_signal_checker: # self.uv_signal_checker.start() if once: return self.uv_loop.run(pyuv.UV_RUN_ONCE) else: return self.uv_loop.run() def abort(self, wait=False): """ Stop the loop. If run is executing, it will exit after completing the next loop iteration. Set *wait* to True to cause abort to switch to the hub immediately and wait until it's finished processing. Waiting for the hub will only work from the main greenthread; all other greenthreads will become unreachable. """ if self.running: self.stopping = True if wait: assert self.greenlet is not greenlet.getcurrent( ), "Can't abort with wait from inside the hub's greenlet." # schedule an immediate timer just so the hub doesn't sleep self.run_callback(lambda: None) # switch to it; when done the hub will switch back to its parent, # the main greenlet self.switch() def destroy(self): """ Destroy the events loop :return: None """ global _default_loop_destroyed if self.uv_loop: self._stopped() ## destroy all the signals stuff #if self.uv_signal_checker: # for handler in self.uv_sighandlers: handler.stop() # self.uv_signal_checker.stop() if self.uv_loop == pyuv.Loop.default_loop(): _default_loop_destroyed = True del self.uv_loop @property def num_active(self): try: return self.uv_loop.active_handles except AttributeError: return 0 @property def counters(self): try: return self.uv_loop.counters except AttributeError: return 0 @property def last_error(self): try: return self.uv_loop.last_err except AttributeError: return 0 ## ## timers ## def add_timer(self, timer): """ Add a timer in the hub :param timer: :param unreferenced: if True, we unreference the timer, so the loop does not wait until it is triggered :return: """ eventtimer = pyuv.Timer(self.uv_loop) eventtimer.data = timer timer.impltimer = eventtimer eventtimer.start(self._timer_triggered, float(timer.seconds), 0) self.timers.add(timer) def _timer_canceled(self, timer): """ A timer has been canceled :param timer: the timer that has been canceled :return: nothing """ try: timer.destroy() except (AttributeError, TypeError): pass try: self.timers.remove(timer) except KeyError: pass def _timer_triggered(self, handle): """ Performs the timer trigger :param timer: the timer that has been triggered :return: nothing """ timer = handle.data try: timer() except self.SYSTEM_EXCEPTIONS: self.interrupted = True except Exception, e: self.squelch_exception(sys.exc_info()) try: timer.destroy() except (AttributeError, TypeError): pass try: self.timers.remove(timer) except KeyError: pass