Example #1
0
    async def acquire(self, force_fresh=False):
        """
        Raises:
            BackendConnectionError
            trio.ClosedResourceError: if used after having being closed
        """
        async with self._lock:
            transport = None
            if not force_fresh:
                try:
                    # Fifo style to retrieve oldest first
                    transport = self._transports.pop(0)
                except IndexError:
                    pass

            if not transport:
                if self._closed:
                    raise trio.ClosedResourceError()

                transport = await self._connect_cb()

            try:
                yield transport

            except TransportClosedByPeer:
                raise

            except Exception:
                await transport.aclose()
                raise

            else:
                self._transports.append(transport)
Example #2
0
    def close(self):
        """Close this receive channel object synchronously.

        All channel objects have an asynchronous `~.AsyncResource.aclose` method.
        Memory channels can also be closed synchronously. This has the same
        effect on the channel and other tasks using it, but `close` is not a
        trio checkpoint. This simplifies cleaning up in cancelled tasks.

        Using ``with receive_channel:`` will close the channel object on
        leaving the with block.

        """
        if self._closed:
            return
        self._closed = True
        for task in self._tasks:
            trio.lowlevel.reschedule(task, Error(trio.ClosedResourceError()))
            del self._state.receive_tasks[task]
        self._tasks.clear()
        self._state.open_receive_channels -= 1
        if self._state.open_receive_channels == 0:
            assert not self._state.receive_tasks
            for task in self._state.send_tasks:
                task.custom_sleep_data._tasks.remove(task)
                trio.lowlevel.reschedule(task,
                                         Error(trio.BrokenResourceError()))
            self._state.send_tasks.clear()
            self._state.data.clear()
Example #3
0
    async def acquire(self, force_fresh=False):
        async with self._lock:
            transport = None
            if not force_fresh:
                try:
                    transport = self.transports.pop()
                except IndexError:
                    pass

            if not transport:
                if self._closed:
                    raise trio.ClosedResourceError()

                transport = await _connect(self.addr, self.device_id,
                                           self.signing_key)
                transport.logger = transport.logger.bind(
                    device_id=self.device_id)

            try:
                yield transport

            except TransportClosedByPeer:
                raise

            except Exception:
                await transport.aclose()
                raise

            else:
                self.transports.append(transport)
Example #4
0
    async def receive_some(self, max_bytes=None) -> bytes:
        with self._receive_conflict_detector:
            if max_bytes is None:
                max_bytes = DEFAULT_RECEIVE_SIZE
            else:
                if not isinstance(max_bytes, int):
                    raise TypeError("max_bytes must be integer >= 1")
                if max_bytes < 1:
                    raise ValueError("max_bytes must be integer >= 1")

            await trio.lowlevel.checkpoint()
            while True:
                try:
                    data = os.read(self._fd_holder.fd, max_bytes)
                except BlockingIOError:
                    await trio.lowlevel.wait_readable(self._fd_holder.fd)
                except OSError as e:
                    if e.errno == errno.EBADF:
                        raise trio.ClosedResourceError(
                            "file was already closed") from None
                    else:
                        raise trio.BrokenResourceError from e
                else:
                    break

            return data
Example #5
0
    async def receive_some(self, max_bytes: int) -> bytes:
        with self._conflict_detector:
            if not isinstance(max_bytes, int):
                raise TypeError("max_bytes must be integer >= 1")

            if max_bytes < 1:
                raise ValueError("max_bytes must be integer >= 1")

            await trio.hazmat.checkpoint()
            while True:
                try:
                    data = os.read(self._fd_holder.fd, max_bytes)
                except BlockingIOError:
                    await trio.hazmat.wait_readable(self._fd_holder.fd)
                except OSError as e:
                    if e.errno == errno.EBADF:
                        raise trio.ClosedResourceError(
                            "this pipe was closed"
                        ) from None
                    else:
                        raise trio.BrokenResourceError from e
                else:
                    break

            return data
