class RetryForkExecutorTestCase(RetryTestCase): """ Wrap each coroutine function with AbstractEventLoop.run_in_executor, in order to test the event loop's default executor. The executor may use either a thread or a subprocess, and either case is automatically detected and handled. """ def __init__(self, *pargs, **kwargs): super(RetryForkExecutorTestCase, self).__init__(*pargs, **kwargs) self._executor = None def _setUpExecutor(self): self._executor = ForkExecutor() def _tearDownExecutor(self): if self._executor is not None: self._executor.shutdown(wait=True) self._executor = None def setUp(self): self._setUpExecutor() def tearDown(self): self._tearDownExecutor() def _wrap_coroutine_func(self, coroutine_func): parent_loop = global_event_loop() # Since ThreadPoolExecutor does not propagate cancellation of a # parent_future to the underlying coroutine, use kill_switch to # propagate task cancellation to wrapper, so that HangForever's # thread returns when retry eventually cancels parent_future. def wrapper(kill_switch): loop = global_event_loop() if loop is parent_loop: # thread in main process result = coroutine_func() event = threading.Event() loop.call_soon_threadsafe(result.add_done_callback, lambda result: event.set()) loop.call_soon_threadsafe(kill_switch.add_done_callback, lambda kill_switch: event.set()) event.wait() return result.result() else: # child process try: return loop.run_until_complete(coroutine_func()) finally: loop.close() def execute_wrapper(): kill_switch = parent_loop.create_future() parent_future = asyncio.ensure_future( parent_loop.run_in_executor(self._executor, wrapper, kill_switch), loop=parent_loop) parent_future.add_done_callback( lambda parent_future: None if kill_switch.done() else kill_switch.set_result(None)) return parent_future return execute_wrapper
class RetryForkExecutorTestCase(RetryTestCase): """ Wrap each coroutine function with AbstractEventLoop.run_in_executor, in order to test the event loop's default executor. The executor may use either a thread or a subprocess, and either case is automatically detected and handled. """ def __init__(self, *pargs, **kwargs): super(RetryForkExecutorTestCase, self).__init__(*pargs, **kwargs) self._executor = None def _setUpExecutor(self): self._executor = ForkExecutor() def _tearDownExecutor(self): if self._executor is not None: self._executor.shutdown(wait=True) self._executor = None def setUp(self): self._setUpExecutor() def tearDown(self): self._tearDownExecutor() def _wrap_coroutine_func(self, coroutine_func): parent_loop = global_event_loop() # Since ThreadPoolExecutor does not propagate cancellation of a # parent_future to the underlying coroutine, use kill_switch to # propagate task cancellation to wrapper, so that HangForever's # thread returns when retry eventually cancels parent_future. def wrapper(kill_switch): loop = global_event_loop() if loop is parent_loop: # thread in main process result = coroutine_func() event = threading.Event() loop.call_soon_threadsafe(result.add_done_callback, lambda result: event.set()) loop.call_soon_threadsafe(kill_switch.add_done_callback, lambda kill_switch: event.set()) event.wait() return result.result() else: # child process try: return loop.run_until_complete(coroutine_func()) finally: loop.close() def execute_wrapper(): kill_switch = parent_loop.create_future() parent_future = asyncio.ensure_future(parent_loop.run_in_executor( self._executor, wrapper, kill_switch), loop=parent_loop) parent_future.add_done_callback( lambda parent_future: None if kill_switch.done() else kill_switch.set_result(None)) return parent_future return execute_wrapper
class RetryForkExecutorTestCase(RetryTestCase): """ Wrap each coroutine function with AbstractEventLoop.run_in_executor, in order to test the event loop's default executor. The executor may use either a thread or a subprocess, and either case is automatically detected and handled. """ def __init__(self, *pargs, **kwargs): super(RetryForkExecutorTestCase, self).__init__(*pargs, **kwargs) self._executor = None def _setUpExecutor(self): self._executor = ForkExecutor() def _tearDownExecutor(self): if self._executor is not None: self._executor.shutdown(wait=True) self._executor = None def setUp(self): self._setUpExecutor() def tearDown(self): self._tearDownExecutor() @contextlib.contextmanager def _wrap_coroutine_func(self, coroutine_func): parent_loop = global_event_loop() parent_pid = portage.getpid() pending = weakref.WeakValueDictionary() # Since ThreadPoolExecutor does not propagate cancellation of a # parent_future to the underlying coroutine, use kill_switch to # propagate task cancellation to wrapper, so that HangForever's # thread returns when retry eventually cancels parent_future. def wrapper(kill_switch): if portage.getpid() == parent_pid: # thread in main process def done_callback(result): result.cancelled() or result.exception() or result.result() kill_switch.set() def start_coroutine(future): result = asyncio.ensure_future(coroutine_func(), loop=parent_loop) pending[id(result)] = result result.add_done_callback(done_callback) future.set_result(result) future = Future() parent_loop.call_soon_threadsafe(start_coroutine, future) kill_switch.wait() if not future.done(): future.cancel() raise asyncio.CancelledError elif not future.result().done(): future.result().cancel() raise asyncio.CancelledError else: return future.result().result() # child process loop = global_event_loop() try: return loop.run_until_complete(coroutine_func()) finally: loop.close() def execute_wrapper(): kill_switch = threading.Event() parent_future = asyncio.ensure_future(parent_loop.run_in_executor( self._executor, wrapper, kill_switch), loop=parent_loop) def kill_callback(parent_future): if not kill_switch.is_set(): kill_switch.set() parent_future.add_done_callback(kill_callback) return parent_future try: yield execute_wrapper finally: while True: try: _, future = pending.popitem() except KeyError: break try: parent_loop.run_until_complete(future) except (Exception, asyncio.CancelledError): pass future.cancelled() or future.exception() or future.result()