2016-10-13 17:48:35 +00:00
|
|
|
import queue
|
|
|
|
|
|
|
|
from functools import wraps
|
|
|
|
|
|
|
|
from concurrent.futures import Executor, Future, CancelledError
|
|
|
|
|
|
|
|
|
|
|
|
class AsyncExecutorWrapper(object):
|
|
|
|
""" This class will wrap a syncronous library transparently in a way which
|
|
|
|
will move all calls off to an asynchronous Executor, and will change all
|
|
|
|
returned values to be Future objects.
|
|
|
|
"""
|
|
|
|
SYNC_FLAG_FIELD = '__AsyncExecutorWrapper__sync__'
|
|
|
|
|
|
|
|
def __init__(self, delegate, executor):
|
|
|
|
""" Wrap the specified synchronous delegate instance, and submit() all
|
|
|
|
method calls to the specified Executor instance.
|
|
|
|
"""
|
|
|
|
self._delegate = delegate
|
|
|
|
self._executor = executor
|
|
|
|
|
|
|
|
def __getattr__(self, attr_name):
|
|
|
|
maybe_callable = getattr(self._delegate, attr_name) # Will raise proper attribute error
|
|
|
|
if callable(maybe_callable):
|
|
|
|
# Build a callable which when executed places the request
|
|
|
|
# onto a queue
|
|
|
|
@wraps(maybe_callable)
|
|
|
|
def wrapped_method(*args, **kwargs):
|
|
|
|
if getattr(maybe_callable, self.SYNC_FLAG_FIELD, False):
|
|
|
|
sync_result = Future()
|
|
|
|
try:
|
|
|
|
sync_result.set_result(maybe_callable(*args, **kwargs))
|
|
|
|
except Exception as ex:
|
|
|
|
sync_result.set_exception(ex)
|
|
|
|
return sync_result
|
|
|
|
|
|
|
|
try:
|
|
|
|
return self._executor.submit(maybe_callable, *args, **kwargs)
|
|
|
|
except queue.Full as ex:
|
|
|
|
queue_full = Future()
|
|
|
|
queue_full.set_exception(ex)
|
|
|
|
return queue_full
|
|
|
|
|
|
|
|
return wrapped_method
|
|
|
|
else:
|
|
|
|
return maybe_callable
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def sync(cls, f):
|
|
|
|
""" Annotate the given method to flag it as synchronous so that AsyncExecutorWrapper
|
|
|
|
will return the result immediately without submitting it to the executor.
|
|
|
|
"""
|
|
|
|
setattr(f, cls.SYNC_FLAG_FIELD, True)
|
|
|
|
return f
|
|
|
|
|
|
|
|
|
2017-05-15 17:45:44 +00:00
|
|
|
class NullExecutorCancelled(CancelledError):
|
|
|
|
def __init__(self):
|
|
|
|
super(NullExecutorCancelled, self).__init__('Null executor always fails.')
|
|
|
|
|
|
|
|
|
2016-10-13 17:48:35 +00:00
|
|
|
class NullExecutor(Executor):
|
|
|
|
""" Executor instance which always returns a Future completed with a
|
|
|
|
CancelledError exception. """
|
|
|
|
def submit(self, _, *args, **kwargs):
|
|
|
|
always_fail = Future()
|
2017-05-15 17:45:44 +00:00
|
|
|
always_fail.set_exception(NullExecutorCancelled())
|
2016-10-13 17:48:35 +00:00
|
|
|
return always_fail
|