Skip to main content

reth_ipc/server/
connection.rs

1//! An IPC connection.
2
3use super::MESSAGE_BUFFER_CAPACITY;
4use crate::stream_codec::StreamCodec;
5use futures::{stream::FuturesUnordered, FutureExt, Sink, Stream};
6use std::{
7    collections::VecDeque,
8    future::Future,
9    io,
10    pin::Pin,
11    task::{Context, Poll},
12};
13use tokio::{
14    io::{AsyncRead, AsyncWrite},
15    sync::mpsc,
16};
17use tokio_util::codec::Framed;
18use tower::Service;
19
20pub(crate) type JsonRpcStream<T> = Framed<T, StreamCodec>;
21
22#[pin_project::pin_project]
23pub(crate) struct IpcConn<T>(#[pin] pub(crate) T);
24
25impl<T> Stream for IpcConn<JsonRpcStream<T>>
26where
27    T: AsyncRead + AsyncWrite,
28{
29    type Item = io::Result<String>;
30
31    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
32        self.project().0.poll_next(cx)
33    }
34}
35
36impl<T> Sink<String> for IpcConn<JsonRpcStream<T>>
37where
38    T: AsyncRead + AsyncWrite,
39{
40    type Error = io::Error;
41
42    fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
43        // NOTE: we always flush here this prevents buffering in the underlying
44        // `Framed` impl that would cause stalled requests
45        self.project().0.poll_flush(cx)
46    }
47
48    fn start_send(self: Pin<&mut Self>, item: String) -> Result<(), Self::Error> {
49        self.project().0.start_send(item)
50    }
51
52    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
53        self.project().0.poll_flush(cx)
54    }
55
56    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
57        self.project().0.poll_close(cx)
58    }
59}
60
61/// Drives an [`IpcConn`] forward.
62///
63/// This forwards received requests from the connection to the service and sends responses to the
64/// connection.
65///
66/// This future terminates when the connection is closed.
67#[pin_project::pin_project]
68#[must_use = "futures do nothing unless you `.await` or poll them"]
69pub(crate) struct IpcConnDriver<T, S, Fut> {
70    #[pin]
71    pub(crate) conn: IpcConn<JsonRpcStream<T>>,
72    pub(crate) service: S,
73    /// rpc requests in progress
74    #[pin]
75    pub(crate) pending_calls: FuturesUnordered<Fut>,
76    pub(crate) items: VecDeque<String>,
77    /// Messages sent outside of calls, such as subscription notifications.
78    pub(crate) messages: mpsc::Receiver<String>,
79}
80
81impl<T, S> Future for IpcConnDriver<T, S, S::Future>
82where
83    S: Service<String, Response = Option<String>> + Send + 'static,
84    S::Error: Into<Box<dyn core::error::Error + Send + Sync>>,
85    S::Future: Send + Unpin,
86    T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
87{
88    type Output = ();
89
90    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
91        let mut this = self.project();
92
93        // items are also pushed from external
94        // this will act as a manual yield point to reduce latencies of the polling future that may
95        // submit items from an additional source (subscription)
96        let mut budget = 5;
97
98        // ensure we still have enough budget for another iteration
99        'outer: loop {
100            budget -= 1;
101            if budget == 0 {
102                // make sure we're woken up again
103                cx.waker().wake_by_ref();
104                return Poll::Pending
105            }
106
107            // Only take messages while the send queue has room, so a client that stops reading
108            // backs up its subscriptions instead of growing the queue.
109            let mut messages_pending = false;
110            while this.items.len() < MESSAGE_BUFFER_CAPACITY {
111                let Poll::Ready(Some(item)) = this.messages.poll_recv(cx) else {
112                    messages_pending = true;
113                    break
114                };
115                this.items.push_back(item);
116            }
117
118            // write all responses to the sink
119            while this.conn.as_mut().poll_ready(cx).is_ready() {
120                if let Some(item) = this.items.pop_front() {
121                    if let Err(err) = this.conn.as_mut().start_send(item) {
122                        tracing::warn!("IPC response failed: {:?}", err);
123                        return Poll::Ready(())
124                    }
125                } else {
126                    break
127                }
128            }
129
130            // A full queue left `messages` unpolled, so it won't wake this task for the messages
131            // that are still waiting. Take them now that the writes made room.
132            if !messages_pending && this.items.len() < MESSAGE_BUFFER_CAPACITY {
133                continue 'outer
134            }
135
136            'inner: loop {
137                // drain all calls that are ready and put them in the output item queue
138                let drained = if this.pending_calls.is_empty() {
139                    false
140                } else {
141                    if let Poll::Ready(Some(res)) = this.pending_calls.as_mut().poll_next(cx) {
142                        let item = match res {
143                            Ok(Some(resp)) => resp,
144                            Ok(None) => continue 'inner,
145                            Err(err) => err.into().to_string(),
146                        };
147                        this.items.push_back(item);
148                        continue 'outer;
149                    }
150                    true
151                };
152
153                // The queue is only full if the socket isn't writable, which wakes this task once
154                // it is. Until then, new requests stay unread.
155                if this.items.len() >= MESSAGE_BUFFER_CAPACITY {
156                    return Poll::Pending
157                }
158
159                // read from the stream
160                match this.conn.as_mut().poll_next(cx) {
161                    Poll::Ready(res) => match res {
162                        Some(Ok(item)) => {
163                            let mut call = this.service.call(item);
164                            match call.poll_unpin(cx) {
165                                Poll::Ready(res) => {
166                                    let item = match res {
167                                        Ok(Some(resp)) => resp,
168                                        Ok(None) => continue 'inner,
169                                        Err(err) => err.into().to_string(),
170                                    };
171                                    this.items.push_back(item);
172                                    continue 'outer
173                                }
174                                Poll::Pending => {
175                                    this.pending_calls.push(call);
176                                }
177                            }
178                        }
179                        Some(Err(err)) => {
180                            // this can happen if the client closes the connection
181                            tracing::debug!("IPC request failed: {:?}", err);
182                            return Poll::Ready(())
183                        }
184                        None => return Poll::Ready(()),
185                    },
186                    Poll::Pending => {
187                        if drained || this.pending_calls.is_empty() {
188                            // at this point all things are pending
189                            return Poll::Pending
190                        }
191                    }
192                }
193            }
194        }
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201    use std::{
202        pin::pin,
203        sync::{
204            atomic::{AtomicBool, Ordering},
205            Arc, Mutex,
206        },
207        task::{Wake, Waker},
208    };
209    use tokio::io::ReadBuf;
210    use tokio_util::codec::Decoder;
211
212    /// Transport of a client that sends nothing and accepts every write unless `blocked`.
213    #[derive(Clone, Default)]
214    struct MockIo(Arc<Mutex<MockState>>);
215
216    #[derive(Default)]
217    struct MockState {
218        blocked: bool,
219        written: Vec<u8>,
220    }
221
222    impl AsyncRead for MockIo {
223        fn poll_read(
224            self: Pin<&mut Self>,
225            _: &mut Context<'_>,
226            _: &mut ReadBuf<'_>,
227        ) -> Poll<io::Result<()>> {
228            Poll::Pending
229        }
230    }
231
232    impl AsyncWrite for MockIo {
233        fn poll_write(
234            self: Pin<&mut Self>,
235            _: &mut Context<'_>,
236            buf: &[u8],
237        ) -> Poll<io::Result<usize>> {
238            let mut state = self.0.lock().unwrap();
239            if state.blocked {
240                return Poll::Pending
241            }
242            state.written.extend_from_slice(buf);
243            Poll::Ready(Ok(buf.len()))
244        }
245
246        fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
247            Poll::Ready(Ok(()))
248        }
249
250        fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
251            Poll::Ready(Ok(()))
252        }
253    }
254
255    /// Service of a connection that receives no requests.
256    struct NoCalls;
257
258    impl Service<String> for NoCalls {
259        type Response = Option<String>;
260        type Error = io::Error;
261        type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
262
263        fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
264            Poll::Ready(Ok(()))
265        }
266
267        fn call(&mut self, _: String) -> Self::Future {
268            std::future::ready(Ok(None))
269        }
270    }
271
272    /// Records whether the driver woke itself.
273    #[derive(Default)]
274    struct Woken(AtomicBool);
275
276    impl Wake for Woken {
277        fn wake(self: Arc<Self>) {
278            self.0.store(true, Ordering::Relaxed);
279        }
280    }
281
282    /// Polls the driver until it is pending without having woken itself.
283    fn poll_until_parked(mut driver: Pin<&mut impl Future<Output = ()>>) {
284        let woken = Arc::new(Woken::default());
285        let waker = Waker::from(woken.clone());
286        let mut cx = Context::from_waker(&waker);
287        loop {
288            woken.0.store(false, Ordering::Relaxed);
289            assert!(driver.as_mut().poll(&mut cx).is_pending());
290            if !woken.0.load(Ordering::Relaxed) {
291                return
292            }
293        }
294    }
295
296    #[test]
297    fn takes_waiting_messages_after_draining_full_queue() {
298        let io = MockIo::default();
299        io.0.lock().unwrap().blocked = true;
300        let (tx, rx) = mpsc::channel(MESSAGE_BUFFER_CAPACITY);
301        let mut driver = pin!(IpcConnDriver {
302            conn: IpcConn(StreamCodec::stream_incoming().framed(io.clone())),
303            service: NoCalls,
304            pending_calls: Default::default(),
305            items: Default::default(),
306            messages: rx,
307        });
308
309        // The client doesn't read, so the send queue fills up and messages wait in the channel.
310        let mut sent = 0;
311        loop {
312            while tx.try_send(format!("<{sent}>")).is_ok() {
313                sent += 1;
314            }
315            poll_until_parked(driver.as_mut());
316            if tx.capacity() == 0 {
317                break
318            }
319        }
320        assert!(io.0.lock().unwrap().written.is_empty());
321
322        // The client reads again, fast enough that the whole queue is written at once. The
323        // messages that waited in the channel must follow without anything else waking the driver.
324        io.0.lock().unwrap().blocked = false;
325        poll_until_parked(driver.as_mut());
326
327        let written = String::from_utf8(io.0.lock().unwrap().written.clone()).unwrap();
328        assert_eq!(written.matches('<').count(), sent);
329    }
330}