Skip to main content

protocol_droid/application/bridge/
unsolicited.rs

1//! Frames a peer sent that no call in flight was waiting for.
2//!
3//! Two of this crate's transports put replies somewhere other than the response
4//! to the request that asked for them — `2024-11-05` answers `202` and streams
5//! the answer later, and a `stdio` server may push a notification between the
6//! write and the read. A peer that reads until it finds its own id therefore
7//! walks straight past frames that belong to nobody.
8//!
9//! What it must not do is drop them. The crate's promise is that an unmodelled
10//! directive reaches the other side byte-for-byte, and a notification that
11//! happened to land mid-call is real traffic that somebody downstream is
12//! waiting for. So a peer that skips a frame parks it here, and the caller
13//! decides where it goes next.
14//!
15//! ## Why this lives in `application` and not in each adapter
16//!
17//! Both peers want it, and the honest factoring for something two adapters want
18//! is the layer they both already depend on. Putting it here also keeps the
19//! decision in one place: *a frame the bridge could not attribute is never
20//! discarded* is a property of the bridge, not of HTTP.
21
22use std::collections::VecDeque;
23use std::future::Future;
24use std::pin::Pin;
25use std::sync::{Arc, Mutex};
26use std::task::{Context, Poll, Waker};
27
28use serde_json::{Map, Value};
29
30/// A queue of frames the peer pushed while some other call was in flight.
31///
32/// Draining rather than cloning: the frames are the peer's, and a caller that
33/// polls in a loop should get each one exactly once.
34#[derive(Debug, Default)]
35pub struct Unsolicited {
36    /// The frames and the wakers together, under one lock.
37    ///
38    /// Together because "is the queue empty, and if so park me" has to be one
39    /// atomic step. With the queue and the waker list under separate locks a
40    /// frame could land between the check and the park, and the waker that
41    /// would have been told about it is the one just stored.
42    inner: Arc<Mutex<Inner>>,
43}
44
45#[derive(Debug, Default)]
46struct Inner {
47    frames: VecDeque<Map<String, Value>>,
48    wakers: Vec<Waker>,
49}
50
51/// Resolves once something has been parked in an [`Unsolicited`].
52#[derive(Debug)]
53struct Pending {
54    inner: Arc<Mutex<Inner>>,
55}
56
57impl Future for Pending {
58    type Output = ();
59
60    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
61        let Ok(mut inner) = self.inner.lock() else {
62            // A poisoned lock means the queue is unreadable, so waiting on it
63            // is waiting forever. Resolving lets the caller drain, see
64            // nothing, and carry on — which beats hanging.
65            return Poll::Ready(());
66        };
67        if !inner.frames.is_empty() {
68            return Poll::Ready(());
69        }
70        if !inner.wakers.iter().any(|held| held.will_wake(cx.waker())) {
71            inner.wakers.push(cx.waker().clone());
72        }
73        Poll::Pending
74    }
75}
76
77impl Unsolicited {
78    /// An empty queue.
79    #[must_use]
80    pub fn new() -> Self {
81        Self {
82            inner: Arc::new(Mutex::new(Inner::default())),
83        }
84    }
85
86    /// Parks a frame that no call was waiting for.
87    ///
88    /// A poisoned lock is a frame not parked. It is dropped rather than
89    /// reported because the caller is in the middle of a call and has no way to
90    /// act on a queue fault; the alternative — panicking — would take the
91    /// bridge down over a bookkeeping problem.
92    pub fn hold(&self, frame: Map<String, Value>) {
93        let mut wakers = Vec::new();
94        {
95            let Ok(mut inner) = self.inner.lock() else {
96                return;
97            };
98            inner.frames.push_back(frame);
99            wakers.append(&mut inner.wakers);
100        }
101        // Woken outside the lock: a woken task polled straight away takes this
102        // lock, and holding it across `wake` is how a wake-up becomes a stall.
103        for waker in wakers {
104            waker.wake();
105        }
106    }
107
108    /// Takes everything held so far, leaving the queue empty.
109    #[must_use]
110    pub fn take(&self) -> Vec<Map<String, Value>> {
111        self.inner
112            .lock()
113            .map_or_else(|_| Vec::new(), |mut inner| inner.frames.drain(..).collect())
114    }
115
116    /// Resolves once something has been parked here.
117    ///
118    /// A queue that is already non-empty resolves on the first poll, which is
119    /// what stops a caller parking a frame between its own `take` and its first
120    /// poll from waiting for a wake-up that has already happened.
121    ///
122    /// No `#[must_use]` here: the returned future is itself `#[must_use]`, so
123    /// a second copy of the attribute adds nothing this signature does not
124    /// already say.
125    pub fn wait(&self) -> impl Future<Output = ()> + Send + 'static {
126        Pending {
127            inner: Arc::clone(&self.inner),
128        }
129    }
130
131    /// How many frames are waiting.
132    #[must_use]
133    pub fn len(&self) -> usize {
134        self.inner.lock().map_or(0, |inner| inner.frames.len())
135    }
136
137    /// Whether nothing is waiting.
138    #[must_use]
139    pub fn is_empty(&self) -> bool {
140        self.len() == 0
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use serde_json::{Map, Value, json};
147
148    use super::Unsolicited;
149
150    fn frame(id: i64) -> Map<String, Value> {
151        json!({"jsonrpc": "2.0", "id": id})
152            .as_object()
153            .cloned()
154            .unwrap_or_default()
155    }
156
157    /// A held frame comes back, in order, exactly once.
158    #[test]
159    fn a_held_frame_comes_back_exactly_once() {
160        let held = Unsolicited::new();
161        held.hold(frame(1));
162        held.hold(frame(2));
163        assert_eq!(held.len(), 2);
164
165        let first = held.take();
166        assert_eq!(first.len(), 2);
167        assert_eq!(first.first().and_then(|f| f.get("id")), Some(&json!(1)));
168        assert_eq!(first.get(1).and_then(|f| f.get("id")), Some(&json!(2)));
169
170        // Draining, not cloning: a second poll must not hand them back.
171        assert!(held.take().is_empty());
172        assert!(held.is_empty());
173    }
174
175    /// A queue nobody wrote to reports itself empty rather than guessing.
176    #[test]
177    fn a_fresh_queue_is_empty() {
178        let held = Unsolicited::new();
179        assert!(held.is_empty());
180        assert_eq!(held.len(), 0);
181        assert!(held.take().is_empty());
182    }
183}