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}