melin_server_runtime/early_reply.rs
1//! The answers a reader decides on without publishing the request, on
2//! their way to the response stage.
3//!
4//! # Early replies
5//!
6//! Four answers need nothing from the pipeline: a write refused while the
7//! node is halted (the application's rejection, see [`crate::halt`]), a
8//! request the application's decoder filters (an empty batch) or refuses
9//! (a `Refused` frame naming why), and a request shed because the input
10//! ring is full (`ServerBusy`). All of them reach the response stage
11//! through one queue, from the reader (or the DPDK poll thread) that
12//! decided them, and every one names its request's ID.
13//!
14//! # Released between replies, in no order
15//!
16//! Every final frame names its request, so an early reply does not wait
17//! for the replies to requests sent before it: a refusal goes out while
18//! a write ahead of it is still held by the ack policy. The one rule left
19//! is that it never splits another request's reply, so the response stage
20//! sends early replies only between two events' replies, which a
21//! `BatchBoundary` tracker vouches for with an `AtBoundary` token (both
22//! crate-internal, in `batch_boundary.rs`).
23//!
24//! The response stage works from a snapshot of the queue: it takes one,
25//! then drains its control channel, and only then sends what the snapshot
26//! holds (`EarlyReplyQueue::register`). In that order because an early
27//! reply must never reach the stage ahead of its connection's
28//! registration: the reader answers a connection only once its
29//! `Connected` is queued, so a snapshot taken before the drain holds no
30//! reply for a connection the drain does not register. The stage takes a
31//! snapshot once per iteration, after reading the output ring, and again
32//! on each turn of a wait on the durability gate that holds an event's
33//! first slot. One per iteration is all the steady state pays: no poll per
34//! event.
35//!
36//! # Counted
37//!
38//! An early reply's request leaves no other trace — nothing is journaled
39//! — so the reader counts each kind once per receive, once its answer is
40//! queued (a request parked on is counted when it is finally answered),
41//! and the health endpoint exports the counts: halt refusals through
42//! [`HaltGate::record_refused`](crate::halt::HaltGate::record_refused) as
43//! `melin_writes_refused_total`; shed, filtered and refused requests
44//! through [`IngressMetrics`](melin_transport_core::health::IngressMetrics)
45//! as `melin_requests_shed_total`, `melin_requests_filtered_total` and
46//! `melin_requests_refused_total` (by kind), beside the connections
47//! parked on a full queue.
48
49use melin_pipeline::spsc;
50use melin_pipeline::wait::WaitStrategy;
51use melin_transport_core::pipeline::RequestId;
52use melin_wire_protocol::control::RefusalKind;
53
54use crate::batch_boundary::AtBoundary;
55
56/// Early replies in flight from a reader to the response stage before the
57/// reader can queue no more: the capacity a node gives
58/// [`early_reply_channel`].
59///
60/// The response stage drains the queue between every two events' replies,
61/// and while the ack policy holds an event's first slot, so it fills only
62/// if the stage stops draining (an event whose slots it is waiting on, or
63/// a stage that is not running), not because a write is held. Past it the
64/// reader parks the connection whose answer has nowhere to go, and stops
65/// reading it until the queue has room. A power of two, as the ring
66/// requires; each entry is the application's rejection, or a refusal's
67/// reason, plus a few words.
68pub const EARLY_REPLY_QUEUE_CAPACITY: usize = 4096;
69
70/// An answer a reader decided on without publishing the request, on its
71/// way to the response stage.
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
73pub struct EarlyReply<R> {
74 /// Connection the request arrived on.
75 pub connection_id: u64,
76 /// The client's ID for the request, echoed on the reply's final frame.
77 pub request_id: RequestId,
78 /// What the answer is.
79 pub kind: EarlyKind<R>,
80}
81
82/// What an [`EarlyReply`] answers, and so the frames it is sent as.
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub enum EarlyKind<R> {
85 /// A write refused while the node is halted: the application's
86 /// rejection, then `BatchEnd`.
87 Rejected(R),
88 /// A request the application's decoder filters: an empty batch, a
89 /// `BatchEnd` alone.
90 Filtered,
91 /// A request the application's decoder refuses: a `Refused` frame with
92 /// the decoder's reason.
93 Refused {
94 kind: RefusalKind,
95 reason: &'static str,
96 },
97 /// A request shed because the input ring is full: `ServerBusy`.
98 Busy,
99}
100
101/// Creates the queue a reader hands early replies to the response stage
102/// through, holding at most `capacity` replies in flight: a node passes
103/// [`EARLY_REPLY_QUEUE_CAPACITY`]; a test that wants the queue to fill
104/// passes less.
105///
106/// # Panics
107///
108/// If `capacity` is not a power of two of at least 2, as the ring
109/// requires.
110pub fn early_reply_channel<R: Copy>(capacity: usize) -> (EarlyReplySender<R>, EarlyReplyQueue<R>) {
111 // `Option` because the ring wants `Default` slots and an application's
112 // report need not have one. The wait strategy is unused: the reader
113 // never blocks on this queue (see `EarlyReplySender::try_send`).
114 let (tx, rx) = spsc::channel::<Option<EarlyReply<R>>>(capacity, WaitStrategy::SpinThenYield);
115 (EarlyReplySender { tx }, EarlyReplyQueue { rx })
116}
117
118/// The reader's end of the early-reply queue.
119pub struct EarlyReplySender<R> {
120 tx: spsc::Producer<Option<EarlyReply<R>>>,
121}
122
123impl<R: Copy> EarlyReplySender<R> {
124 /// Queue an early reply, invisible to the response stage until
125 /// [`flush`](Self::flush). Never blocks: a full queue is `Err`, and the
126 /// caller parks the connection whose answer has nowhere to go.
127 pub(crate) fn try_send(&mut self, reply: EarlyReply<R>) -> Result<(), spsc::Full> {
128 self.tx
129 .try_push_with(|slot| *slot = Some(reply))
130 .map(|_| ())
131 }
132
133 /// Make every queued early reply visible to the response stage. The
134 /// reader calls it once on every way out of processing a receive
135 /// (`process_client_frames`), so no reply it decided is left unseen;
136 /// where that falls relative to the input ring's commit does not
137 /// matter, since an early reply waits for nothing published.
138 pub(crate) fn flush(&mut self) {
139 self.tx.flush();
140 }
141}
142
143/// The response stage's end of the early-reply queue.
144///
145/// Public only so it can be handed to a response stage: it has no method
146/// outside the crate, so nothing else can read it. Read only through the
147/// crate's `register`, which takes a snapshot of
148/// what the reader has made visible and then drains the stage's control
149/// channel, and hands back the token that sends the snapshot's replies.
150/// Those made visible since wait for the next snapshot.
151pub struct EarlyReplyQueue<R> {
152 rx: spsc::Consumer<Option<EarlyReply<R>>>,
153}
154
155impl<R: Copy> EarlyReplyQueue<R> {
156 /// Take a snapshot of the early replies the reader has made visible,
157 /// then run `drain_control`, the stage's drain of its control channel,
158 /// and return the token that sends the snapshot's replies. One load of
159 /// the queue's cursor.
160 ///
161 /// The one place the two happen, in that order, so a reply sent
162 /// through the token always finds its connection registered: the
163 /// reader answers a connection only once its `Connected` is queued,
164 /// so every reply in a snapshot taken before the drain belongs to a
165 /// connection the drain registers.
166 #[inline]
167 pub(crate) fn register(&mut self, drain_control: impl FnOnce()) -> Registered<'_, R> {
168 let mut registered = Registered { queue: self };
169 registered.reregister(drain_control);
170 registered
171 }
172}
173
174/// A snapshot of the early-reply queue taken before the response stage's
175/// control channel was drained (see [`EarlyReplyQueue::register`]), and so
176/// the right to send what it holds. Borrows the queue: nothing reads it
177/// past the token's back.
178pub(crate) struct Registered<'q, R> {
179 queue: &'q mut EarlyReplyQueue<R>,
180}
181
182impl<R: Copy> Registered<'_, R> {
183 /// Take a fresh snapshot, then run `drain_control`, as
184 /// [`EarlyReplyQueue::register`] does: for a stage that keeps the token
185 /// across a wait (on the durability gate) and wants what the reader
186 /// decided meanwhile.
187 #[inline]
188 pub(crate) fn reregister(&mut self, drain_control: impl FnOnce()) {
189 self.queue.rx.refresh();
190 drain_control();
191 }
192
193 /// Hand `emit` every early reply in the snapshot, oldest first, and
194 /// return how many.
195 ///
196 /// `AtBoundary` is the stage's word that it is between two events'
197 /// replies: an early reply sent anywhere else would split one.
198 #[inline]
199 pub(crate) fn drain(
200 &mut self,
201 _between_events: &AtBoundary<'_>,
202 mut emit: impl FnMut(EarlyReply<R>),
203 ) -> usize {
204 let mut sent = 0;
205 // Only `try_send` writes the ring, and it never writes `None`.
206 // `try_consume_visible` never reads the queue's cursor, so this
207 // cannot see past the snapshot.
208 while let Some((_, Some(reply))) = self.queue.rx.try_consume_visible() {
209 emit(reply);
210 sent += 1;
211 }
212 sent
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use crate::batch_boundary::BatchBoundary;
220
221 fn refusal(connection_id: u64) -> EarlyReply<u64> {
222 EarlyReply {
223 connection_id,
224 request_id: RequestId(connection_id * 10),
225 kind: EarlyKind::Rejected(connection_id * 100),
226 }
227 }
228
229 /// Every early reply `registered` holds, by connection, drained
230 /// between two events.
231 fn drained(registered: &mut Registered<'_, u64>) -> Vec<u64> {
232 let boundary = BatchBoundary::new();
233 let at = boundary
234 .at_boundary()
235 .expect("a fresh tracker is between events");
236 let mut out = Vec::new();
237 let count = registered.drain(&at, |reply| out.push(reply.connection_id));
238 assert_eq!(count, out.len(), "the count is of what was handed over");
239 out
240 }
241
242 /// A reply is seen only once the reader has flushed it and a snapshot
243 /// has been taken since; a snapshot drained is not drained twice.
244 #[test]
245 fn a_refusal_waits_for_both_the_flush_and_a_snapshot() {
246 let (mut tx, mut rx) = early_reply_channel::<u64>(EARLY_REPLY_QUEUE_CAPACITY);
247 tx.try_send(refusal(1)).unwrap();
248 let mut registered = rx.register(|| {});
249 assert!(drained(&mut registered).is_empty(), "not flushed");
250
251 tx.flush();
252 assert!(
253 drained(&mut registered).is_empty(),
254 "flushed, but after the snapshot"
255 );
256
257 registered.reregister(|| {});
258 assert_eq!(drained(&mut registered), [1]);
259 assert!(drained(&mut registered).is_empty(), "drained once");
260 registered.reregister(|| {});
261 assert!(drained(&mut registered).is_empty(), "the queue is empty");
262 }
263
264 /// The snapshot is taken before the control channel is drained, so a
265 /// reply made visible during the drain (a reply for a connection whose
266 /// `Connected` the drain may already be past) waits for the next
267 /// snapshot, which a drain follows again. On both ways to take one.
268 #[test]
269 fn a_snapshot_is_taken_before_the_control_drain() {
270 let (mut tx, mut rx) = early_reply_channel::<u64>(EARLY_REPLY_QUEUE_CAPACITY);
271 tx.try_send(refusal(1)).unwrap();
272 tx.flush();
273 let mut drains = 0;
274 let mut registered = rx.register(|| {
275 drains += 1;
276 tx.try_send(refusal(2)).unwrap();
277 tx.flush();
278 });
279 assert_eq!(drains, 1, "registering drains the control channel once");
280 assert_eq!(drained(&mut registered), [1], "the snapshot predates 2");
281
282 registered.reregister(|| {
283 drains += 1;
284 tx.try_send(refusal(3)).unwrap();
285 tx.flush();
286 });
287 assert_eq!(drains, 2);
288 assert_eq!(drained(&mut registered), [2], "the snapshot predates 3");
289
290 let mut registered = rx.register(|| drains += 1);
291 assert_eq!(drained(&mut registered), [3]);
292 assert_eq!(drains, 3);
293 }
294
295 #[test]
296 fn a_full_queue_is_an_error_not_a_wait() {
297 let (mut tx, _rx) = early_reply_channel::<u64>(EARLY_REPLY_QUEUE_CAPACITY);
298 for _ in 0..EARLY_REPLY_QUEUE_CAPACITY {
299 tx.try_send(refusal(1)).unwrap();
300 }
301 assert!(tx.try_send(refusal(1)).is_err());
302 }
303
304 /// The capacity is the caller's: a queue built smaller fills at its
305 /// own size.
306 #[test]
307 fn a_queue_holds_the_capacity_it_was_built_with() {
308 for capacity in [2, 8] {
309 let (mut tx, _rx) = early_reply_channel::<u64>(capacity);
310 for _ in 0..capacity {
311 tx.try_send(refusal(1)).unwrap();
312 }
313 assert!(tx.try_send(refusal(1)).is_err(), "{capacity}");
314 }
315 }
316
317 /// A drained queue has room again: capacity bounds replies in flight,
318 /// not replies over the queue's life.
319 #[test]
320 fn a_drained_queue_takes_more() {
321 let (mut tx, mut rx) = early_reply_channel::<u64>(EARLY_REPLY_QUEUE_CAPACITY);
322 for round in 0..3u64 {
323 for _ in 0..EARLY_REPLY_QUEUE_CAPACITY {
324 tx.try_send(refusal(round)).unwrap();
325 }
326 tx.flush();
327 let mut registered = rx.register(|| {});
328 assert_eq!(
329 drained(&mut registered).len(),
330 EARLY_REPLY_QUEUE_CAPACITY,
331 "round {round}"
332 );
333 }
334 }
335
336 /// Every kind of early reply goes through the queue as it was sent,
337 /// with its request's ID, in order among the others.
338 #[test]
339 fn every_kind_travels_with_its_request_id() {
340 let (mut tx, mut rx) = early_reply_channel::<u64>(EARLY_REPLY_QUEUE_CAPACITY);
341 let kinds = [
342 EarlyKind::Rejected(7),
343 EarlyKind::Filtered,
344 EarlyKind::Refused {
345 kind: RefusalKind::Malformed,
346 reason: "bad",
347 },
348 EarlyKind::Busy,
349 ];
350 for (n, kind) in (0u64..).zip(kinds) {
351 tx.try_send(EarlyReply {
352 connection_id: 1,
353 request_id: RequestId(100 + n),
354 kind,
355 })
356 .unwrap();
357 }
358 tx.flush();
359 let boundary = BatchBoundary::new();
360 let at = boundary.at_boundary().expect("between events");
361 let mut released = Vec::new();
362 rx.register(|| {})
363 .drain(&at, |r| released.push((r.request_id, r.kind)));
364 assert_eq!(
365 released,
366 (100..).map(RequestId).zip(kinds).collect::<Vec<_>>()
367 );
368 }
369}