|
|
|
@ -27,146 +27,210 @@ |
|
|
|
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
|
|
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
|
|
|
|
|
|
"""The Future interface missing from Python's standard library. |
|
|
|
|
"""A Future interface. |
|
|
|
|
|
|
|
|
|
Python's concurrent.futures library defines a Future class very much like the |
|
|
|
|
Future defined here, but since that class is concrete and without construction |
|
|
|
|
semantics it is only available within the concurrent.futures library itself. |
|
|
|
|
The Future class defined here is an entirely abstract interface that anyone may |
|
|
|
|
Python doesn't have a Future interface in its standard library. In the absence |
|
|
|
|
of such a standard, three separate, incompatible implementations |
|
|
|
|
(concurrent.futures.Future, ndb.Future, and asyncio.Future) have appeared. This |
|
|
|
|
interface attempts to be as compatible as possible with |
|
|
|
|
concurrent.futures.Future. From ndb.Future it adopts a traceback-object accessor |
|
|
|
|
method. |
|
|
|
|
|
|
|
|
|
Unlike the concrete and implemented Future classes listed above, the Future |
|
|
|
|
class defined in this module is an entirely abstract interface that anyone may |
|
|
|
|
implement and use. |
|
|
|
|
|
|
|
|
|
The one known incompatibility between this interface and the interface of |
|
|
|
|
concurrent.futures.Future is that this interface defines its own CancelledError |
|
|
|
|
and TimeoutError exceptions rather than raising the implementation-private |
|
|
|
|
concurrent.futures._base.CancelledError and the |
|
|
|
|
built-in-but-only-in-3.3-and-later TimeoutError. |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
import abc |
|
|
|
|
import collections |
|
|
|
|
|
|
|
|
|
RETURNED = object() |
|
|
|
|
RAISED = object() |
|
|
|
|
ABORTED = object() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Outcome(object): |
|
|
|
|
"""A sum type describing the outcome of some computation. |
|
|
|
|
|
|
|
|
|
Attributes: |
|
|
|
|
category: One of RETURNED, RAISED, or ABORTED, respectively indicating |
|
|
|
|
that the computation returned a value, raised an exception, or was |
|
|
|
|
aborted. |
|
|
|
|
return_value: The value returned by the computation. Must be present if |
|
|
|
|
category is RETURNED. |
|
|
|
|
exception: The exception raised by the computation. Must be present if |
|
|
|
|
category is RAISED. |
|
|
|
|
""" |
|
|
|
|
__metaclass__ = abc.ABCMeta |
|
|
|
|
|
|
|
|
|
class TimeoutError(Exception): |
|
|
|
|
"""Indicates that a particular call timed out.""" |
|
|
|
|
|
|
|
|
|
class _EasyOutcome( |
|
|
|
|
collections.namedtuple('_EasyOutcome', |
|
|
|
|
['category', 'return_value', 'exception']), |
|
|
|
|
Outcome): |
|
|
|
|
"""A trivial implementation of Outcome.""" |
|
|
|
|
|
|
|
|
|
# All Outcomes describing abortion are indistinguishable so there might as well |
|
|
|
|
# be only one. |
|
|
|
|
_ABORTED_OUTCOME = _EasyOutcome(ABORTED, None, None) |
|
|
|
|
class CancelledError(Exception): |
|
|
|
|
"""Indicates that the computation underlying a Future was cancelled.""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def aborted(): |
|
|
|
|
"""Returns an Outcome indicating that a computation was aborted. |
|
|
|
|
class Future(object): |
|
|
|
|
"""A representation of a computation in another control flow. |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
An Outcome indicating that a computation was aborted. |
|
|
|
|
Computations represented by a Future may be yet to be begun, may be ongoing, |
|
|
|
|
or may have already completed. |
|
|
|
|
""" |
|
|
|
|
return _ABORTED_OUTCOME |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def raised(exception): |
|
|
|
|
"""Returns an Outcome indicating that a computation raised an exception. |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
exception: The exception raised by the computation. |
|
|
|
|
__metaclass__ = abc.ABCMeta |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
An Outcome indicating that a computation raised the given exception. |
|
|
|
|
""" |
|
|
|
|
return _EasyOutcome(RAISED, None, exception) |
|
|
|
|
# NOTE(nathaniel): This isn't the return type that I would want to have if it |
|
|
|
|
# were up to me. Were this interface being written from scratch, the return |
|
|
|
|
# type of this method would probably be a sum type like: |
|
|
|
|
# |
|
|
|
|
# NOT_COMMENCED |
|
|
|
|
# COMMENCED_AND_NOT_COMPLETED |
|
|
|
|
# PARTIAL_RESULT<Partial_Result_Type> |
|
|
|
|
# COMPLETED<Result_Type> |
|
|
|
|
# UNCANCELLABLE |
|
|
|
|
# NOT_IMMEDIATELY_DETERMINABLE |
|
|
|
|
@abc.abstractmethod |
|
|
|
|
def cancel(self): |
|
|
|
|
"""Attempts to cancel the computation. |
|
|
|
|
|
|
|
|
|
This method does not block. |
|
|
|
|
|
|
|
|
|
def returned(value): |
|
|
|
|
"""Returns an Outcome indicating that a computation returned a value. |
|
|
|
|
Returns: |
|
|
|
|
True if the computation has not yet begun, will not be allowed to take |
|
|
|
|
place, and determination of both was possible without blocking. False |
|
|
|
|
under all other circumstances including but not limited to the |
|
|
|
|
computation's already having begun, the computation's already having |
|
|
|
|
finished, and the computation's having been scheduled for execution on a |
|
|
|
|
remote system for which a determination of whether or not it commenced |
|
|
|
|
before being cancelled cannot be made without blocking. |
|
|
|
|
""" |
|
|
|
|
raise NotImplementedError() |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
value: The value returned by the computation. |
|
|
|
|
# NOTE(nathaniel): Here too this isn't the return type that I'd want this |
|
|
|
|
# method to have if it were up to me. I think I'd go with another sum type |
|
|
|
|
# like: |
|
|
|
|
# |
|
|
|
|
# NOT_CANCELLED (this object's cancel method hasn't been called) |
|
|
|
|
# NOT_COMMENCED |
|
|
|
|
# COMMENCED_AND_NOT_COMPLETED |
|
|
|
|
# PARTIAL_RESULT<Partial_Result_Type> |
|
|
|
|
# COMPLETED<Result_Type> |
|
|
|
|
# UNCANCELLABLE |
|
|
|
|
# NOT_IMMEDIATELY_DETERMINABLE |
|
|
|
|
# |
|
|
|
|
# Notice how giving the cancel method the right semantics obviates most |
|
|
|
|
# reasons for this method to exist. |
|
|
|
|
@abc.abstractmethod |
|
|
|
|
def cancelled(self): |
|
|
|
|
"""Describes whether the computation was cancelled. |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
An Outcome indicating that a computation returned the given value. |
|
|
|
|
""" |
|
|
|
|
return _EasyOutcome(RETURNED, value, None) |
|
|
|
|
This method does not block. |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
True if the computation was cancelled any time before its result became |
|
|
|
|
immediately available. False under all other circumstances including but |
|
|
|
|
not limited to this object's cancel method not having been called and |
|
|
|
|
the computation's result having become immediately available. |
|
|
|
|
""" |
|
|
|
|
raise NotImplementedError() |
|
|
|
|
|
|
|
|
|
class Future(object): |
|
|
|
|
"""A representation of a computation happening in another control flow. |
|
|
|
|
@abc.abstractmethod |
|
|
|
|
def running(self): |
|
|
|
|
"""Describes whether the computation is taking place. |
|
|
|
|
|
|
|
|
|
Computations represented by a Future may have already completed, may be |
|
|
|
|
ongoing, or may be yet to be begun. |
|
|
|
|
This method does not block. |
|
|
|
|
|
|
|
|
|
Computations represented by a Future are considered uninterruptable; once |
|
|
|
|
started they will be allowed to terminate either by returning or raising |
|
|
|
|
an exception. |
|
|
|
|
""" |
|
|
|
|
__metaclass__ = abc.ABCMeta |
|
|
|
|
Returns: |
|
|
|
|
True if the computation is scheduled to take place in the future or is |
|
|
|
|
taking place now, or False if the computation took place in the past or |
|
|
|
|
was cancelled. |
|
|
|
|
""" |
|
|
|
|
raise NotImplementedError() |
|
|
|
|
|
|
|
|
|
# NOTE(nathaniel): These aren't quite the semantics I'd like here either. I |
|
|
|
|
# would rather this only returned True in cases in which the underlying |
|
|
|
|
# computation completed successfully. A computation's having been cancelled |
|
|
|
|
# conflicts with considering that computation "done". |
|
|
|
|
@abc.abstractmethod |
|
|
|
|
def cancel(self): |
|
|
|
|
"""Attempts to cancel the computation. |
|
|
|
|
def done(self): |
|
|
|
|
"""Describes whether the computation has taken place. |
|
|
|
|
|
|
|
|
|
This method does not block. |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
True if the computation will not be allowed to take place or False if |
|
|
|
|
the computation has already taken place or is currently taking place. |
|
|
|
|
True if the computation is known to have either completed or have been |
|
|
|
|
unscheduled or interrupted. False if the computation may possibly be |
|
|
|
|
executing or scheduled to execute later. |
|
|
|
|
""" |
|
|
|
|
raise NotImplementedError() |
|
|
|
|
|
|
|
|
|
@abc.abstractmethod |
|
|
|
|
def cancelled(self): |
|
|
|
|
"""Describes whether the computation was cancelled. |
|
|
|
|
def result(self, timeout=None): |
|
|
|
|
"""Accesses the outcome of the computation or raises its exception. |
|
|
|
|
|
|
|
|
|
This method may return immediately or may block. |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
timeout: The length of time in seconds to wait for the computation to |
|
|
|
|
finish or be cancelled, or None if this method should block until the |
|
|
|
|
computation has finished or is cancelled no matter how long that takes. |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
True if the computation was cancelled and did not take place or False |
|
|
|
|
if the computation took place, is taking place, or is scheduled to |
|
|
|
|
take place in the future. |
|
|
|
|
The return value of the computation. |
|
|
|
|
|
|
|
|
|
Raises: |
|
|
|
|
TimeoutError: If a timeout value is passed and the computation does not |
|
|
|
|
terminate within the allotted time. |
|
|
|
|
CancelledError: If the computation was cancelled. |
|
|
|
|
Exception: If the computation raised an exception, this call will raise |
|
|
|
|
the same exception. |
|
|
|
|
""" |
|
|
|
|
raise NotImplementedError() |
|
|
|
|
|
|
|
|
|
@abc.abstractmethod |
|
|
|
|
def done(self): |
|
|
|
|
"""Describes whether the computation has taken place. |
|
|
|
|
def exception(self, timeout=None): |
|
|
|
|
"""Return the exception raised by the computation. |
|
|
|
|
|
|
|
|
|
This method may return immediately or may block. |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
timeout: The length of time in seconds to wait for the computation to |
|
|
|
|
terminate or be cancelled, or None if this method should block until |
|
|
|
|
the computation is terminated or is cancelled no matter how long that |
|
|
|
|
takes. |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
True if the computation took place; False otherwise. |
|
|
|
|
The exception raised by the computation, or None if the computation did |
|
|
|
|
not raise an exception. |
|
|
|
|
|
|
|
|
|
Raises: |
|
|
|
|
TimeoutError: If a timeout value is passed and the computation does not |
|
|
|
|
terminate within the allotted time. |
|
|
|
|
CancelledError: If the computation was cancelled. |
|
|
|
|
""" |
|
|
|
|
raise NotImplementedError() |
|
|
|
|
|
|
|
|
|
@abc.abstractmethod |
|
|
|
|
def outcome(self): |
|
|
|
|
"""Accesses the outcome of the computation. |
|
|
|
|
def traceback(self, timeout=None): |
|
|
|
|
"""Access the traceback of the exception raised by the computation. |
|
|
|
|
|
|
|
|
|
If the computation has not yet completed, this method blocks until it has. |
|
|
|
|
This method may return immediately or may block. |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
timeout: The length of time in seconds to wait for the computation to |
|
|
|
|
terminate or be cancelled, or None if this method should block until |
|
|
|
|
the computation is terminated or is cancelled no matter how long that |
|
|
|
|
takes. |
|
|
|
|
|
|
|
|
|
Returns: |
|
|
|
|
An Outcome describing the outcome of the computation. |
|
|
|
|
The traceback of the exception raised by the computation, or None if the |
|
|
|
|
computation did not raise an exception. |
|
|
|
|
|
|
|
|
|
Raises: |
|
|
|
|
TimeoutError: If a timeout value is passed and the computation does not |
|
|
|
|
terminate within the allotted time. |
|
|
|
|
CancelledError: If the computation was cancelled. |
|
|
|
|
""" |
|
|
|
|
raise NotImplementedError() |
|
|
|
|
|
|
|
|
|
@abc.abstractmethod |
|
|
|
|
def add_done_callback(self, callback): |
|
|
|
|
def add_done_callback(self, fn): |
|
|
|
|
"""Adds a function to be called at completion of the computation. |
|
|
|
|
|
|
|
|
|
The callback will be passed an Outcome object describing the outcome of |
|
|
|
|
The callback will be passed this Future object describing the outcome of |
|
|
|
|
the computation. |
|
|
|
|
|
|
|
|
|
If the computation has already completed, the callback will be called |
|
|
|
|
immediately. |
|
|
|
|
|
|
|
|
|
Args: |
|
|
|
|
callback: A callable taking an Outcome as its single parameter. |
|
|
|
|
fn: A callable taking a this Future object as its single parameter. |
|
|
|
|
""" |
|
|
|
|
raise NotImplementedError() |
|
|
|
|