Beispiel #1
0
def test_outcomes_unwrap_raises_trio_error_over_qt_error():
    """Unwrapping an Outcomes prioritizes a Trio error over a Qt error."""
    class LocalUniqueException(Exception):
        pass

    class AnotherUniqueException(Exception):
        pass

    this_outcome = qtrio.Outcomes(
        qt=outcome.Error(AnotherUniqueException()),
        trio=outcome.Error(LocalUniqueException()),
    )

    with pytest.raises(LocalUniqueException):
        this_outcome.unwrap()
    def callback():
        # Protect against race-conditions
        with condition:
            # Notify the fuse thread/winfsp that the call has started
            condition.notify()
            # Check for cancellation in a thread-safe way
            if cancelled:
                queue.put_nowait(RuntimeError("Cancelled"))
                return

        # Wrapper for the asynchronous function
        # Keyboard interrupt is enabled when using `trio_token.run_sync_soon`,
        # So let's disable it as we're heading back to user code.
        @trio.lowlevel.disable_ki_protection
        async def unprotected_afn():
            return await afn(*args)

        # System task target
        async def await_in_trio_thread_task():
            queue.put_nowait(await outcome.acapture(unprotected_afn))

        # Spawn system task
        try:
            trio.lowlevel.spawn_system_task(await_in_trio_thread_task, name=afn)
        # System nursery is closed
        except RuntimeError:
            queue.put_nowait(outcome.Error(trio.RunFinishedError("system nursery is closed")))
Beispiel #3
0
    async def _run(self) -> None:
        """
        Context manager for this instance.
        """

        # TODO nothing sets this ... yet
        self._close = trio.Event()

        async with trio.open_nursery() as n:
            # fx is the FIFO, opened in read-write mode
            fx = await n.start(self._reader)
            self.main = n
            self._is_running.set()

            try:
                await self._is_connected.wait()
                if self._handlers:
                    async with trio.open_nursery() as nn:
                        for event in self._handlers.keys():
                            nn.start_soon(partial(self.rpc,action="handle", event=event))
                self.do_register_aliases()
                yield self

            finally:
                if fx is not None:
                    os.close(fx)
                for k,v in self._replies.items():
                    if isinstance(v,trio.Event):
                        self._replies[k] = outcome.Error(EOFError())
                        v.set()
                n.cancel_scope.cancel()
                pass # end finally
            pass # end nursery
Beispiel #4
0
    async def __trio_thread_main(self):
        # The non-context-manager equivalent of open_loop()
        async with trio.open_nursery() as nursery:
            asyncio.set_event_loop(self)
            await self._main_loop_init(nursery)
            self.__blocking_result_queue.put(True)

            while not self._closed:
                # This *blocks*
                req = self.__blocking_job_queue.get()
                if req is None:
                    self.stop()
                    break
                async_fn, args = req

                result = await outcome.acapture(async_fn, *args)
                if type(result) == outcome.Error and type(
                        result.error) == trio.Cancelled:
                    res = RuntimeError("Main loop cancelled")
                    res.__cause__ = result.error.__cause__
                    result = outcome.Error(res)
                self.__blocking_result_queue.put(result)
            with trio.CancelScope(shield=True):
                await self._main_loop_exit()
            self.__blocking_result_queue.put(None)
            nursery.cancel_scope.cancel()
Beispiel #5
0
    async def wrapper(asyncfn, *args):
        nonlocal result

        try:
            await asyncfn(*args)
        except Exception as e:
            result = outcome.Error(e)
            event.set()
Beispiel #6
0
def test_outcomes_unwrap_raises_qt_error_over_trio_none():
    """Unwrapping an Outcomes prioritizes a Qt error over a Trio None."""
    class LocalUniqueException(Exception):
        pass

    this_outcome = qtrio.Outcomes(qt=outcome.Error(LocalUniqueException()))
    with pytest.raises(LocalUniqueException):
        this_outcome.unwrap()
Beispiel #7
0
 def notify_closing(self, handle):
     handle = _get_base_socket(handle)
     waiters = self._afd_waiters.get(handle)
     if waiters is not None:
         if waiters.read_task is not None:
             _core.reschedule(
                 waiters.read_task,
                 outcome.Error(_core.ClosedResourceError())
             )
             waiters.read_task = None
         if waiters.write_task is not None:
             _core.reschedule(
                 waiters.write_task,
                 outcome.Error(_core.ClosedResourceError())
             )
             waiters.write_task = None
         self._refresh_afd(handle)
