Skip to main content

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}