def join(self, timeout: Optional[int] = 0): """ Approximate equivalent of Gevent's or Celery's `.join()` where current thread blocks until all tasks are reported done. :param Union[int, float] timeout: If set, wait only for this amount of time in seconds. :raises: TimeoutError """ timeout_is_set = bool(timeout) timeout_time = time.time() + timeout period = gevent.getswitchinterval() * 10 remaining_tasks = self.get_remaining_tasks_count() while remaining_tasks: if timeout_is_set and timeout_time < time.time(): raise TimeoutError( f'{self} has {remaining_tasks} remaining tasks after timeout' ) gevent.sleep(period) remaining_tasks = self.get_remaining_tasks_count()
def _run_callbacks(self): # pylint:disable=too-many-branches # When we're running callbacks, its safe for timers to # update the notion of the current time (because if we're here, # we're not running in a timer callback that may let other timers # run; this is mostly an issue for libuv). # That's actually a bit of a lie: on libev, self._timer0 really is # a timer, and so sometimes this is running in a timer callback, not # a prepare callback. But that's OK, libev doesn't suffer from cascading # timer expiration and its safe to update the loop time at any # moment there. self.starting_timer_may_update_loop_time = True try: count = CALLBACK_CHECK_COUNT now = self.now() expiration = now + getswitchinterval() self._stop_callback_timer() while self._callbacks: cb = self._callbacks.popleft() count -= 1 self.unref() # XXX: libuv doesn't have a global ref count! callback = cb.callback cb.callback = None args = cb.args if callback is None or args is None: # it's been stopped continue try: callback(*args) except: # pylint:disable=bare-except # If we allow an exception to escape this method (while we are running the ev callback), # then CFFI will print the error and libev will continue executing. # There are two problems with this. The first is that the code after # the loop won't run. The second is that any remaining callbacks scheduled # for this loop iteration will be silently dropped; they won't run, but they'll # also not be *stopped* (which is not a huge deal unless you're looking for # consistency or checking the boolean/pending status; the loop doesn't keep # a reference to them like it does to watchers...*UNLESS* the callback itself had # a reference to a watcher; then I don't know what would happen, it depends on # the state of the watcher---a leak or crash is not totally inconceivable). # The Cython implementation in core.ppyx uses gevent_call from callbacks.c # to run the callback, which uses gevent_handle_error to handle any errors the # Python callback raises...it unconditionally simply prints any error raised # by loop.handle_error and clears it, so callback handling continues. # We take a similar approach (but are extra careful about printing) try: self.handle_error(cb, *sys.exc_info()) except: # pylint:disable=bare-except try: print("Exception while handling another error", file=sys.stderr) traceback.print_exc() except: # pylint:disable=bare-except pass # Nothing we can do here finally: # NOTE: this must be reset here, because cb.args is used as a flag in # the callback class so that bool(cb) of a callback that has been run # becomes False cb.args = None # We've finished running one group of callbacks # but we may have more, so before looping check our # switch interval. if count == 0 and self._callbacks: count = CALLBACK_CHECK_COUNT self.update_now() if self.now() >= expiration: now = 0 break # Update the time before we start going again, if we didn't # just do so. if now != 0: self.update_now() if self._callbacks: self._start_callback_timer() finally: self.starting_timer_may_update_loop_time = False
def _run_callbacks(self): # pylint:disable=too-many-branches # When we're running callbacks, its safe for timers to # update the notion of the current time (because if we're here, # we're not running in a timer callback that may let other timers # run; this is mostly an issue for libuv). # That's actually a bit of a lie: on libev, self._timer0 really is # a timer, and so sometimes this is running in a timer callback, not # a prepare callback. But that's OK, libev doesn't suffer from cascading # timer expiration and its safe to update the loop time at any # moment there. self.starting_timer_may_update_loop_time = True try: count = CALLBACK_CHECK_COUNT now = self.now() expiration = now + getswitchinterval() self._stop_callback_timer() while self._callbacks: cb = self._callbacks.popleft() # pylint:disable=assignment-from-no-return count -= 1 self.unref() # XXX: libuv doesn't have a global ref count! callback = cb.callback cb.callback = None args = cb.args if callback is None or args is None: # it's been stopped continue try: callback(*args) except: # pylint:disable=bare-except # If we allow an exception to escape this method (while we are running the ev callback), # then CFFI will print the error and libev will continue executing. # There are two problems with this. The first is that the code after # the loop won't run. The second is that any remaining callbacks scheduled # for this loop iteration will be silently dropped; they won't run, but they'll # also not be *stopped* (which is not a huge deal unless you're looking for # consistency or checking the boolean/pending status; the loop doesn't keep # a reference to them like it does to watchers...*UNLESS* the callback itself had # a reference to a watcher; then I don't know what would happen, it depends on # the state of the watcher---a leak or crash is not totally inconceivable). # The Cython implementation in core.ppyx uses gevent_call from callbacks.c # to run the callback, which uses gevent_handle_error to handle any errors the # Python callback raises...it unconditionally simply prints any error raised # by loop.handle_error and clears it, so callback handling continues. # We take a similar approach (but are extra careful about printing) try: self.handle_error(cb, *sys.exc_info()) except: # pylint:disable=bare-except try: print("Exception while handling another error", file=sys.stderr) traceback.print_exc() except: # pylint:disable=bare-except pass # Nothing we can do here finally: # NOTE: this must be reset here, because cb.args is used as a flag in # the callback class so that bool(cb) of a callback that has been run # becomes False cb.args = None # We've finished running one group of callbacks # but we may have more, so before looping check our # switch interval. if count == 0 and self._callbacks: count = CALLBACK_CHECK_COUNT self.update_now() if self.now() >= expiration: now = 0 break # Update the time before we start going again, if we didn't # just do so. if now != 0: self.update_now() if self._callbacks: self._start_callback_timer() finally: self.starting_timer_may_update_loop_time = False