Example #6
0
 async def send_all(self, data):
     if self.socket.did_shutdown_SHUT_WR:
         raise trio.ClosedResourceError("can't send data after sending EOF")
     with self._send_conflict_detector:
         with _translate_socket_errors_to_stream_errors():
             with memoryview(data) as data:
                 if not data:
                     if self.socket.fileno() == -1:
                         raise trio.ClosedResourceError(
                             "socket was already closed")
                     await trio.lowlevel.checkpoint()
                     return
                 total_sent = 0
                 while total_sent < len(data):
                     with data[total_sent:] as remaining:
                         sent = await self.socket.send(remaining)
                     total_sent += sent
    async def _receive_some_into(self, buffer):
        if self._handle_holder.closed:  # pragma: no cover, never closed in this lib
            raise trio.ClosedResourceError("this pipe is already closed")
        try:
            return await trio.lowlevel.readinto_overlapped(
                self._handle_holder.handle, buffer)
        except BrokenPipeError:
            if self._handle_holder.closed:  # pragma: no cover, never closed in this lib
                raise trio.ClosedResourceError(
                    "another task closed this pipe") from None

            # Windows raises BrokenPipeError on one end of a pipe
            # whenever the other end closes, regardless of direction.
            # Convert this to EndOfChannel.
            #
            # We are raising an exception so we don't need to checkpoint,
            # in contrast to PipeReceiveStream.
            raise trio.EndOfChannel
Example #8
0
def _translate_socket_errors_to_stream_errors():
    try:
        yield
    except OSError as exc:
        if exc.errno in _closed_stream_errnos:
            raise trio.ClosedResourceError(
                "this socket was already closed") from None
        else:
            raise trio.BrokenResourceError(
                "socket connection broken: {}".format(exc)) from exc
Example #9
0
 async def wait_send_all_might_not_block(self) -> None:
     with self._conflict_detector:
         if self._fd_holder.closed:
             raise trio.ClosedResourceError("this pipe was already closed")
         try:
             await trio.hazmat.wait_writable(self._fd_holder.fd)
         except BrokenPipeError as e:
             # kqueue: raises EPIPE on wait_writable instead
             # of sending, which is annoying
             raise trio.BrokenResourceError from e
Example #10
0
def _translate_socket_errors_to_stream_errors():
    try:
        yield
    except OSError as exc:
        if exc.errno in {errno.EBADF, errno.ENOTSOCK}:
            # EBADF on Unix, ENOTSOCK on Windows
            raise trio.ClosedResourceError(
                "this socket was already closed") from None
        else:
            raise trio.BrokenResourceError(
                "socket connection broken: {}".format(exc)) from exc
Example #11
0
async def coro_read_exactly(stream: trio.abc.ReceiveStream, num_bytes: int) -> bytes:
    buffer = io.BytesIO()
    bytes_remaining = num_bytes
    while bytes_remaining > 0:
        data = await stream.receive_some(bytes_remaining)
        if data == b"":
            raise trio.ClosedResourceError("Encuntered end of stream")
        buffer.write(data)
        bytes_remaining -= len(data)

    return buffer.getvalue()
Example #12
0
    async def send(self, data: Any) -> None:
        '''
        Send a message over this stream to the far end.

        '''
        if self._ctx._error:
            raise self._ctx._error  # from None

        if self._closed:
            raise trio.ClosedResourceError('This stream was already closed')

        await self._ctx.chan.send({'yield': data, 'cid': self._ctx.cid})
Example #13
0
 async def _read_messages(self):
     async with trio.open_nursery() as n:
         while True:
             try:
                 msg = await self._transport.read()
                 n.start_soon(self._dispatch_message, msg)
             except Exception as e:
                 if isinstance(e, NoDataError):
                     e = trio.ClosedResourceError(str(e))
                 self._set_error(e)
                 self._scope.cancel()
                 break
Example #14
0
    async def send_all(self, data: bytes):
        async with self._conflict_detector:
            # have to check up front, because send_all(b"") on a closed pipe
            # should raise
            if self._fd_holder.closed:
                raise trio.ClosedResourceError("this pipe was already closed")

            length = len(data)
            # adapted from the SocketStream code
            with memoryview(data) as view:
                sent = 0
                while sent < length:
                    with view[sent:] as remaining:
                        try:
                            sent += os.write(self._fd_holder.fd, remaining)
                        except BlockingIOError:
                            await trio.hazmat.wait_writable(self._fd_holder.fd)
                        except OSError as e:
                            if e.errno == errno.EBADF:
                                raise trio.ClosedResourceError(
                                    "this pipe was closed") from None
                            else:
                                raise trio.BrokenResourceError from e
Example #15
0
    async def _shutdown(self) -> None:
        error = self._set_error(trio.ClosedResourceError())

        if self._outbox_receiver is not None:
            while True:
                try:
                    _msg, result, _needs_response = await self._outbox_receiver.receive_nowait(
                    )
                    result.fail(error)
                except trio.WouldBlock:
                    break
            await self._outbox_receiver.aclose()

        self.close()