Beispiel #8
0
 def notify_socket_close(self, sock):
     if not isinstance(sock, int):
         sock = sock.fileno()
     for mode in ["read", "write"]:
         if sock in self._socket_waiters[mode]:
             task = self._socket_waiters[mode].pop(sock)
             exc = _core.ClosedResourceError(
                 "another task closed this socket")
             _core.reschedule(task, outcome.Error(exc))
Beispiel #9
0
 async def _second_runner(self, queue: RequestQueue[int, int]) -> None:
     while True:
         many = await queue.get_many()
         for val, coro in many[::-1]:
             try:
                 result = await self.queue.request(val + 100)
             except Exception as e:
                 coro.resume(outcome.Error(e))
             else:
                 coro.resume(outcome.Value(result))
Beispiel #10
0
    def notify_socket_close(self, sock):
        if type(sock) is not stdlib_socket.socket:
            raise TypeError("need a stdlib socket")

        for mode in ["read", "write"]:
            if sock in self._socket_waiters[mode]:
                task = self._socket_waiters[mode].pop(sock)
                exc = _core.ClosedResourceError(
                    "another task closed this socket")
                _core.reschedule(task, outcome.Error(exc))
Beispiel #11
0
 async def _first_runner(self, queue: RequestQueue) -> None:
     while True:
         many = await queue.get_many()
         for val, coro in many[::-1]:
             if val == 1337:
                 try:
                     failing_function(x)  # type: ignore
                 except Exception as e:
                     coro.resume(outcome.Error(e))
             else:
                 coro.resume(outcome.Value(val + 10))
Beispiel #12
0
def outcome_from_application_return_code(return_code: int) -> outcome.Outcome:
    """Create either an :class:`outcome.Value` in the case of a 0 `return_code` or an
    :class:`outcome.Error` with a :class:`ReturnCodeError` otherwise.

    Args:
        return_code: The return code to be processed.
    """

    if return_code == 0:
        return outcome.Value(return_code)

    return outcome.Error(qtrio.ReturnCodeError(return_code))
Beispiel #13
0
    async def consume_next():
        try:
            item = await async_generator.__anext__()
            result = outcome.Value(value=item)
        except StopAsyncIteration:
            result = outcome.Value(value=STOP)
        except asyncio.CancelledError:
            # Once we are cancelled, we do not call reschedule() anymore
            return
        except Exception as e:
            result = outcome.Error(error=e)

        trio.hazmat.reschedule(task, result)
Beispiel #14
0
 async def receiver():
     async with child_recv_chan:
         async for i in child_recv_chan:
             # Just consume all results from the channel until exhausted
             pass
     # And then wrap up the result and push it to the parent channel
     errors = [e.error for e in result_list if isinstance(e, outcome.Error)]
     if len(errors) > 0:
         result = outcome.Error(trio.MultiError(errors))
     else:
         result = outcome.Value([o.unwrap() for o in result_list])
     async with parent_send_chan:
         await parent_send_chan.send(result)
Beispiel #15
0
    def callback(q, afn, args):
        @disable_ki_protection
        async def unprotected_afn():
            coro = coroutine_or_error(afn, *args)
            return await coro

        async def await_in_trio_thread_task():
            q.put_nowait(await outcome.acapture(unprotected_afn))

        try:
            trio.lowlevel.spawn_system_task(await_in_trio_thread_task,
                                            name=afn)
        except RuntimeError:  # system nursery is closed
            q.put_nowait(
                outcome.Error(
                    trio.RunFinishedError("system nursery is closed")))
Beispiel #16
0
def wake_all(waiters, exc):
    try:
        current_task = _core.current_task()
    except RuntimeError:
        current_task = None
    raise_at_end = False
    for attr_name in ["read_task", "write_task"]:
        task = getattr(waiters, attr_name)
        if task is not None:
            if task is current_task:
                raise_at_end = True
            else:
                _core.reschedule(task, outcome.Error(copy.copy(exc)))
            setattr(waiters, attr_name, None)
    if raise_at_end:
        raise exc
