def test_capture(): def add(x, y): return x + y v = outcome.capture(add, 2, y=3) assert type(v) == Value assert v.unwrap() == 5 def raise_ValueError(x): raise ValueError(x) e = outcome.capture(raise_ValueError, "two") assert type(e) == Error assert type(e.error) is ValueError assert e.error.args == ("two",)
def _run_sync_cb(self, q, fn, args): @_core.disable_ki_protection def unprotected_fn(): return fn(*args) res = outcome.capture(unprotected_fn) q.put_nowait(res)
def _run_sync_cb(self, q, fn, args): @disable_ki_protection def unprotected_fn(): return fn(*args) res = outcome.capture(unprotected_fn) q.put_nowait(res)
async def _async_callback(self, ret_func, ret_args=(), ret_kwargs=None): # check if canceled if self._gen is None: return if iscoroutinefunction(ret_func): with trio.CancelScope(shield=True): # TODO: cancel this when event is cancelled result = await outcome.acapture(ret_func, *ret_args, **(ret_kwargs or {})) assert not (hasattr(result, 'error') and isinstance(result.error, trio.Cancelled)) else: result = outcome.capture(ret_func, *ret_args, **(ret_kwargs or {})) # check if canceled if self._gen is None: return event = self._clock.create_lifecycle_aware_trigger(partial( self._kivy_callback, result), _do_nothing, release_ref=False) try: event() except ClockNotRunningError: pass
def install_fn(): installed_set( capture( install, *[ _wrap_callable(arg, queue_put) if callable(arg) else arg for arg in args ]))
def callback(q, fn, args): @disable_ki_protection def unprotected_fn(): return fn(*args) res = outcome.capture(unprotected_fn) q.put_nowait(res)
def _work(self): while True: if self._worker_lock.acquire(timeout=IDLE_TIMEOUT): # We got a job fn, deliver = self._job self._job = None result = outcome.capture(fn) # Tell the cache that we're available to be assigned a new # job. We do this *before* calling 'deliver', so that if # 'deliver' triggers a new job, it can be assigned to us # instead of spawning a new thread. self._thread_cache._idle_workers[self] = None deliver(result) else: # Timeout acquiring lock, so we can probably exit. But, # there's a race condition: we might be assigned a job *just* # as we're about to exit. So we have to check. try: del self._thread_cache._idle_workers[self] except KeyError: # Someone else removed us from the idle worker queue, so # they must be in the process of assigning us a job - loop # around and wait for it. continue else: # We successfully removed ourselves from the idle # worker queue, so no more jobs are incoming; it's safe to # exit. return
def worker_thread_fn(): result = outcome.capture(sync_fn, *args) try: token.run_sync_soon(report_back_in_trio_thread_fn, result) except trio.RunFinishedError: # The entire run finished, so our particular task is certainly # long gone -- it must have cancelled. pass
def worker_thread_fn(): result = outcome.capture(sync_fn, *args) try: token.run_sync_soon(report_back_in_trio_thread_fn, result) except _core.RunFinishedError: # The entire run finished, so our particular task is certainly # long gone -- it must have cancelled. pass
def test_traceback_frame_removal(): def raise_ValueError(x): raise ValueError(x) e = outcome.capture(raise_ValueError, 'abc') with pytest.raises(ValueError) as exc_info: e.unwrap() frames = traceback.extract_tb(exc_info.value.__traceback__) functions = [function for _, _, function, _ in frames] assert functions[-2:] == ['unwrap', 'raise_ValueError']
def _handle_job(self): # Handle job in a separate method to ensure user-created # objects are cleaned up in a consistent manner. fn, deliver = self._job self._job = None result = outcome.capture(fn) # Tell the cache that we're available to be assigned a new # job. We do this *before* calling 'deliver', so that if # 'deliver' triggers a new job, it can be assigned to us # instead of spawning a new thread. self._thread_cache._idle_workers[self] = None deliver(result)
def thread_fn(self): while True: try: request = self._portal.run(self._receive_from_trio.receive) except (Cancelled, RunFinishedError): break except trio.EndOfChannel: with suppress(Cancelled, RunFinishedError): self._portal.run(self._send_to_trio.aclose) break response = outcome.capture(request) self._portal.run(self._send_to_trio.send, response)
def report_back_in_trio_thread_fn(result): def do_release_then_return_result(): # release_on_behalf_of is an arbitrary user-defined method, so it # might raise an error. If it does, we want that error to # replace the regular return value, and if the regular return was # already an exception then we want them to chain. try: return result.unwrap() finally: limiter.release_on_behalf_of(placeholder) result = outcome.capture(do_release_then_return_result) if task_register[0] is not None: _core.reschedule(task_register[0], result)
def report_back_in_trio_thread_fn(result): def do_release_then_return_result(): # release_on_behalf_of is an arbitrary user-defined method, so it # might raise an error. If it does, we want that error to # replace the regular return value, and if the regular return was # already an exception then we want them to chain. try: return result.unwrap() finally: limiter.release_on_behalf_of(placeholder) result = outcome.capture(do_release_then_return_result) if task_register[0] is not None: trio.lowlevel.reschedule(task_register[0], result)
def thread_fn(self): while True: try: request = self._portal.run(self._request_queue.get) except Cancelled: continue except RunFinishedError: break if request is not _STOP: response = outcome.capture(request) self._portal.run(self._response_queue.put, response) else: self._portal.run(self._response_queue.put, None) break
def thread_fn(self): while True: fut = asyncio.run_coroutine_threadsafe(self._request_queue.get(), self._loop) try: request = fut.result() except CancelledError: continue if request.func is not None: request.response = outcome.capture(request.func) self._loop.call_soon_threadsafe(request.set_finished) else: self._loop.call_soon_threadsafe(request.set_finished) break
def callback(q, fn, args): @disable_ki_protection def unprotected_fn(): ret = fn(*args) if inspect.iscoroutine(ret): # Manually close coroutine to avoid RuntimeWarnings ret.close() raise TypeError( "Trio expected a sync function, but {!r} appears to be " "asynchronous".format(getattr(fn, "__qualname__", fn))) return ret res = outcome.capture(unprotected_fn) q.put_nowait(res)
def kivy_thread_callback_stopped(*largs): # This is the function that runs in the worker thread to do the # actual work and then schedule the calls to report back to trio # are we handling the callback? lock.setdefault(None, 0) # it was canceled so we have nothing to do if lock[None] is None: return def raise_stopped_error(): raise EventLoopStoppedError( f'async_run_in_kivy failed to complete <{func}> because ' f'clock stopped') task_container[1] = outcome.capture(raise_stopped_error) token.run_sync_soon( _report_kivy_back_in_trio_thread_fn, task_container, task)
def kivy_thread_callback(*largs): # This is the function that runs in the worker thread to do the # actual work and then schedule the calls to report back to trio # are we handling the callback? lock.setdefault(None, 0) # it was canceled so we have nothing to do if lock[None] is None: return task_container[1] = outcome.capture(func, *args, **kwargs) # this may raise a RunFinishedError, but # The entire run finished, so our particular tasks are # certainly long gone - this shouldn't have happened because # either the task should still be waiting because it wasn't # canceled or if it was canceled we should have returned above token.run_sync_soon( _report_kivy_back_in_trio_thread_fn, task_container, task)
def thread_fn(self): while True: fut = asyncio.run_coroutine_threadsafe(self._request_queue.get(), self._loop) try: request = fut.result() except CancelledError: continue if request is not _STOP: response = outcome.capture(request) fut = asyncio.run_coroutine_threadsafe( self._response_queue.put(response), self._loop) fut.result() else: fut = asyncio.run_coroutine_threadsafe( self._response_queue.put(None), self._loop) fut.result() break
def run_it(): result = outcome.capture(sync_fn) trio_token.run_sync_soon(trio.lowlevel.reschedule, task, result)
def capture(cls, sync_fn, *args): return outcome.capture(sync_fn, *args)
def _wrap_callable(fn, wake_up): return lambda _, *args: wake_up(capture(fn, *args))
def done_cb(_): trio.lowlevel.reschedule(task, outcome.capture(future.result))
def abort(raise_cancel): result = outcome.capture(raise_cancel) _core.reschedule(task, result) return _core.Abort.FAILED
def is_done(_): nonlocal result result = outcome.capture(future.result) self.stop()
def done_cb(_): trio.hazmat.reschedule(task, outcome.capture(future.result))
def harness(x, in_q, out_q): result_q.put(outcome.capture(_core.run, main, x, in_q, out_q))
def target() -> None: result = outcome.capture(fn) trio.from_thread.run_sync(send_channel.send_nowait, result, trio_token=trio_token)
async def run_sync(self, sync_fn: Callable, *args) -> Optional[Outcome]: await trio.lowlevel.checkpoint() if self.retire is not _special_none_making_retire: return capture(lambda *a: (sync_fn, args, trio.current_effective_deadline()))
def _work(recv_pipe, send_pipe, idle_timeout, init, retire): import inspect import signal def handle_job(job): fn, args = loads(job) ret = fn(*args) if inspect.iscoroutine(ret): # Manually close coroutine to avoid RuntimeWarnings ret.close() raise TypeError( "trio-parallel worker expected a sync function, but {!r} appears " "to be asynchronous".format(getattr( fn, "__qualname__", fn))) return ret def safe_dumps(result): try: return dumps(result, protocol=HIGHEST_PROTOCOL) except BaseException as exc: return dumps(Error(exc), protocol=HIGHEST_PROTOCOL) def poll(timeout): deadline = time.perf_counter() + timeout while timeout > _abc.MAX_TIMEOUT: if recv_pipe.poll(_abc.MAX_TIMEOUT): return True timeout = deadline - time.perf_counter() else: return recv_pipe.poll(timeout) # Intercept keyboard interrupts to avoid passing KeyboardInterrupt # between processes. (Trio will take charge via cancellation.) signal.signal(signal.SIGINT, signal.SIG_IGN) try: if isinstance(init, bytes): # true except on "fork" # Signal successful startup to spawn/forkserver parents. send_pipe.send_bytes(ACK) init = loads(init) if isinstance(retire, bytes): # true except on "fork" retire = loads(retire) init() while poll(idle_timeout): send_pipe.send_bytes( safe_dumps(capture(handle_job, recv_pipe.recv_bytes()))) if retire(): break except (BrokenPipeError, EOFError): # Graceful shutdown: If the main process closes the pipes, we will # observe one of these exceptions and can simply exit quietly. # Closing pipes manually fixed some __del__ flakiness in CI send_pipe.close() recv_pipe.close() return except BaseException: # Ensure BrokenWorkerError raised in the main proc. send_pipe.close() # recv_pipe must remain open and clear until the main proc closes it. try: while True: recv_pipe.recv_bytes() except EOFError: pass raise else: # Clean idle shutdown or retirement: close recv_pipe first to minimize # subsequent race. recv_pipe.close() # Race condition: it is possible to sneak a write through in the main process # between the while loop predicate and recv_pipe.close(). Naively, this would # make a clean shutdown look like a broken worker. By sending a sentinel # value, we can indicate to a waiting main process that we have hit this # race condition and need a restart. However, the send MUST be non-blocking # to free this process's resources in a timely manner. Therefore, this message # can be any size on Windows but must be less than 512 bytes by POSIX.1-2001. send_pipe.send_bytes(dumps(None, protocol=HIGHEST_PROTOCOL)) send_pipe.close()
def _handle_exitstatus(self, sts): """This overrides an internal API of subprocess.Popen""" self.__result = outcome.capture(_compute_returncode, sts)