spate_core/feed/mod.rs
1//! The work feed: the contract a queue-fed source reads its work through.
2//!
3//! A [`Feed`] carries [`Unit`]s from receipt to acknowledgement. A source
4//! [`receive`](Feed::receive)s units, reads each one, and
5//! [`acknowledge`](Feed::acknowledge)s it under the ack rule below. A lister
6//! or a worker [`publish`](Feed::publish)es units into the same feed and
7//! learns from a [`Confirmation`] when the feed has accepted one.
8//! [`memory::MemoryFeed`] is the in-process feed for a single instance.
9//!
10//! # The ack rule
11//!
12//! Every backend and every source follows it:
13//!
14//! - A worker acknowledges a unit only after it has read the unit to its end
15//! and the sink has acknowledged every record in it.
16//! - A unit read in several parts is acknowledged once, after every part
17//! meets that rule.
18//! - A unit whose input was replaced while it was read may be acknowledged
19//! once a unit naming the input's current state is stored in the feed and
20//! the sink has acknowledged every record read from it. Past the source's
21//! re-enqueue cap it is acknowledged without one, and the source counts it.
22//! - Otherwise, a unit whose read stops before its end leaves the feed
23//! through redelivery, until the feed moves it to its dead-letter
24//! destination.
25//!
26//! A feed delivers a unit at least once. A second delivery of a unit the
27//! worker already holds, under a new [`Receipt`], is read again from its
28//! start. No backend deduplicates by [`Unit::id`], and a source keys the
29//! units it holds by receipt.
30//!
31//! # Threading
32//!
33//! A feed is driven synchronously, so no method waits on I/O. A backend runs
34//! its receives, lease renewals and publishes on a task or thread it owns.
35//! [`receive`](Feed::receive) returns what that task has already buffered,
36//! and the other methods hand their
37//! request to it. The backend signals the [`FeedWaker`] it was given when a
38//! unit becomes ready to receive, and after a `receive` that takes some units
39//! and leaves others, so the source parks on the paired [`FeedPark`] between
40//! polls. A `receive` that takes nothing sends no signal. Methods take
41//! `&self` and may be called from any thread, so one `Arc<dyn Feed>` serves a
42//! reader and a publisher at once.
43//!
44//! # Ending a bounded job
45//!
46//! A backend that can count what it holds answers
47//! [`read_depth`](Feed::read_depth) with a [`DepthReading`], checked like a
48//! [`Confirmation`]. A source ends a bounded job through an [`EndCheck`]: the
49//! feed and the reader must stay empty over a settling window of reads at
50//! most [`END_CHECK_INTERVAL`] apart, and the feed's dead-letter destination
51//! decides between a completed and a stalled job.
52//!
53//! # Stopping
54//!
55//! A backend that [`hands_back`](Feed::hands_back) takes part in a graceful
56//! stop, in this order:
57//!
58//! 1. The source calls [`stop_receiving`](Feed::stop_receiving) when the stop
59//! begins, and reads on the units it holds, renewing them, until each is
60//! read to its end or [`STOP_RESERVE`] before the drain deadline.
61//! 2. The source [`hand_back`](Feed::hand_back)s every unit it has not read
62//! to its end, so another reader can take it at once.
63//! 3. The pipeline drains its sink and acknowledges what the sink has
64//! acknowledged, under the ack rule.
65//! 4. The runtime calls [`close`](Feed::close), which sends what the backend
66//! still has queued and gives up what is held, within the time left before
67//! the deadline.
68//!
69//! A backend that does not hand back skips the reading, and its held units
70//! return through redelivery.
71
72use crate::error::ErrorClass;
73use crate::metrics::Meter;
74use std::fmt;
75use std::future::Future;
76use std::pin::Pin;
77use std::task::{Context, Poll};
78use std::time::Duration;
79use tokio::sync::oneshot;
80
81mod end_check;
82pub mod memory;
83mod metered;
84
85pub use end_check::{END_CHECK_INTERVAL, EndAction, EndCheck, EndOutcome, MarkerRead};
86pub(crate) use metered::{DEAD_LETTER_READ_INTERVAL, DeadLetterSampler, MeteredFeed};
87
88/// The settling window a bounded job waits out when its `feed:` section sets
89/// none: five minutes.
90pub const DEFAULT_SETTLING_WINDOW: Duration = Duration::from_secs(5 * 60);
91
92/// How long before the drain deadline a stopping worker stops reading the
93/// units of a feed that [`hands_back`](Feed::hands_back): five seconds. The
94/// hand-back, the sink drain and the feed's close run in this reserve.
95pub const STOP_RESERVE: Duration = Duration::from_secs(5);
96
97/// One unit of work: an identity and an opaque body the source decodes.
98#[derive(Clone, Debug, PartialEq, Eq)]
99#[non_exhaustive]
100pub struct Unit {
101 /// The unit's name, for logs and dead letters. Two units may share it,
102 /// and no feed deduplicates by it.
103 pub id: String,
104 /// What the source reads to find the unit's input.
105 pub body: Vec<u8>,
106}
107
108impl Unit {
109 /// Build a unit.
110 pub fn new(id: impl Into<String>, body: impl Into<Vec<u8>>) -> Unit {
111 Unit {
112 id: id.into(),
113 body: body.into(),
114 }
115 }
116}
117
118/// Names one delivery of one unit.
119///
120/// The feed mints a new receipt for every delivery, so a redelivery or a
121/// duplicate of a held unit carries a receipt the holder has not seen. A
122/// receipt is valid until the delivery it names is acknowledged, failed or
123/// replaced by a redelivery; a call with it after that returns
124/// [`Gone`](FeedErrorKind::Gone).
125#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
126pub struct Receipt(pub u64);
127
128impl fmt::Display for Receipt {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 write!(f, "receipt {}", self.0)
131 }
132}
133
134/// A unit handed to a reader, with the receipt that names this delivery.
135#[derive(Clone, Debug, PartialEq, Eq)]
136#[non_exhaustive]
137pub struct Delivery {
138 /// The unit delivered.
139 pub unit: Unit,
140 /// The receipt for this delivery, unique within the feed.
141 pub receipt: Receipt,
142 /// How many times the feed has delivered this published unit, this
143 /// delivery included, so `1` on the first. Each publish starts its own
144 /// count, whatever the unit's id.
145 pub deliveries: u32,
146}
147
148impl Delivery {
149 /// Build a delivery. Backends outside this crate construct it here, as the
150 /// struct is `#[non_exhaustive]`.
151 #[must_use]
152 pub fn new(unit: Unit, receipt: Receipt, deliveries: u32) -> Delivery {
153 Delivery {
154 unit,
155 receipt,
156 deliveries,
157 }
158 }
159}
160
161/// What the runtime hands a feed backend for its own metric families, through
162/// [`Feed::attach_metrics`].
163#[derive(Debug)]
164#[non_exhaustive]
165pub struct FeedMetricsCtx {
166 /// Scoped `spate_<backend>_feed_*` and pre-labeled with the pipeline,
167 /// `component="feed"` and the [`backend`](Feed::backend) key as
168 /// `component_type`. `None` when the backend key cannot scope a family: a
169 /// reserved root or `custom` opts out silently, and a malformed key is
170 /// logged and also yields `None`.
171 pub meter: Option<Meter>,
172 /// The calling pipeline's `metrics.per_partition_detail`. Gates the
173 /// backend's own per-partition families: when `false`, register and emit
174 /// only aggregate series. Set whether or not [`meter`](Self::meter) is
175 /// `Some`.
176 pub per_partition_detail: bool,
177}
178
179impl FeedMetricsCtx {
180 /// Context carrying `meter`, with per-partition detail off. Backends
181 /// outside this crate construct it here, as the struct is
182 /// `#[non_exhaustive]`.
183 ///
184 /// # Examples
185 ///
186 /// ```
187 /// use spate_core::feed::FeedMetricsCtx;
188 /// use spate_core::metrics::{Meter, render_local};
189 ///
190 /// let meter = Meter::with_namespace("probe_feed", "orders", "feed", "probe");
191 /// let cx = FeedMetricsCtx::new(Some(meter)).with_partition_detail(true);
192 /// assert!(cx.per_partition_detail);
193 ///
194 /// let meter = cx.meter.expect("a scoped Meter");
195 /// let text = render_local(|| meter.counter("receives_total", &[]).increment(1));
196 /// assert!(text.lines().any(|l| {
197 /// l == r#"spate_probe_feed_receives_total{pipeline="orders",component="feed",component_type="probe"} 1"#
198 /// }));
199 /// ```
200 #[must_use]
201 pub fn new(meter: Option<Meter>) -> Self {
202 FeedMetricsCtx {
203 meter,
204 per_partition_detail: false,
205 }
206 }
207
208 /// Enable cardinality-sensitive per-partition series. Called by the
209 /// runtime from `metrics.per_partition_detail`.
210 #[must_use]
211 pub fn with_partition_detail(mut self, enabled: bool) -> Self {
212 self.per_partition_detail = enabled;
213 self
214 }
215}
216
217/// How a caller must react to a [`FeedError`].
218#[derive(Clone, Copy, Debug, PartialEq, Eq)]
219#[non_exhaustive]
220pub enum FeedErrorKind {
221 /// The receipt no longer names a delivery the caller holds: the unit was
222 /// acknowledged, failed, or delivered again under a new receipt. The call
223 /// changed nothing. The caller lets go of that delivery and does not
224 /// retry.
225 Gone,
226 /// The feed refused the request, and sending it again fails the same way:
227 /// a unit the feed cannot store, such as one over its size limit.
228 Refused,
229 /// Transient: the backend could not take the request now. Retrying may
230 /// succeed.
231 Retryable,
232 /// Unrecoverable: misconfiguration, missing permissions, a feed that no
233 /// longer exists. The pipeline must stop.
234 Fatal,
235}
236
237/// Error from a feed backend.
238#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
239#[error("feed error ({kind:?}): {reason}")]
240#[non_exhaustive]
241pub struct FeedError {
242 /// How the caller must react; see [`FeedErrorKind`].
243 pub kind: FeedErrorKind,
244 /// Human-readable cause.
245 pub reason: String,
246}
247
248impl FeedError {
249 /// Build an error. Backends outside this crate construct it here, as the
250 /// struct is `#[non_exhaustive]`.
251 pub fn new(kind: FeedErrorKind, reason: impl Into<String>) -> FeedError {
252 FeedError {
253 kind,
254 reason: reason.into(),
255 }
256 }
257
258 /// Map to the framework error taxonomy.
259 ///
260 /// [`Gone`](FeedErrorKind::Gone) and [`Refused`](FeedErrorKind::Refused)
261 /// map to [`Fatal`](ErrorClass::Fatal) only as a backstop. A caller
262 /// handles both before classifying, by letting go of the delivery or the
263 /// unit.
264 #[must_use]
265 pub fn class(&self) -> ErrorClass {
266 match self.kind {
267 FeedErrorKind::Retryable => ErrorClass::Retryable,
268 FeedErrorKind::Gone | FeedErrorKind::Refused | FeedErrorKind::Fatal => {
269 ErrorClass::Fatal
270 }
271 }
272 }
273}
274
275/// A queue of work units, read and filled without waiting.
276///
277/// The module docs state the ack rule every implementation and caller
278/// follows. No method waits on I/O: a backend does its I/O on a task it owns
279/// and answers each call from what that task has buffered. `Ok` from
280/// [`renew`](Feed::renew), [`acknowledge`](Feed::acknowledge) and
281/// [`fail`](Feed::fail) means the backend accepted the request. A backend
282/// that cannot apply an accepted request leaves the unit to be delivered
283/// again, which the ack rule allows.
284///
285/// Dyn-compatible; a source holds an `Arc<dyn Feed>`.
286///
287/// ```
288/// use spate_core::feed::{Feed, Unit, memory::MemoryFeed, wake_channel};
289/// use std::sync::Arc;
290/// use std::time::Duration;
291///
292/// let feed: Arc<dyn Feed> = Arc::new(MemoryFeed::new());
293/// let (waker, park) = wake_channel();
294/// feed.set_waker(waker);
295///
296/// let mut confirmation = feed.publish(Unit::new("day-1", b"2026-10-01".to_vec()));
297/// assert!(matches!(confirmation.try_outcome(), Some(Ok(()))));
298///
299/// // The publish woke the reader, so this park returns at once.
300/// assert!(park.park(Duration::from_secs(5)));
301/// let delivery = feed.receive(8).unwrap().remove(0);
302/// assert_eq!(delivery.unit.id, "day-1");
303/// assert_eq!(delivery.deliveries, 1);
304///
305/// // Read the unit to its end; once the sink has acknowledged every record:
306/// feed.acknowledge(delivery.receipt).unwrap();
307/// ```
308pub trait Feed: Send + Sync {
309 /// The key that names this backend in a `feed:` section, such as
310 /// `"memory"`. The runtime refuses a feed whose key differs from the
311 /// one the pipeline's [`FeedSection`](crate::config::FeedSection) names.
312 fn backend(&self) -> &str;
313
314 /// Hand the backend the waker it signals when a unit becomes ready to
315 /// receive, including one that arrived before this call, and after a
316 /// [`receive`](Feed::receive) that takes some units and leaves others. A
317 /// `receive` that takes nothing sends no signal. Called before the first
318 /// `receive`; a later call replaces the waker.
319 fn set_waker(&self, waker: FeedWaker);
320
321 /// Receive the [`Meter`] and per-partition setting for the backend's own
322 /// metric families. Called before the source is handed the feed, once for
323 /// each pipeline that runs it, with that pipeline's labels and that
324 /// pipeline's `metrics.per_partition_detail`. Resolve handles here and keep
325 /// them, which `&self` leaves to interior mutability.
326 ///
327 /// The backend's own tasks until this call, the calls made before it, and
328 /// a feed the runtime is not given all go without a `Meter`, so treat the
329 /// handles as optional wherever they are used. The default ignores it.
330 fn attach_metrics(&self, _cx: FeedMetricsCtx) {}
331
332 /// Up to `max` deliveries the backend has buffered, oldest first. Never
333 /// waits; returns an empty list when nothing is ready.
334 ///
335 /// # Errors
336 ///
337 /// [`Retryable`](FeedErrorKind::Retryable) when the backend's receive
338 /// task is failing transiently, [`Fatal`](FeedErrorKind::Fatal) when it
339 /// cannot continue.
340 fn receive(&self, max: usize) -> Result<Vec<Delivery>, FeedError>;
341
342 /// Mark a held unit as still in work.
343 ///
344 /// The backend extends the unit's lease on its own schedule, and only for
345 /// units marked since its last extension. A caller renews every unit it
346 /// holds from the controller thread, on each `poll_events` and from
347 /// [`Source::renew_held`](crate::source::Source::renew_held) while a
348 /// revocation drains. On a backend that leases units, a
349 /// controller that stops running stops renewing, and its units return to
350 /// the feed for another reader.
351 ///
352 /// # Errors
353 ///
354 /// [`Gone`](FeedErrorKind::Gone) when the receipt is no longer held.
355 fn renew(&self, receipt: Receipt) -> Result<(), FeedError>;
356
357 /// Remove a unit from the feed as done. Only the ack rule decides when a
358 /// caller may send it.
359 ///
360 /// # Errors
361 ///
362 /// [`Gone`](FeedErrorKind::Gone) when the receipt is no longer held:
363 /// the unit was delivered again, or this receipt was already used.
364 /// [`Retryable`](FeedErrorKind::Retryable) when the request may be sent
365 /// again.
366 fn acknowledge(&self, receipt: Receipt) -> Result<(), FeedError>;
367
368 /// Return a held unit that this reader could not process, so the feed
369 /// delivers it again. At the feed's delivery cap the unit moves to its
370 /// dead-letter destination instead. `reason` is for the feed's logs.
371 ///
372 /// # Errors
373 ///
374 /// [`Gone`](FeedErrorKind::Gone) when the receipt is no longer held.
375 /// [`Retryable`](FeedErrorKind::Retryable) when the request may be sent
376 /// again.
377 fn fail(&self, receipt: Receipt, reason: &str) -> Result<(), FeedError>;
378
379 /// Add a unit to the feed. The returned [`Confirmation`] resolves once
380 /// the feed has stored the unit or refused it. A unit published twice is
381 /// delivered twice.
382 fn publish(&self, unit: Unit) -> Confirmation;
383
384 /// Count what the feed, or its dead-letter destination, holds. The
385 /// returned [`DepthReading`] settles once the backend has read the
386 /// counts. Never waits.
387 ///
388 /// `None` means the backend cannot count, and a source that ends a
389 /// bounded job on this feed refuses it. The default returns `None`.
390 fn read_depth(&self, _of: DepthOf) -> Option<DepthReading> {
391 None
392 }
393
394 /// Whether a stopping worker reads this feed's units to the
395 /// [`STOP_RESERVE`] and hands back the rest, as the module docs'
396 /// "Stopping" order describes. Startup refuses a drain timeout no longer
397 /// than the reserve on a feed that answers `true`. The default returns
398 /// `false`.
399 fn hands_back(&self) -> bool {
400 false
401 }
402
403 /// Receive nothing more: the worker is stopping. Units already buffered
404 /// are the backend's to give back. The default does nothing.
405 fn stop_receiving(&self) {}
406
407 /// Return a held unit for another reader to take at once. The
408 /// delivery does not count as a failure; a backend that counts
409 /// deliveries toward a dead-letter limit counts this one as it counts
410 /// any delivery.
411 ///
412 /// The default accepts the call and leaves the unit held, so it returns
413 /// through redelivery; a backend whose positions move only by commit
414 /// keeps it.
415 ///
416 /// # Errors
417 ///
418 /// [`Gone`](FeedErrorKind::Gone) when the receipt is no longer held.
419 /// [`Retryable`](FeedErrorKind::Retryable) when the backend could not
420 /// take the request now; [`close`](Feed::close) gives the unit up.
421 fn hand_back(&self, _receipt: Receipt) -> Result<(), FeedError> {
422 Ok(())
423 }
424
425 /// Send what the backend still has queued and give up every unit still
426 /// held, within `within`. Called once, after the pipeline's final
427 /// commit. Never waits: the returned [`Confirmation`] settles when the
428 /// backend is done, and a caller stops waiting for it after `within`.
429 ///
430 /// `None` means the backend has nothing to close. The default returns
431 /// `None`.
432 fn close(&self, _within: Duration) -> Option<Confirmation> {
433 None
434 }
435}
436
437/// Where the feed handed to
438/// [`Source::configure_feed`](crate::source::Source::configure_feed) came
439/// from.
440#[derive(Clone, Copy, Debug, PartialEq, Eq)]
441#[non_exhaustive]
442pub enum FeedOrigin {
443 /// The runtime built it from the `feed:` section, so only the pipeline
444 /// holds it.
445 Section,
446 /// Code passed it to `Pipeline::feed`, and may keep a handle that
447 /// publishes into it.
448 Given,
449}
450
451/// What a [`read_depth`](Feed::read_depth) counts.
452#[derive(Clone, Copy, Debug, PartialEq, Eq)]
453#[non_exhaustive]
454pub enum DepthOf {
455 /// The units the feed holds for its readers.
456 Feed,
457 /// The units the feed has moved to its dead-letter destination.
458 DeadLetters,
459}
460
461/// The counts one [`read_depth`](Feed::read_depth) found.
462///
463/// Every count is `0` in a feed that holds nothing. A backend whose counts
464/// are estimates sets [`approximate`](Depth::approximate).
465#[derive(Clone, Debug, PartialEq, Eq)]
466#[non_exhaustive]
467pub struct Depth {
468 /// What was read: the queue, topic or list, for logs and errors. Carries
469 /// no credential.
470 pub what: String,
471 /// Units ready to be received.
472 pub visible: u64,
473 /// Units the feed will make visible later.
474 pub delayed: u64,
475 /// Units received by a reader and not yet acknowledged or failed.
476 pub in_flight: u64,
477 /// Acknowledges and fails this instance handed the backend that the
478 /// backend has not yet applied.
479 pub unsent: u64,
480 /// Whether the counts are estimates that can lag the feed.
481 pub approximate: bool,
482}
483
484impl Depth {
485 /// A reading of `what` with every count `0`. Backends outside this crate
486 /// build it here and set the counts, as the struct is
487 /// `#[non_exhaustive]`.
488 #[must_use]
489 pub fn new(what: impl Into<String>, approximate: bool) -> Depth {
490 Depth {
491 what: what.into(),
492 visible: 0,
493 delayed: 0,
494 in_flight: 0,
495 unsent: 0,
496 approximate,
497 }
498 }
499
500 /// Whether every count is `0`.
501 #[must_use]
502 pub fn is_empty(&self) -> bool {
503 // Compared one by one: a sum could wrap to zero.
504 self.visible == 0 && self.delayed == 0 && self.in_flight == 0 && self.unsent == 0
505 }
506}
507
508impl fmt::Display for Depth {
509 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510 write!(
511 f,
512 "{}: {} visible, {} delayed, {} in flight, {} unsent",
513 self.what, self.visible, self.delayed, self.in_flight, self.unsent
514 )?;
515 if self.approximate {
516 f.write_str(" (approximate)")?;
517 }
518 Ok(())
519 }
520}
521
522/// The backend's half of a [`DepthReading`]: settles one read.
523///
524/// Dropping it unsettled resolves the reading as
525/// [`Retryable`](FeedErrorKind::Retryable).
526#[derive(Debug)]
527pub struct DepthReporter(oneshot::Sender<Result<Depth, FeedError>>);
528
529impl DepthReporter {
530 /// Report the counts read, or why they could not be read.
531 pub fn report(self, outcome: Result<Depth, FeedError>) {
532 // A dropped `DepthReading` means nobody is waiting for the outcome.
533 let _ = self.0.send(outcome);
534 }
535}
536
537/// The outcome of one [`read_depth`](Feed::read_depth), once the backend
538/// reports it.
539///
540/// Checked from a synchronous loop with
541/// [`try_outcome`](DepthReading::try_outcome).
542#[derive(Debug)]
543pub struct DepthReading {
544 rx: oneshot::Receiver<Result<Depth, FeedError>>,
545 // The settled outcome, kept so a check after settling repeats it.
546 outcome: Option<Result<Depth, FeedError>>,
547}
548
549impl DepthReading {
550 /// The outcome if the backend has settled the read, `None` while it is
551 /// pending. Never waits. Once settled, every later call returns the same
552 /// outcome.
553 pub fn try_outcome(&mut self) -> Option<Result<Depth, FeedError>> {
554 if self.outcome.is_none() {
555 use oneshot::error::TryRecvError;
556 self.outcome = match self.rx.try_recv() {
557 Ok(outcome) => Some(outcome),
558 Err(TryRecvError::Empty) => None,
559 Err(TryRecvError::Closed) => Some(Err(FeedError::new(
560 FeedErrorKind::Retryable,
561 "the feed dropped a depth read before settling it",
562 ))),
563 };
564 }
565 self.outcome.clone()
566 }
567}
568
569/// A depth read's two halves: the backend settles the [`DepthReporter`], and
570/// the source checks the [`DepthReading`].
571#[must_use]
572pub fn depth_reading() -> (DepthReporter, DepthReading) {
573 let (tx, rx) = oneshot::channel();
574 (DepthReporter(tx), DepthReading { rx, outcome: None })
575}
576
577/// Wakes a reader parked on the paired [`FeedPark`].
578///
579/// Cheap to clone and safe to signal from any thread, because
580/// [`wake`](FeedWaker::wake) never blocks. The channel behind it holds one
581/// slot, so a burst of signals collapses into one wakeup, and a signal sent
582/// while the reader is between its check and its park makes that park
583/// return at once.
584#[derive(Clone, Debug)]
585pub struct FeedWaker(crossbeam_channel::Sender<()>);
586
587impl FeedWaker {
588 /// Wake the reader if it is parked, or make its next park return at once.
589 /// Never blocks.
590 pub fn wake(&self) {
591 // Full slot means a wakeup is already pending; nothing to add.
592 let _ = self.0.try_send(());
593 }
594}
595
596/// The reader's half of [`wake_channel`]: where it waits for its feed.
597#[derive(Debug)]
598pub struct FeedPark {
599 rx: crossbeam_channel::Receiver<()>,
600 // Keeps the channel connected after every `FeedWaker` drops, so `park`
601 // waits out its timeout instead of returning at once in a loop.
602 _tx: crossbeam_channel::Sender<()>,
603}
604
605impl FeedPark {
606 /// Wait until a [`FeedWaker`] signals or `timeout` passes. Returns
607 /// `true` when a signal ended the wait, including one sent before the
608 /// call.
609 pub fn park(&self, timeout: Duration) -> bool {
610 self.rx.recv_timeout(timeout).is_ok()
611 }
612}
613
614/// A waker for a [`Feed`] and the park its reader waits on.
615#[must_use]
616pub fn wake_channel() -> (FeedWaker, FeedPark) {
617 let (tx, rx) = crossbeam_channel::bounded(1);
618 (FeedWaker(tx.clone()), FeedPark { rx, _tx: tx })
619}
620
621/// The backend's half of a [`Confirmation`]: settles one publish or close.
622///
623/// Dropping it unsettled resolves the confirmation as
624/// [`Retryable`](FeedErrorKind::Retryable), since the feed may or may not
625/// have finished.
626#[derive(Debug)]
627pub struct Confirmer(oneshot::Sender<Result<(), FeedError>>);
628
629impl Confirmer {
630 /// Report that the feed has stored the unit, or finished closing.
631 pub fn confirm(self) {
632 // A dropped `Confirmation` means nobody is waiting for the outcome.
633 let _ = self.0.send(Ok(()));
634 }
635
636 /// Report that the feed did not store the unit or close cleanly, and why.
637 pub fn reject(self, error: FeedError) {
638 // A dropped `Confirmation` means nobody is waiting for the outcome.
639 let _ = self.0.send(Err(error));
640 }
641}
642
643/// The outcome of one [`publish`](Feed::publish) or
644/// [`close`](Feed::close), once the feed reports it.
645///
646/// Await it, or check it from a synchronous loop with
647/// [`try_outcome`](Confirmation::try_outcome). `Ok(())` means the feed has
648/// stored the unit, or finished closing.
649#[derive(Debug)]
650pub struct Confirmation {
651 rx: oneshot::Receiver<Result<(), FeedError>>,
652 // The settled outcome, kept so a check after settling repeats it and the
653 // receiver is never polled again once it has yielded.
654 outcome: Option<Result<(), FeedError>>,
655 on_settle: Option<OnSettle>,
656}
657
658/// What a [`Confirmation`] runs on its outcome when it settles.
659struct OnSettle(Box<SettleFn>);
660
661// `Sync` keeps the confirmation `Sync`.
662type SettleFn = dyn FnOnce(&Result<(), FeedError>) + Send + Sync;
663
664impl fmt::Debug for OnSettle {
665 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
666 f.write_str("OnSettle")
667 }
668}
669
670impl Confirmation {
671 /// The outcome if the feed has settled the call, `None` while it is
672 /// pending. Never waits. Once settled, every later call returns the same
673 /// outcome.
674 pub fn try_outcome(&mut self) -> Option<Result<(), FeedError>> {
675 if self.outcome.is_none() {
676 use oneshot::error::TryRecvError;
677 let outcome = match self.rx.try_recv() {
678 Ok(outcome) => outcome,
679 Err(TryRecvError::Empty) => return None,
680 Err(TryRecvError::Closed) => Err(unsettled()),
681 };
682 self.settle(outcome);
683 }
684 self.outcome.clone()
685 }
686
687 /// Run `f` on the outcome once, when a check or an await first finds it
688 /// settled. A confirmation dropped before then never runs it.
689 pub(crate) fn on_settle(
690 mut self,
691 f: impl FnOnce(&Result<(), FeedError>) + Send + Sync + 'static,
692 ) -> Confirmation {
693 self.on_settle = Some(OnSettle(Box::new(f)));
694 self
695 }
696
697 fn settle(&mut self, outcome: Result<(), FeedError>) {
698 if let Some(OnSettle(f)) = self.on_settle.take() {
699 f(&outcome);
700 }
701 self.outcome = Some(outcome);
702 }
703}
704
705impl Future for Confirmation {
706 type Output = Result<(), FeedError>;
707
708 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
709 if let Some(outcome) = &self.outcome {
710 return Poll::Ready(outcome.clone());
711 }
712 let outcome = match Pin::new(&mut self.rx).poll(cx) {
713 Poll::Pending => return Poll::Pending,
714 Poll::Ready(Ok(outcome)) => outcome,
715 Poll::Ready(Err(_)) => Err(unsettled()),
716 };
717 self.settle(outcome.clone());
718 Poll::Ready(outcome)
719 }
720}
721
722/// A publish's or a close's two halves: the backend settles the
723/// [`Confirmer`], and the caller reads the [`Confirmation`].
724#[must_use]
725pub fn confirmation() -> (Confirmer, Confirmation) {
726 let (tx, rx) = oneshot::channel();
727 (
728 Confirmer(tx),
729 Confirmation {
730 rx,
731 outcome: None,
732 on_settle: None,
733 },
734 )
735}
736
737fn unsettled() -> FeedError {
738 FeedError::new(
739 FeedErrorKind::Retryable,
740 "the feed dropped the confirmation before settling it; the publish or close may or may \
741 not have finished",
742 )
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748
749 /// A feed with nothing in it, which only has to compile behind `dyn`.
750 struct EmptyFeed;
751
752 impl Feed for EmptyFeed {
753 fn backend(&self) -> &str {
754 "empty"
755 }
756
757 fn set_waker(&self, _waker: FeedWaker) {}
758
759 fn receive(&self, _max: usize) -> Result<Vec<Delivery>, FeedError> {
760 Ok(vec![])
761 }
762
763 fn renew(&self, receipt: Receipt) -> Result<(), FeedError> {
764 Err(FeedError::new(FeedErrorKind::Gone, receipt.to_string()))
765 }
766
767 fn acknowledge(&self, receipt: Receipt) -> Result<(), FeedError> {
768 Err(FeedError::new(FeedErrorKind::Gone, receipt.to_string()))
769 }
770
771 fn fail(&self, receipt: Receipt, _reason: &str) -> Result<(), FeedError> {
772 Err(FeedError::new(FeedErrorKind::Gone, receipt.to_string()))
773 }
774
775 fn publish(&self, _unit: Unit) -> Confirmation {
776 let (confirmer, confirmation) = confirmation();
777 confirmer.reject(FeedError::new(FeedErrorKind::Refused, "empty feed"));
778 confirmation
779 }
780 }
781
782 /// Pins that a feed metrics context carries no per-partition detail unless
783 /// it is enabled, and carries it once enabled.
784 #[test]
785 fn feed_metrics_ctx_partition_detail_defaults_off_and_round_trips() {
786 let cx = FeedMetricsCtx::new(None);
787 assert!(cx.meter.is_none());
788 assert!(!cx.per_partition_detail);
789 let cx = FeedMetricsCtx::new(None).with_partition_detail(true);
790 assert!(cx.per_partition_detail);
791 }
792
793 /// Pins that `Feed` is dyn-compatible and that one `Arc<dyn Feed>` can be
794 /// shared across threads.
795 #[test]
796 fn feed_is_dyn_compatible() {
797 fn shared<T: Send + Sync>(_: &T) {}
798 let feed: std::sync::Arc<dyn Feed> = std::sync::Arc::new(EmptyFeed);
799 shared(&feed);
800 assert!(feed.receive(1).unwrap().is_empty());
801 let err = feed.acknowledge(Receipt(7)).unwrap_err();
802 assert_eq!(err.kind, FeedErrorKind::Gone);
803 let mut confirmation = feed.publish(Unit::new("u", Vec::new()));
804 assert_eq!(
805 confirmation.try_outcome().unwrap().unwrap_err().kind,
806 FeedErrorKind::Refused
807 );
808 assert!(feed.read_depth(DepthOf::Feed).is_none());
809 assert!(!feed.hands_back());
810 feed.stop_receiving();
811 assert!(feed.hand_back(Receipt(7)).is_ok());
812 assert!(feed.close(Duration::from_secs(1)).is_none());
813 }
814
815 /// Pins that a backend that does not override the stop methods neither
816 /// hands back nor has anything to close, so a stop drains it as before.
817 #[test]
818 fn a_feed_neither_hands_back_nor_closes_by_default() {
819 assert!(!EmptyFeed.hands_back());
820 assert!(EmptyFeed.hand_back(Receipt(1)).is_ok());
821 assert!(EmptyFeed.close(Duration::ZERO).is_none());
822 }
823
824 /// Pins that a backend that does not override `read_depth` reports no
825 /// depth, for the feed and its dead letters alike.
826 #[test]
827 fn a_feed_reports_no_depth_by_default() {
828 assert!(EmptyFeed.read_depth(DepthOf::Feed).is_none());
829 assert!(EmptyFeed.read_depth(DepthOf::DeadLetters).is_none());
830 }
831
832 /// Pins that a depth read whose backend drops the reporter reads as
833 /// Retryable, and that a settled read repeats its outcome.
834 #[test]
835 fn a_depth_reading_settles_once_and_repeats() {
836 let (reporter, mut reading) = depth_reading();
837 assert!(reading.try_outcome().is_none());
838 drop(reporter);
839 let kind = reading.try_outcome().unwrap().unwrap_err().kind;
840 assert_eq!(kind, FeedErrorKind::Retryable);
841
842 let (reporter, mut reading) = depth_reading();
843 let mut depth = Depth::new("q", true);
844 depth.delayed = 2;
845 reporter.report(Ok(depth.clone()));
846 assert_eq!(reading.try_outcome().unwrap().unwrap(), depth);
847 assert_eq!(reading.try_outcome().unwrap().unwrap(), depth);
848 }
849
850 /// Pins that a depth reads as empty only when every count is zero, with
851 /// no sum that could wrap, and how it prints.
852 #[test]
853 fn a_depth_is_empty_only_with_every_count_zero() {
854 let mut depth = Depth::new("q", false);
855 assert!(depth.is_empty());
856 depth.visible = u64::MAX;
857 depth.delayed = 1;
858 assert!(!depth.is_empty());
859 let mut depth = Depth::new("q", true);
860 depth.unsent = 1;
861 assert!(!depth.is_empty());
862 assert_eq!(
863 depth.to_string(),
864 "q: 0 visible, 0 delayed, 0 in flight, 1 unsent (approximate)"
865 );
866 assert_eq!(
867 Depth::new("q", false).to_string(),
868 "q: 0 visible, 0 delayed, 0 in flight, 0 unsent"
869 );
870 }
871
872 /// Pins that a publish whose backend drops the confirmer reads as
873 /// Retryable, both checked and awaited.
874 #[test]
875 fn a_dropped_confirmer_reads_as_retryable() {
876 let (confirmer, mut checked) = confirmation();
877 assert!(checked.try_outcome().is_none());
878 drop(confirmer);
879 let kind = checked.try_outcome().unwrap().unwrap_err().kind;
880 assert_eq!(kind, FeedErrorKind::Retryable);
881
882 let (confirmer, awaited) = confirmation();
883 drop(confirmer);
884 let rt = tokio::runtime::Builder::new_current_thread()
885 .build()
886 .unwrap();
887 let kind = rt.block_on(awaited).unwrap_err().kind;
888 assert_eq!(kind, FeedErrorKind::Retryable);
889 }
890
891 /// Pins that a settled outcome is returned by every later check and by an
892 /// await after the check.
893 #[test]
894 fn a_settled_confirmation_repeats_its_outcome() {
895 let (confirmer, mut confirmation) = confirmation();
896 assert!(confirmation.try_outcome().is_none());
897 confirmer.confirm();
898 assert!(matches!(confirmation.try_outcome(), Some(Ok(()))));
899 assert!(matches!(confirmation.try_outcome(), Some(Ok(()))));
900 let rt = tokio::runtime::Builder::new_current_thread()
901 .build()
902 .unwrap();
903 assert!(rt.block_on(confirmation).is_ok());
904 }
905
906 /// Pins that a wake sent before the park ends it, and that a burst of
907 /// wakes collapses into one.
908 #[test]
909 fn a_wake_before_the_park_ends_it_once() {
910 let (waker, park) = wake_channel();
911 assert!(!park.park(Duration::ZERO));
912 waker.wake();
913 waker.clone().wake();
914 assert!(park.park(Duration::ZERO));
915 assert!(!park.park(Duration::ZERO));
916 }
917
918 /// Pins that a park whose wakers have all dropped still waits out its
919 /// timeout, so a reader that loses its feed does not spin.
920 #[test]
921 fn a_park_with_no_waker_left_waits_out_its_timeout() {
922 let (waker, park) = wake_channel();
923 drop(waker);
924 let timeout = Duration::from_millis(20);
925 let start = std::time::Instant::now();
926 assert!(!park.park(timeout));
927 assert!(
928 start.elapsed() >= timeout,
929 "park returned after {:?}, before its timeout",
930 start.elapsed()
931 );
932 }
933
934 /// Pins the error classes: only Retryable is retryable.
935 #[test]
936 fn only_a_retryable_feed_error_classes_as_retryable() {
937 let class = |kind| FeedError::new(kind, "x").class();
938 assert_eq!(class(FeedErrorKind::Retryable), ErrorClass::Retryable);
939 assert_eq!(class(FeedErrorKind::Gone), ErrorClass::Fatal);
940 assert_eq!(class(FeedErrorKind::Refused), ErrorClass::Fatal);
941 assert_eq!(class(FeedErrorKind::Fatal), ErrorClass::Fatal);
942 }
943
944 /// Pins that a confirmation with a settle hook says so in its debug
945 /// output, since the hook itself has nothing to print.
946 #[test]
947 fn a_settle_hook_shows_in_the_debug_output() {
948 let (_confirmer, confirmation) = confirmation();
949 let hooked = confirmation.on_settle(|_| {});
950 assert!(format!("{hooked:?}").contains("OnSettle"), "{hooked:?}");
951 }
952
953 /// Pins that an awaited outcome is repeated to a later check, though the
954 /// await spent the receiver.
955 #[test]
956 fn an_awaited_confirmation_repeats_its_outcome_to_a_check() {
957 let (confirmer, mut confirmation) = confirmation();
958 confirmer.confirm();
959 let rt = tokio::runtime::Builder::new_current_thread()
960 .build()
961 .unwrap();
962 assert!(rt.block_on(&mut confirmation).is_ok());
963 assert!(matches!(confirmation.try_outcome(), Some(Ok(()))));
964 }
965}