Beispiel #17
0
    async def consume_next():
        t = sniffio.current_async_library_cvar.set("asyncio")
        try:
            item = await async_generator.__anext__()
            result = outcome.Value(value=item)
        except StopAsyncIteration:
            result = outcome.Value(value=STOP)
        except asyncio.CancelledError:
            # Once we are cancelled, we do not call reschedule() anymore
            return
        except Exception as e:
            result = outcome.Error(error=e)
        finally:
            sniffio.current_async_library_cvar.reset(t)

        trio.hazmat.reschedule(task, result)
Beispiel #18
0
    def notify_closing(self, fd):
        if not isinstance(fd, int):
            fd = fd.fileno()

        for filter in [select.KQ_FILTER_READ, select.KQ_FILTER_WRITE]:
            key = (fd, filter)
            receiver = self._registered.get(key)

            if receiver is None:
                continue

            if type(receiver) is _core.Task:
                event = select.kevent(fd, filter, select.KQ_EV_DELETE)
                self._kqueue.control([event], 0)
                exc = _core.ClosedResourceError("another task closed this fd")
                _core.reschedule(receiver, outcome.Error(exc))
                del self._registered[key]
            else:
                # XX this is an interesting example of a case where being able
                # to close a queue would be useful...
                raise NotImplementedError(
                    "can't close an fd that monitor_kevent is using")
Beispiel #19
0
 async def _msg_in(self, msg):
     if msg.get("result",()) and msg["result"][0] != "Pong":
         self.__logger.debug("IN %r",msg)
     seq = msg.get("seq",None)
     if seq is not None:
         try:
             ev = self._replies[seq]
         except KeyError:
             self.__logger.warning("Unknown Reply %r",msg)
             return
         else:
             if not isinstance(ev, trio.Event):
                 self.__logger.warning("Dup Reply %r",msg)
                 return
         try:
             self._replies[seq] = outcome.Value(msg["result"])
         except KeyError:
             self._replies[seq] = outcome.Error(RuntimeError(msg.get("error","Unknown error")))
         ev.set()
     else:
         await self._dispatch(msg)
     self.__logger.debug("IN done")
Beispiel #20
0
 def interceptor(underlying_coro):  # type: ignore
     nonlocal result, delivered
     try:
         next_trap = "hello"
         while True:
             trap_result = yield next_trap
             if isinstance(trap_result, outcome.Error):
                 if isinstance(trap_result.error, MonitorKill):
                     delivered = True
             next_trap = underlying_coro.send(trap_result)
             if not delivered:
                 next_trap = underlying_coro.send(
                     outcome.Error(MonitorKill()))
                 delivered = True
     except StopIteration as ex:
         return ex.value
     except MonitorKill as ex:
         result = ex
     except BaseException as ex:
         result = ex
         raise
     finally:
         done.set()
Beispiel #21
0
 async def set_error(self, exc):
     """Set the internal flag value to True, and wake any waiting tasks."""
     self.value = outcome.Error(exc)
     await self.event.set()
Beispiel #22
0
 def interrupt(task):
     exc = _core.ClosedResourceError("another task closed this fd")
     _core.reschedule(task, outcome.Error(exc))
Beispiel #23
0
 async def set_error(self, err):
     await self._q_w.send(outcome.Error(err))
