//! Clonable duplex byte streams that implement `futures_io` traits. //! //! `erl_dist` requires `AsyncRead + AsyncWrite + Unpin + Clone` on the carrier //! (smol's `TcpStream` is clonable; iroh bi-streams and tokio-rustls streams are //! not). We wrap any split read/write half in a shared duplex. //! //! Layout: `async_dup::Arc>` — Arc is `Clone`, and //! both Arc and Mutex forward futures-io traits when the inner type does. use async_dup::{Arc as DupArc, Mutex as DupMutex}; use futures::io::{AsyncRead, AsyncWrite}; use std::io; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::{AsyncRead as TokioRead, AsyncWrite as TokioWrite, ReadBuf}; /// A clonable futures-io duplex built from independent tokio read/write halves. #[derive(Clone)] pub struct DistStream { inner: DupArc>, } /// Type-erased tokio read half. type DynRead = Box; /// Type-erased tokio write half. type DynWrite = Box; struct Combined { read: DynRead, write: DynWrite, } impl DistStream { /// Wrap independent tokio async read/write halves. pub fn from_split(read: R, write: W) -> Self where R: TokioRead + Unpin + Send + 'static, W: TokioWrite + Unpin + Send + 'static, { Self { inner: DupArc::new(DupMutex::new(Combined { read: Box::new(read), write: Box::new(write), })), } } /// Wrap a single tokio type that is both `AsyncRead` and `AsyncWrite`. pub fn from_rw(stream: T) -> Self where T: TokioRead + TokioWrite + Unpin + Send + 'static, { let (read, write) = tokio::io::split(stream); Self::from_split(read, write) } } impl TokioRead for Combined { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { Pin::new(&mut self.read).poll_read(cx, buf) } } impl TokioWrite for Combined { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut self.write).poll_write(cx, buf) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.write).poll_flush(cx) } fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.write).poll_shutdown(cx) } } // Bridge tokio traits → futures_io for erl_dist. impl AsyncRead for Combined { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let mut read_buf = ReadBuf::new(buf); match TokioRead::poll_read(self, cx, &mut read_buf) { Poll::Ready(Ok(())) => Poll::Ready(Ok(read_buf.filled().len())), Poll::Ready(Err(e)) => Poll::Ready(Err(e)), Poll::Pending => Poll::Pending, } } } impl AsyncWrite for Combined { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { TokioWrite::poll_write(self, cx, buf) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { TokioWrite::poll_flush(self, cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { TokioWrite::poll_shutdown(self, cx) } } impl AsyncRead for DistStream { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { Pin::new(&mut self.inner).poll_read(cx, buf) } } impl AsyncWrite for DistStream { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut self.inner).poll_write(cx, buf) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.inner).poll_flush(cx) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.inner).poll_close(cx) } } impl std::fmt::Debug for DistStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DistStream").finish_non_exhaustive() } }