Skip to main content

async_rs/util/
split.rs

1use futures_io::{AsyncRead, AsyncWrite};
2use std::{
3    fmt, io,
4    pin::Pin,
5    sync::{Arc, Mutex},
6    task::{Context, Poll},
7};
8
9/// Split a bidirectional stream into independently-owned read and write halves.
10///
11/// Each half is `Send + 'static` (given the stream is), so the two can be driven by *separate*
12/// futures/tasks — e.g. a reader loop and a writer loop running concurrently — without one holding
13/// a `&mut` to the whole stream.
14///
15/// The halves share the stream behind a lock that is held only for the duration of a single
16/// `poll_read`/`poll_write`/`poll_flush`/`poll_close`. Because reads and writes touch different
17/// directions of the socket they never truly contend on data, only on this short critical section.
18/// This is a runtime-agnostic split over the `AsyncRead + AsyncWrite` traits; a reactor that exposes
19/// an owned, lock-free split of its own stream type can offer that separately.
20pub fn split<S: AsyncRead + AsyncWrite + Unpin>(stream: S) -> (ReadHalf<S>, WriteHalf<S>) {
21    let shared = Arc::new(Mutex::new(stream));
22    (
23        ReadHalf {
24            shared: shared.clone(),
25        },
26        WriteHalf { shared },
27    )
28}
29
30/// The read half produced by [`split`].
31pub struct ReadHalf<S> {
32    shared: Arc<Mutex<S>>,
33}
34
35/// The write half produced by [`split`].
36pub struct WriteHalf<S> {
37    shared: Arc<Mutex<S>>,
38}
39
40impl<S> fmt::Debug for ReadHalf<S> {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.debug_struct("ReadHalf").finish()
43    }
44}
45
46impl<S> fmt::Debug for WriteHalf<S> {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        f.debug_struct("WriteHalf").finish()
49    }
50}
51
52impl<S: AsyncRead + AsyncWrite + Unpin> ReadHalf<S> {
53    /// Reunite the two halves back into the original stream, if they came from the same [`split`].
54    #[must_use]
55    pub fn unsplit(self, write: WriteHalf<S>) -> Option<S> {
56        if Arc::ptr_eq(&self.shared, &write.shared) {
57            drop(write);
58            Arc::try_unwrap(self.shared)
59                .ok()
60                .map(|m| m.into_inner().unwrap_or_else(|e| e.into_inner()))
61        } else {
62            None
63        }
64    }
65}
66
67fn lock<S>(shared: &Arc<Mutex<S>>) -> std::sync::MutexGuard<'_, S> {
68    shared.lock().unwrap_or_else(|e| e.into_inner())
69}
70
71impl<S: AsyncRead + Unpin> AsyncRead for ReadHalf<S> {
72    fn poll_read(
73        self: Pin<&mut Self>,
74        cx: &mut Context<'_>,
75        buf: &mut [u8],
76    ) -> Poll<io::Result<usize>> {
77        Pin::new(&mut *lock(&self.shared)).poll_read(cx, buf)
78    }
79
80    fn poll_read_vectored(
81        self: Pin<&mut Self>,
82        cx: &mut Context<'_>,
83        bufs: &mut [io::IoSliceMut<'_>],
84    ) -> Poll<io::Result<usize>> {
85        Pin::new(&mut *lock(&self.shared)).poll_read_vectored(cx, bufs)
86    }
87}
88
89impl<S: AsyncWrite + Unpin> AsyncWrite for WriteHalf<S> {
90    fn poll_write(
91        self: Pin<&mut Self>,
92        cx: &mut Context<'_>,
93        buf: &[u8],
94    ) -> Poll<io::Result<usize>> {
95        Pin::new(&mut *lock(&self.shared)).poll_write(cx, buf)
96    }
97
98    fn poll_write_vectored(
99        self: Pin<&mut Self>,
100        cx: &mut Context<'_>,
101        bufs: &[io::IoSlice<'_>],
102    ) -> Poll<io::Result<usize>> {
103        Pin::new(&mut *lock(&self.shared)).poll_write_vectored(cx, bufs)
104    }
105
106    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
107        Pin::new(&mut *lock(&self.shared)).poll_flush(cx)
108    }
109
110    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
111        Pin::new(&mut *lock(&self.shared)).poll_close(cx)
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use crate::util::simple_block_on;
119    use std::{
120        collections::VecDeque,
121        future::poll_fn,
122        pin::Pin,
123        task::{Context, Poll},
124    };
125
126    // An in-memory duplex: writes append to an outgoing buffer, reads drain a preloaded incoming
127    // buffer. Enough to exercise that the two halves poll independently.
128    struct Duplex {
129        incoming: VecDeque<u8>,
130        outgoing: Vec<u8>,
131    }
132
133    impl AsyncRead for Duplex {
134        fn poll_read(
135            mut self: Pin<&mut Self>,
136            _cx: &mut Context<'_>,
137            buf: &mut [u8],
138        ) -> Poll<io::Result<usize>> {
139            let n = self.incoming.len().min(buf.len());
140            for slot in buf.iter_mut().take(n) {
141                *slot = self.incoming.pop_front().unwrap();
142            }
143            Poll::Ready(Ok(n))
144        }
145    }
146
147    impl AsyncWrite for Duplex {
148        fn poll_write(
149            mut self: Pin<&mut Self>,
150            _cx: &mut Context<'_>,
151            buf: &[u8],
152        ) -> Poll<io::Result<usize>> {
153            self.outgoing.extend_from_slice(buf);
154            Poll::Ready(Ok(buf.len()))
155        }
156        fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
157            Poll::Ready(Ok(()))
158        }
159        fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
160            Poll::Ready(Ok(()))
161        }
162    }
163
164    #[test]
165    fn split_read_and_write_independently() {
166        let duplex = Duplex {
167            incoming: b"hello".iter().copied().collect(),
168            outgoing: Vec::new(),
169        };
170        let (mut r, mut w) = split(duplex);
171        simple_block_on(async {
172            let n = poll_fn(|cx| Pin::new(&mut w).poll_write(cx, b"world"))
173                .await
174                .unwrap();
175            assert_eq!(n, 5);
176            let mut buf = [0u8; 5];
177            let n = poll_fn(|cx| Pin::new(&mut r).poll_read(cx, &mut buf))
178                .await
179                .unwrap();
180            assert_eq!(n, 5);
181            assert_eq!(&buf, b"hello");
182        });
183        let stream = r.unsplit(w).expect("same split");
184        assert_eq!(stream.outgoing, b"world");
185    }
186
187    #[test]
188    fn unsplit_rejects_foreign_half() {
189        let (r1, _w1) = split(Duplex {
190            incoming: VecDeque::new(),
191            outgoing: Vec::new(),
192        });
193        let (_r2, w2) = split(Duplex {
194            incoming: VecDeque::new(),
195            outgoing: Vec::new(),
196        });
197        assert!(r1.unsplit(w2).is_none());
198    }
199}