| import sys |
| from concurrent.futures import Future |
| from threading import RLock |
| import logging |
| |
| from .logwrap import LogWrapper |
| |
| try: |
| from concurrent.futures import InvalidStateError |
| except ImportError: # pragma: no cover |
| # InvalidStateError doesn't exist on older versions. |
| # Keep code simple by declaring a class of same name (which will never |
| # have any instances created). |
| class InvalidStateError(RuntimeError): |
| pass |
| |
| |
| LOG = LogWrapper(logging.getLogger("more_executors._Future")) |
| |
| # This value should be used for any blocking waits likely to be invoked |
| # from the main thread, where blocking forever is technically appropriate. |
| # |
| # The reason for this is that, in Python 2.x, a blocking wait with no |
| # timeout (such as a thread join) is entirely uninterruptible, always |
| # retrying on EINTR, which can easily lead to a process not responding |
| # to anything but SIGKILL. |
| # |
| # Providing a timeout value - no matter what it is - causes the wait to |
| # become interruptible, which is desirable. |
| # |
| # This value is an arbitrary choice. 100 years ought to be enough for anyone :) |
| MAX_TIMEOUT = 60 * 60 * 24 * 365 * 100 |
| |
| |
| class _Future(Future): |
| # Need to reimplement some of the Future class. |
| # We must hold callbacks ourselves and not let the parent class handle them. |
| # This is necessary for locking: we need to handle some locking ourselves, |
| # but we must NOT have the callbacks invoked while our lock is held. |
| # (Note: an alternative would be to access concurrent.futures private variables) |
| def __init__(self): |
| super(_Future, self).__init__() |
| self._me_done_callbacks = [] |
| self._me_lock = RLock() |
| |
| def _me_invoke_callbacks(self): |
| for callback in self._me_done_callbacks: |
| try: |
| callback(self) |
| except Exception: |
| LOG.exception("exception calling callback for %r", self) |
| |
| # Drop references to the callbacks once no longer required, |
| # so that futures don't keep other objects alive longer than needed |
| self._me_done_callbacks = [] |
| |
| def add_done_callback(self, fn): |
| # Overrides function from parent; intentionally does not call super, |
| # so we own the callbacks ourself |
| with self._me_lock: |
| if not self.done(): |
| self._me_done_callbacks.append(fn) |
| return |
| # Already done -> call it directly |
| fn(self) |
| |
| def cancel(self): |
| with self._me_lock: |
| if self.cancelled(): |
| return True |
| if self.done(): |
| return False |
| if not self._me_cancel(): |
| return False |
| out = super(_Future, self).cancel() |
| if out: |
| self.set_running_or_notify_cancel() |
| if out: |
| self._me_invoke_callbacks() |
| return out |
| |
| def _me_cancel(self): |
| raise NotImplementedError( |
| "BUG: override this method in subclasses!" |
| ) # pragma: no cover |
| |
| |
| def copy_future_exception(f1, f2): |
| if "exception_info" in dir(f1): |
| exception, traceback = f1.exception_info() |
| else: |
| exception, traceback = (f1.exception(), None) |
| |
| copy_exception(f2, exception, traceback) |
| |
| |
| def copy_exception(future, exception=None, traceback=None): |
| exc_info = sys.exc_info() |
| if exception is None: |
| exception = exc_info[1] |
| if traceback is None: |
| traceback = exc_info[2] |
| |
| try: |
| try: |
| future.set_exception_info(exception, traceback) |
| return |
| except AttributeError: |
| pass |
| |
| future.set_exception(exception) |
| |
| except InvalidStateError: |
| # See commentary on try_set_result below. |
| LOG.debug("%s: can't set exception %s", future, exception, exc_info=True) |
| |
| |
| def try_set_result(future, result): |
| # Try to set a result on a future, but tolerate an InvalidStateError. |
| # |
| # The intended usage of this function is to set results on futures we've |
| # already handed out to the user and which therefore could potentially |
| # have been cancelled by them at any time.. |
| try: |
| future.set_result(result) |
| except InvalidStateError: |
| LOG.debug("%s: can't set result %s", future, result, exc_info=True) |