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)
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()
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)
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
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
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
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
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
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
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()
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})
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
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
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()
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
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()
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)
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)
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()