Beispiel #24
0
def _greenback_shim(orig_coro: Coroutine[Any, Any, Any]) -> Generator[Any, Any, Any]:
    # In theory this could be written as a simpler function that uses
    # _greenback_shim_sync():
    #
    #     next_yield = "ready"
    #     while True:
    #         try:
    #             target = partial(orig_coro.send, (yield next_yield))
    #         except BaseException as ex:
    #             target = partial(orig_coro.throw, ex)
    #         try:
    #             next_yield = yield from _greenback_shim_sync(target)
    #         except StopIteration as ex:
    #             return ex.value
    #
    # In practice, this doesn't work that well: _greenback_shim_sync()
    # has a hard time raising StopIteration, because it's a generator,
    # and unrolling it into a non-generator iterable makes it slower.
    # So we'll accept a bit of code duplication.
    parent_greenlet = greenlet.getcurrent()

    # The greenlet in which each send() or throw() call will occur.
    child_greenlet: Optional[greenlet.greenlet] = None

    # The contextvars.Context that we have most recently seen as active
    # for this task and propagated to child_greenlet
    curr_ctx: Optional[contextvars.Context] = None

    # The next thing we plan to yield to the event loop. (The first yield
    # goes to ensure_portal() rather than to the event loop, so we use a
    # string that is unlikely to be a valid event loop trap.)
    next_yield: Any = "ready"

    # The next thing we plan to send to the original coroutine. This is an
    # outcome representing the value or error that the event loop resumed
    # us with.
    next_send: outcome.Outcome[Any]
    while True:
        try:
            # Normally we send to orig_coro whatever the event loop sent us
            next_send = outcome.Value((yield next_yield))
        except BaseException as ex:
            # If the event loop resumed us with an error, we forward that error
            next_send = outcome.Error(ex)
        try:
            if not child_greenlet:
                # Start a new send() or throw() call on the original coroutine.
                child_greenlet = greenlet.greenlet(next_send.send)
                switch_arg: Any = orig_coro
            else:
                # Resume the previous send() or throw() call, which is currently
                # at a simulated yield point in a greenback.await_() call.
                switch_arg = next_send

            if (
                greenlet_needs_context_fixup
                and parent_greenlet.gr_context is not curr_ctx
                and child_greenlet.gr_context is curr_ctx
            ):
                # Make sure the child greenlet's contextvars context
                # is the same as our own, even if our own context
                # changes (such as via trio.Task.context assignment),
                # unless the child greenlet appears to have changed
                # its context privately through a call to Context.run().
                #
                # Note 'parent_greenlet.gr_context' here is just a
                # portable way of getting the current contextvars
                # context, which is not exposed by the contextvars
                # module directly (copy_context() returns a copy, not
                # a new reference to the original).  Upon initial
                # creation of child_greenlet, curr_ctx and
                # child_greenlet.gr_context will both be None, so this
                # condition works for that case too.
                child_greenlet.gr_context = curr_ctx = parent_greenlet.gr_context

            next_yield = child_greenlet.switch(switch_arg)
            if child_greenlet.dead:
                # The send() or throw() call completed so we need to
                # create a new greenlet for the next one.
                child_greenlet = curr_ctx = None
        except StopIteration as ex:
            # The underlying coroutine completed, so we forward its return value.
            return ex.value
Beispiel #25
0
def _greenback_shim_sync(target: Callable[[], Any]) -> Generator[Any, Any, Any]:
    """Run target(), forwarding the event loop traps and responses necessary
    to implement any await_() calls that it makes.

    This is only a little bit faster than using greenback_shim() plus a
    sync-to-async wrapper -- maybe 2us faster for the entire call,
    so it only matters when you're scoping the portal to a very small
    range. We ship it anyway because it's easier to understand than
    the async-compatible _greenback_shim(), and helps with understanding
    the latter.
    """

    parent_greenlet = greenlet.getcurrent()
    curr_ctx = None

    # The greenlet in which we run target().
    child_greenlet = greenlet.greenlet(target)

    # The next thing we plan to yield to the event loop.
    next_yield: Any

    # The next thing we plan to send via greenlet.switch(). This is an
    # outcome representing the value or error that the event loop resumed
    # us with. Initially None for the very first zero-argument switch().
    next_send: Optional[outcome.Outcome[Any]] = None

    while True:
        if (
            greenlet_needs_context_fixup
            and parent_greenlet.gr_context is not curr_ctx
            and child_greenlet.gr_context is curr_ctx
        ):
            # Make sure the child greenlet's contextvars context
            # is the same as our own, even if our own context
            # changes (such as via trio.Task.context assignment),
            # unless the child greenlet appears to have changed
            # its context privately through a call to Context.run().
            #
            # Note 'parent_greenlet.gr_context' here is just a
            # portable way of getting the current contextvars
            # context, which is not exposed by the contextvars
            # module directly (copy_context() returns a copy, not
            # a new reference to the original).  Upon initial
            # creation of child_greenlet, curr_ctx and
            # child_greenlet.gr_context will both be None, so this
            # condition works for that case too.
            child_greenlet.gr_context = curr_ctx = parent_greenlet.gr_context

        if next_send is None:
            next_yield = child_greenlet.switch()
        else:
            next_yield = child_greenlet.switch(next_send)
        if child_greenlet.dead:
            # target() returned, so next_yield is its return value, not an
            # event loop trap. (If it exits with an exception, that exception
            # will propagate out of switch() and thus out of the loop, which
            # is what we want.)
            return next_yield
        try:
            # Normally we send to orig_coro whatever the event loop sent us
            next_send = outcome.Value((yield next_yield))
        except BaseException as ex:
            # If the event loop resumed us with an error, we forward that error
            next_send = outcome.Error(ex)