Example #16
0
    async def _write_messages(self):
        while True:
            msg, result, needs_response = await self._outbox_receiver.receive()

            try:
                await self._transport.write(msg)
                if not needs_response:
                    result.set()
            except trio.Cancelled:
                result.fail(self._set_error(trio.ClosedResourceError()))
                raise
            except Exception as e:
                result.fail(self._set_error(e))
                self._scope.cancel()
                break
Example #17
0
 async def aclose(self):
     if self._closed:
         await trio.hazmat.checkpoint()
         return
     self._closed = True
     for task in self._tasks:
         trio.hazmat.reschedule(task, Error(trio.ClosedResourceError()))
         del self._state.send_tasks[task]
     self._tasks.clear()
     self._state.open_send_channels -= 1
     if self._state.open_send_channels == 0:
         assert not self._state.send_tasks
         for task in self._state.receive_tasks:
             task.custom_sleep_data._tasks.remove(task)
             trio.hazmat.reschedule(task, Error(trio.EndOfChannel()))
         self._state.receive_tasks.clear()
     await trio.hazmat.checkpoint()
Example #18
0
    async def _send_data(self, data: bytes, fds):
        if self.socket.did_shutdown_SHUT_WR:
            raise trio.ClosedResourceError("can't send data after sending EOF")

        with _translate_socket_errors_to_stream_errors():
            if self._leftover_to_send:
                # A previous message was partly sent - finish sending it now.
                await self._send_remainder(self._leftover_to_send)

            with memoryview(data) as data:
                if fds:
                    sent = await self.socket.sendmsg([data], [
                        (trio.socket.SOL_SOCKET, trio.socket.SCM_RIGHTS, fds)
                    ])
                else:
                    sent = await self.socket.send(data)

                await self._send_remainder(data, sent)
Example #19
0
    async def open_stream(
        self,
        backpressure: Optional[bool] = True,
        msg_buffer_size: Optional[int] = None,
    ) -> AsyncGenerator[MsgStream, None]:
        '''
        Open a ``MsgStream``, a bi-directional stream connected to the
        cross-actor (far end) task for this ``Context``.

        This context manager must be entered on both the caller and
        callee for the stream to logically be considered "connected".

        A ``MsgStream`` is currently "one-shot" use, meaning if you
        close it you can not "re-open" it for streaming and instead you
        must re-establish a new surrounding ``Context`` using
        ``Portal.open_context()``.  In the future this may change but
        currently there seems to be no obvious reason to support
        "re-opening":
            - pausing a stream can be done with a message.
            - task errors will normally require a restart of the entire
              scope of the inter-actor task context due to the nature of
              ``trio``'s cancellation system.

        '''
        actor = current_actor()

        # here we create a mem chan that corresponds to the
        # far end caller / callee.

        # Likewise if the surrounding context has been cancelled we error here
        # since it likely means the surrounding block was exited or
        # killed

        if self._cancel_called:
            task = trio.lowlevel.current_task().name
            raise ContextCancelled(
                f'Context around {actor.uid[0]}:{task} was already cancelled!')

        if not self._portal and not self._started_called:
            raise RuntimeError(
                'Context.started()` must be called before opening a stream')

        # NOTE: in one way streaming this only happens on the
        # caller side inside `Actor.start_remote_task()` so if you try
        # to send a stop from the caller to the callee in the
        # single-direction-stream case you'll get a lookup error
        # currently.
        ctx = actor.get_context(
            self.chan,
            self.cid,
            msg_buffer_size=msg_buffer_size,
        )
        ctx._backpressure = backpressure
        assert ctx is self

        # XXX: If the underlying channel feeder receive mem chan has
        # been closed then likely client code has already exited
        # a ``.open_stream()`` block prior or there was some other
        # unanticipated error or cancellation from ``trio``.

        if ctx._recv_chan._closed:
            raise trio.ClosedResourceError(
                'The underlying channel for this stream was already closed!?')

        async with MsgStream(
                ctx=self,
                rx_chan=ctx._recv_chan,
        ) as rchan:

            if self._portal:
                self._portal._streams.add(rchan)

            try:
                self._stream_opened = True

                # ensure we aren't cancelled before delivering
                # the stream
                # await trio.lowlevel.checkpoint()
                yield rchan

                # XXX: Make the stream "one-shot use".  On exit, signal
                # ``trio.EndOfChannel``/``StopAsyncIteration`` to the
                # far end.
                await self.send_stop()

            finally:
                if self._portal:
                    self._portal._streams.remove(rchan)
Example #20
0
 async def _check_not_closed(self):
     await trio.sleep(0)
     if self._error is not None:
         raise self._error
     if self._outbox_sender is None:
         raise trio.ClosedResourceError()