Beispiel #26
0
def test_outcome_from_application_return_code_error():
    """Non-zero return code result in outcome.Error"""
    result = qtrio._core.outcome_from_application_return_code(return_code=-1)

    assert result == outcome.Error(qtrio.ReturnCodeError(-1))
Beispiel #27
0
 def throw(self, exn: BaseException) -> None:
     return self.resume(outcome.Error(exn))
Beispiel #28
0
 async def set_error(self, exc):
     """Set the result to raise this exceptio, and wake any waiting task.
     """
     self.value = outcome.Error(exc)
     await self.event.set()
Beispiel #29
0
 def socket_check(what, sock):
     try:
         select([sock], [sock], [sock], 0)
     except OSError as exc:
         socket_ready(what, sock, outcome.Error(exc))
Beispiel #30
0
 def resume(self, value: Outcome[SendType]) -> None:
     if self.cancelled:
         # discard the result - not great, obviously...
         logger.debug("TrioContinuation(%s): resumed after cancellation",
                      self.task)
         return
     if self.on_stack:
         logger.debug("TrioContinuation(%s): immediately resumed with %s",
                      self.task, value)
         # This will happen if the function passed to shift immediately resumes the
         # continuation. With trio, we run the function passed to shift on the
         # coroutine that's being suspended. So we can't resume the coroutine here,
         # since it's already running. Instead we'll save the outcome, and in shift()
         # we check saved_send and just return immediately if it's set. This is not
         # normal shift/reset semantics but it's the best we can do with how trio is
         # structured.
         self.saved_send = value
         return
     resuming_task = GLOBAL_RUN_CONTEXT.task
     runner = GLOBAL_RUN_CONTEXT.runner
     logger.debug("TrioContinuation(%s): resuming with %s", self.task,
                  value)
     global _under_coro_runner
     try:
         previous_runner = _under_coro_runner
         _under_coro_runner = Runner.TRIO
         # We have to temporarily set GLOBAL_RUN_CONTEXT.task to the task that is being
         # resumed; after all, that's the task that's really going to be running. This
         # wouldn't be necessary if we had proper dynamically scoped variables in
         # Python :(
         GLOBAL_RUN_CONTEXT.task = self.task
         # a little bit of reschedule(), before we run the task
         self.task._abort_func = None
         self.task.custom_sleep_data = None
         try:
             msg = self.task.context.run(self.task.coro.send, value)
         except StopIteration as exn:
             logger.debug("TrioContinuation(%s): return %s", self.task,
                          exn.value)
             GLOBAL_RUN_CONTEXT.runner.task_exited(self.task,
                                                   outcome.Value(exn.value))
             return
         except BaseException as exn:
             logger.debug("TrioContinuation(%s): raised %s", self.task, exn)
             exn = exn.with_traceback(exn.__traceback__
                                      and exn.__traceback__.tb_next)
             GLOBAL_RUN_CONTEXT.runner.task_exited(self.task,
                                                   outcome.Error(exn))
             return
         logger.debug("TrioContinuation(%s): yield %s", self.task, msg)
     finally:
         _under_coro_runner = previous_runner
         GLOBAL_RUN_CONTEXT.task = resuming_task
     if msg is CancelShieldedCheckpoint:
         runner.reschedule(self.task)
     elif type(msg) is WaitTaskRescheduled:
         self.task._abort_func = msg.abort_func
         if runner.ki_pending and self.task is runner.main_task:
             self.task._attempt_delivery_of_pending_ki()
         self.task._attempt_delivery_of_any_pending_cancel()
     elif type(msg) is PermanentlyDetachCoroutineObject:
         runner.task_exited(self.task, msg.final_outcome)
     else:
         raise TypeError("bad yield from continuation", msg)