Skip to main content

spate_core/feed/
memory.rs

1//! The in-process feed: a [`Feed`] for one pipeline instance.
2//!
3//! [`MemoryFeed`] keeps its units in memory and nothing across a restart. It
4//! has no lease: a received unit stays held until it is acknowledged or
5//! failed, so it is delivered again only after a [`fail`](Feed::fail). It
6//! counts every delivery of a unit and moves a unit failed at its attempt cap,
7//! the tenth delivery unless [`with_attempt_cap`](MemoryFeed::with_attempt_cap)
8//! or a section's `attempt_cap` sets another, to an in-memory dead-letter list.
9//! Its [`counts`](MemoryFeed::counts) are exact, and so is the depth it
10//! reports through [`read_depth`](Feed::read_depth).
11
12use super::{
13    Confirmation, Delivery, Depth, DepthOf, DepthReading, Feed, FeedError, FeedErrorKind,
14    FeedWaker, Receipt, Unit, confirmation, depth_reading,
15};
16use crate::config::{ConfigError, FeedSection};
17use crate::hash::FastMap;
18use serde::Deserialize;
19use std::collections::VecDeque;
20use std::num::NonZeroU32;
21use std::sync::{Arc, Mutex, MutexGuard};
22
23/// Deliveries after which a failed unit is dead-lettered, unless set.
24const ATTEMPT_CAP: NonZeroU32 = NonZeroU32::new(10).expect("non-zero");
25
26/// The key that names this backend in a `feed:` section.
27const BACKEND: &str = "memory";
28
29/// What a [`MemoryFeed`] holds at one moment.
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
31#[non_exhaustive]
32pub struct FeedCounts {
33    /// Units waiting to be received.
34    pub visible: u64,
35    /// Units received and not yet acknowledged or failed.
36    pub held: u64,
37    /// Units moved to the dead-letter list.
38    pub dead_lettered: u64,
39    /// Units acknowledged as done.
40    pub acknowledged: u64,
41}
42
43/// A [`Feed`] held in this process, for a single instance.
44///
45/// Clones share one feed, so a lister and a reader can each hold one. Units
46/// are delivered in the order they were published, and a failed unit goes
47/// to the back of the queue.
48#[derive(Clone, Debug)]
49pub struct MemoryFeed {
50    state: Arc<Mutex<State>>,
51}
52
53impl Default for MemoryFeed {
54    fn default() -> MemoryFeed {
55        MemoryFeed::with_attempt_cap(ATTEMPT_CAP)
56    }
57}
58
59#[derive(Debug)]
60struct State {
61    /// Units waiting to be received, with the deliveries each has had.
62    visible: VecDeque<(Unit, u32)>,
63    held: FastMap<Receipt, (Unit, u32)>,
64    dead: Vec<Unit>,
65    acknowledged: u64,
66    next_receipt: u64,
67    waker: Option<FeedWaker>,
68    /// The delivery on which a failed unit is dead-lettered.
69    attempt_cap: u32,
70}
71
72impl State {
73    fn wake(&self) {
74        if let Some(waker) = &self.waker {
75            waker.wake();
76        }
77    }
78}
79
80impl MemoryFeed {
81    /// An empty feed that dead-letters a unit failed on its tenth delivery.
82    #[must_use]
83    pub fn new() -> MemoryFeed {
84        MemoryFeed::default()
85    }
86
87    /// An empty feed that dead-letters a unit failed on delivery `cap`.
88    #[must_use]
89    pub fn with_attempt_cap(cap: NonZeroU32) -> MemoryFeed {
90        MemoryFeed {
91            state: Arc::new(Mutex::new(State {
92                visible: VecDeque::new(),
93                held: FastMap::default(),
94                dead: Vec::new(),
95                acknowledged: 0,
96                next_receipt: 0,
97                waker: None,
98                attempt_cap: cap.get(),
99            })),
100        }
101    }
102
103    /// An empty feed built from a `feed: { memory: {} }` section, with the
104    /// section's `attempt_cap`, else 10.
105    ///
106    /// # Errors
107    ///
108    /// [`ConfigError::Component`] when the section names another backend,
109    /// when the `memory` body holds a key, or when the section sets
110    /// `location` or `settling_window`.
111    ///
112    /// # Examples
113    ///
114    /// ```
115    /// use spate_core::config::{ComponentConfig, FeedSection, YamlValue};
116    /// use spate_core::feed::memory::MemoryFeed;
117    /// use std::num::NonZeroU32;
118    ///
119    /// let body = YamlValue::Mapping(Default::default());
120    /// let mut section = FeedSection::new(ComponentConfig::new("memory", body));
121    /// section.attempt_cap = NonZeroU32::new(3);
122    ///
123    /// let feed = MemoryFeed::from_section(&section).unwrap();
124    /// assert_eq!(feed.counts().visible, 0);
125    /// ```
126    pub fn from_section(section: &FeedSection) -> Result<MemoryFeed, ConfigError> {
127        // An empty body: every key the in-process feed takes is a common one.
128        #[derive(Deserialize)]
129        #[serde(deny_unknown_fields)]
130        struct Body {}
131
132        let backend = section.backend();
133        if backend.type_tag() != BACKEND {
134            return Err(ConfigError::Component {
135                context: "feed".into(),
136                message: format!(
137                    "the in-process feed is built from `feed: {{ {BACKEND}: {{}} }}`, but the \
138                     section names `{}`",
139                    backend.type_tag()
140                ),
141            });
142        }
143        let _: Option<Body> = backend.deserialize_into()?;
144        if section.location.is_some() {
145            return Err(ConfigError::Component {
146                context: "feed".into(),
147                message: "the in-process feed keeps no state outside the process, so it takes \
148                          no `location`"
149                    .into(),
150            });
151        }
152        if section.settling_window.is_some() {
153            return Err(ConfigError::Component {
154                context: "feed".into(),
155                message: "the in-process feed counts its units exactly, so it takes no \
156                          `settling_window`"
157                    .into(),
158            });
159        }
160        Ok(MemoryFeed::with_attempt_cap(
161            section.attempt_cap.unwrap_or(ATTEMPT_CAP),
162        ))
163    }
164
165    /// The units this feed holds, by state.
166    #[must_use]
167    pub fn counts(&self) -> FeedCounts {
168        let state = self.lock();
169        FeedCounts {
170            visible: state.visible.len() as u64,
171            held: state.held.len() as u64,
172            dead_lettered: state.dead.len() as u64,
173            acknowledged: state.acknowledged,
174        }
175    }
176
177    /// The units moved to the dead-letter list, in the order they moved.
178    #[must_use]
179    pub fn dead_letters(&self) -> Vec<Unit> {
180        self.lock().dead.clone()
181    }
182
183    fn lock(&self) -> MutexGuard<'_, State> {
184        // Nothing panics while holding the lock, so a poisoned lock still
185        // guards consistent state.
186        self.state
187            .lock()
188            .unwrap_or_else(std::sync::PoisonError::into_inner)
189    }
190}
191
192fn gone(receipt: Receipt) -> FeedError {
193    FeedError::new(
194        FeedErrorKind::Gone,
195        format!("{receipt} is not held by the in-process feed"),
196    )
197}
198
199impl Feed for MemoryFeed {
200    fn backend(&self) -> &str {
201        BACKEND
202    }
203
204    fn set_waker(&self, waker: FeedWaker) {
205        let mut state = self.lock();
206        // A unit published before the waker arrived still needs a wakeup.
207        if !state.visible.is_empty() {
208            waker.wake();
209        }
210        state.waker = Some(waker);
211    }
212
213    fn receive(&self, max: usize) -> Result<Vec<Delivery>, FeedError> {
214        let mut state = self.lock();
215        let take = max.min(state.visible.len());
216        let mut out = Vec::with_capacity(take);
217        for _ in 0..take {
218            let Some((unit, deliveries)) = state.visible.pop_front() else {
219                break;
220            };
221            state.next_receipt += 1;
222            let receipt = Receipt(state.next_receipt);
223            let deliveries = deliveries + 1;
224            state.held.insert(receipt, (unit.clone(), deliveries));
225            out.push(Delivery::new(unit, receipt, deliveries));
226        }
227        // A receive that took nothing sends no signal, or a reader at
228        // capacity calling `receive(0)` would never park.
229        if take > 0 && !state.visible.is_empty() {
230            state.wake();
231        }
232        Ok(out)
233    }
234
235    fn renew(&self, receipt: Receipt) -> Result<(), FeedError> {
236        if self.lock().held.contains_key(&receipt) {
237            Ok(())
238        } else {
239            Err(gone(receipt))
240        }
241    }
242
243    fn acknowledge(&self, receipt: Receipt) -> Result<(), FeedError> {
244        let mut state = self.lock();
245        if state.held.remove(&receipt).is_none() {
246            return Err(gone(receipt));
247        }
248        state.acknowledged += 1;
249        Ok(())
250    }
251
252    fn fail(&self, receipt: Receipt, reason: &str) -> Result<(), FeedError> {
253        let mut state = self.lock();
254        let Some((unit, deliveries)) = state.held.remove(&receipt) else {
255            return Err(gone(receipt));
256        };
257        if deliveries >= state.attempt_cap {
258            let id = unit.id.clone();
259            state.dead.push(unit);
260            // Logged after the guard drops: a subscriber that panics must not
261            // unwind through the lock.
262            drop(state);
263            tracing::warn!(
264                unit = %id,
265                deliveries,
266                reason,
267                "in-process feed dead-lettered a unit at its delivery cap"
268            );
269        } else {
270            state.visible.push_back((unit, deliveries));
271            state.wake();
272        }
273        Ok(())
274    }
275
276    fn publish(&self, unit: Unit) -> Confirmation {
277        let mut state = self.lock();
278        state.visible.push_back((unit, 0));
279        state.wake();
280        let (confirmer, confirmation) = confirmation();
281        confirmer.confirm();
282        confirmation
283    }
284
285    fn read_depth(&self, of: DepthOf) -> Option<DepthReading> {
286        let state = self.lock();
287        let depth = match of {
288            DepthOf::DeadLetters => {
289                let mut depth = Depth::new("the in-process dead-letter list", false);
290                depth.visible = state.dead.len() as u64;
291                depth
292            }
293            DepthOf::Feed => {
294                let mut depth = Depth::new("the in-process feed", false);
295                depth.visible = state.visible.len() as u64;
296                depth.in_flight = state.held.len() as u64;
297                depth
298            }
299        };
300        drop(state);
301        let (reporter, reading) = depth_reading();
302        reporter.report(Ok(depth));
303        Some(reading)
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use crate::feed::wake_channel;
311    use std::time::Duration;
312
313    fn unit(id: &str) -> Unit {
314        Unit::new(id, id.as_bytes().to_vec())
315    }
316
317    /// Receive the next unit, which must be `id`.
318    fn receive_one_of(feed: &MemoryFeed, id: &str) -> Delivery {
319        let delivery = feed.receive(1).unwrap().remove(0);
320        assert_eq!(delivery.unit.id, id);
321        delivery
322    }
323
324    fn receive_one(feed: &MemoryFeed) -> Delivery {
325        let mut got = feed.receive(usize::MAX).unwrap();
326        assert_eq!(got.len(), 1, "expected exactly one delivery, got {got:?}");
327        got.remove(0)
328    }
329
330    /// Pins the attempt cap: a unit failed on deliveries 1 through 9 is
331    /// delivered again with its count raised by one, and one failed on its
332    /// tenth delivery moves to the dead-letter list and is never delivered
333    /// again.
334    #[test]
335    fn a_unit_at_the_cap_moves_to_the_in_memory_dead_letter_list() {
336        let feed = MemoryFeed::new();
337        drop(feed.publish(unit("poison")));
338        // The cap is written out: a loop bound read from the constant would
339        // follow a changed cap.
340        for i in 1..=10 {
341            let delivery = receive_one(&feed);
342            assert_eq!(delivery.deliveries, i, "delivery count on attempt {i}");
343            assert_eq!(delivery.unit, unit("poison"));
344            feed.fail(delivery.receipt, "unreadable").unwrap();
345            if i < 10 {
346                assert_eq!(feed.counts().visible, 1, "re-queued after attempt {i}");
347                assert_eq!(feed.counts().dead_lettered, 0);
348            }
349        }
350        assert_eq!(
351            feed.counts(),
352            FeedCounts {
353                visible: 0,
354                held: 0,
355                dead_lettered: 1,
356                acknowledged: 0,
357            }
358        );
359        assert_eq!(feed.dead_letters(), vec![unit("poison")]);
360        assert!(feed.receive(usize::MAX).unwrap().is_empty());
361    }
362
363    /// Pins that the attempt cap a section sets is the delivery that
364    /// dead-letters a failed unit: with `attempt_cap: 3`, failures on
365    /// deliveries 1 and 2 re-queue the unit and the failure on delivery 3
366    /// moves it to the dead-letter list.
367    #[test]
368    fn the_attempt_cap_sets_the_delivery_that_dead_letters() {
369        let section: FeedSection =
370            serde_yaml::from_str("memory: {}\nattempt_cap: 3\n").expect("section");
371        let feed = MemoryFeed::from_section(&section).unwrap();
372        drop(feed.publish(unit("poison")));
373        for i in 1..=2 {
374            let delivery = receive_one(&feed);
375            assert_eq!(delivery.deliveries, i);
376            feed.fail(delivery.receipt, "unreadable").unwrap();
377            assert_eq!(feed.counts().visible, 1, "re-queued after attempt {i}");
378        }
379        let third = receive_one(&feed);
380        assert_eq!(third.deliveries, 3);
381        feed.fail(third.receipt, "unreadable").unwrap();
382        assert_eq!(feed.counts().dead_lettered, 1, "dead-lettered on attempt 3");
383        assert_eq!(feed.counts().visible, 0);
384    }
385
386    /// Pins that `from_section` refuses a `location`, which a caller can set
387    /// on a section built in code, as the `feed` component error its
388    /// `# Errors` names, without printing the value.
389    #[test]
390    fn from_section_refuses_a_location() {
391        let mut section: FeedSection = serde_yaml::from_str("memory: {}\n").expect("section");
392        section.location = Some("s3://u:hunter2@ops/jobs/a/?X-Amz-Signature=hunter3".to_owned());
393        let err = MemoryFeed::from_section(&section).unwrap_err();
394        assert!(
395            matches!(&err, ConfigError::Component { context, message }
396                if context == "feed" && message.contains("`location`")),
397            "{err:?}"
398        );
399        for shown in [err.to_string(), format!("{err:?}")] {
400            assert!(
401                !shown.contains("hunter2") && !shown.contains("ops/jobs/a"),
402                "the location must not print: {shown}"
403            );
404        }
405    }
406
407    /// Pins that `from_section` refuses a `settling_window`, which a caller
408    /// can set on a section built in code, as the `feed` component error its
409    /// `# Errors` names.
410    #[test]
411    fn from_section_refuses_a_settling_window() {
412        let mut section: FeedSection = serde_yaml::from_str("memory: {}\n").expect("section");
413        section.settling_window = Some(Duration::from_secs(60));
414        let err = MemoryFeed::from_section(&section).unwrap_err();
415        assert!(
416            matches!(&err, ConfigError::Component { context, message }
417                if context == "feed" && message.contains("`settling_window`")),
418            "{err:?}"
419        );
420    }
421
422    /// Pins that the depth the feed reports agrees with its counts, each
423    /// count told apart by a different number: two visible units, one held
424    /// as in flight, and three on the dead-letter list, exact and settled at
425    /// once.
426    #[test]
427    fn the_depth_a_memory_feed_reports_matches_its_counts() {
428        let feed = MemoryFeed::with_attempt_cap(NonZeroU32::new(1).expect("non-zero"));
429        let read = |of| {
430            feed.read_depth(of)
431                .expect("the in-process feed reports depth")
432                .try_outcome()
433                .expect("settled at once")
434                .expect("read")
435        };
436        let empty = read(DepthOf::Feed);
437        assert!(empty.is_empty() && !empty.approximate, "{empty}");
438        assert!(read(DepthOf::DeadLetters).is_empty());
439
440        for id in [
441            "dead-1",
442            "dead-2",
443            "dead-3",
444            "held",
445            "visible-1",
446            "visible-2",
447        ] {
448            drop(feed.publish(unit(id)));
449        }
450        for id in ["dead-1", "dead-2", "dead-3"] {
451            let dead = receive_one_of(&feed, id);
452            feed.fail(dead.receipt, "unreadable").unwrap();
453        }
454        let _held = receive_one_of(&feed, "held");
455
456        let counts = feed.counts();
457        let depth = read(DepthOf::Feed);
458        assert_eq!(
459            (depth.visible, depth.in_flight, depth.delayed, depth.unsent),
460            (2, 1, 0, 0)
461        );
462        assert_eq!(
463            (depth.visible, depth.in_flight),
464            (counts.visible, counts.held)
465        );
466        let dead = read(DepthOf::DeadLetters);
467        assert_eq!(dead.visible, 3);
468        assert_eq!(dead.visible, counts.dead_lettered);
469        assert_eq!((dead.delayed, dead.in_flight, dead.unsent), (0, 0, 0));
470        assert!(!dead.approximate);
471    }
472
473    /// Pins that `from_section` refuses a section naming another backend, and
474    /// a key in the `memory` body.
475    #[test]
476    fn from_section_refuses_another_backend_and_a_body_key() {
477        let section: FeedSection = serde_yaml::from_str("other: {}\n").expect("section");
478        let err = MemoryFeed::from_section(&section).unwrap_err().to_string();
479        assert!(err.contains("names `other`"), "{err}");
480
481        let section: FeedSection = serde_yaml::from_str("memory: { depth: 4 }\n").expect("section");
482        let err = MemoryFeed::from_section(&section).unwrap_err().to_string();
483        assert!(err.starts_with("feed.memory"), "{err}");
484        assert!(err.contains("depth"), "{err}");
485
486        let bare: FeedSection = serde_yaml::from_str("memory:\n").expect("section");
487        assert!(MemoryFeed::from_section(&bare).is_ok());
488    }
489
490    /// Pins that acknowledging removes the unit for good, and that its
491    /// receipt then answers Gone to every call.
492    #[test]
493    fn an_acknowledged_unit_leaves_and_its_receipt_is_gone() {
494        let feed = MemoryFeed::new();
495        drop(feed.publish(unit("done")));
496        let delivery = receive_one(&feed);
497        assert_eq!(feed.counts().held, 1);
498        feed.renew(delivery.receipt).unwrap();
499        feed.acknowledge(delivery.receipt).unwrap();
500        assert_eq!(
501            feed.counts(),
502            FeedCounts {
503                visible: 0,
504                held: 0,
505                dead_lettered: 0,
506                acknowledged: 1,
507            }
508        );
509        let kind = |r: Result<(), FeedError>| r.unwrap_err().kind;
510        assert_eq!(
511            kind(feed.acknowledge(delivery.receipt)),
512            FeedErrorKind::Gone
513        );
514        assert_eq!(kind(feed.renew(delivery.receipt)), FeedErrorKind::Gone);
515        assert_eq!(kind(feed.fail(delivery.receipt, "x")), FeedErrorKind::Gone);
516        assert_eq!(feed.counts().acknowledged, 1);
517        assert!(feed.receive(usize::MAX).unwrap().is_empty());
518    }
519
520    /// Pins that the feed does not deduplicate by id: a unit published twice
521    /// is delivered twice, under two receipts, each needing its own
522    /// acknowledgement.
523    #[test]
524    fn a_unit_published_twice_is_delivered_twice() {
525        let feed = MemoryFeed::new();
526        drop(feed.publish(unit("twice")));
527        drop(feed.publish(unit("twice")));
528        let got = feed.receive(usize::MAX).unwrap();
529        assert_eq!(got.len(), 2);
530        assert_ne!(got[0].receipt, got[1].receipt);
531        assert!(
532            got.iter()
533                .all(|d| d.unit == unit("twice") && d.deliveries == 1)
534        );
535        feed.acknowledge(got[0].receipt).unwrap();
536        assert_eq!(feed.counts().held, 1);
537        feed.acknowledge(got[1].receipt).unwrap();
538        assert_eq!(feed.counts().acknowledged, 2);
539    }
540
541    /// Pins that `publish` settles its confirmation before returning and
542    /// wakes a reader parked on the feed, including one whose waker was set
543    /// after the publish.
544    #[test]
545    fn a_publish_is_confirmed_at_once_and_wakes_the_reader() {
546        let feed = MemoryFeed::new();
547        let (waker, park) = wake_channel();
548        feed.set_waker(waker);
549        assert!(!park.park(Duration::ZERO));
550        let mut confirmation = feed.publish(unit("one"));
551        assert!(matches!(confirmation.try_outcome(), Some(Ok(()))));
552        assert!(park.park(Duration::ZERO), "publish did not wake the reader");
553
554        let late = MemoryFeed::new();
555        drop(late.publish(unit("early")));
556        let (waker, park) = wake_channel();
557        late.set_waker(waker);
558        assert!(park.park(Duration::ZERO), "set_waker missed a waiting unit");
559    }
560
561    /// Pins FIFO order, the `max` bound on one receive, and that a failed
562    /// unit below the cap re-queues behind units already waiting and wakes
563    /// the reader.
564    #[test]
565    fn units_arrive_in_publish_order_and_a_failed_unit_goes_to_the_back() {
566        let feed = MemoryFeed::new();
567        let (waker, park) = wake_channel();
568        feed.set_waker(waker);
569        for id in ["a", "b", "c"] {
570            drop(feed.publish(unit(id)));
571        }
572        let first = feed.receive(1).unwrap();
573        assert_eq!(first.len(), 1);
574        assert_eq!(first[0].unit.id, "a");
575        assert!(park.park(Duration::ZERO));
576        feed.fail(first[0].receipt, "retry later").unwrap();
577        assert!(park.park(Duration::ZERO), "fail did not wake the reader");
578        let ids: Vec<_> = feed
579            .receive(usize::MAX)
580            .unwrap()
581            .into_iter()
582            .map(|d| (d.unit.id, d.deliveries))
583            .collect();
584        assert_eq!(
585            ids,
586            vec![
587                ("b".to_owned(), 1),
588                ("c".to_owned(), 1),
589                ("a".to_owned(), 2)
590            ]
591        );
592    }
593
594    /// Pins that a receive which leaves units visible leaves a wake pending,
595    /// so a reader that took only what it had room for does not wait out
596    /// its park.
597    #[test]
598    fn a_partial_receive_leaves_a_wake_pending() {
599        let feed = MemoryFeed::new();
600        let (waker, park) = wake_channel();
601        feed.set_waker(waker);
602        for id in ["a", "b"] {
603            drop(feed.publish(unit(id)));
604        }
605        assert!(park.park(Duration::ZERO));
606        assert_eq!(feed.receive(1).unwrap().len(), 1);
607        assert_eq!(feed.counts().visible, 1);
608        assert!(
609            park.park(Duration::ZERO),
610            "a unit is visible but the reader's park was not signalled"
611        );
612    }
613
614    /// Pins that a receive which takes nothing sends no signal, so a reader
615    /// at capacity calling `receive(0)` parks instead of spinning.
616    #[test]
617    fn an_empty_receive_at_capacity_does_not_wake() {
618        let feed = MemoryFeed::new();
619        let (waker, park) = wake_channel();
620        feed.set_waker(waker);
621        drop(feed.publish(unit("a")));
622        assert!(park.park(Duration::ZERO));
623        assert!(feed.receive(0).unwrap().is_empty());
624        assert!(
625            !park.park(Duration::ZERO),
626            "receive(0) at capacity re-signalled"
627        );
628    }
629
630    /// Pins that a unit failed at the cap is on the dead-letter list, and
631    /// the lock is not poisoned, when the warning about it unwinds.
632    #[test]
633    fn a_panicking_subscriber_in_fail_at_the_cap_keeps_the_unit() {
634        use tracing_subscriber::layer::SubscriberExt;
635
636        struct PanicOnWarn;
637        impl<S: tracing::Subscriber> tracing_subscriber::Layer<S> for PanicOnWarn {
638            fn on_event(
639                &self,
640                event: &tracing::Event<'_>,
641                _ctx: tracing_subscriber::layer::Context<'_, S>,
642            ) {
643                if *event.metadata().level() == tracing::Level::WARN {
644                    panic!("warning emitted");
645                }
646            }
647        }
648
649        let feed = MemoryFeed::new();
650        drop(feed.publish(unit("poison")));
651        for _ in 1..10 {
652            let delivery = receive_one(&feed);
653            feed.fail(delivery.receipt, "x").unwrap();
654        }
655        let last = receive_one(&feed);
656        let subscriber = tracing_subscriber::registry().with(PanicOnWarn);
657        let unwound = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
658            tracing::subscriber::with_default(subscriber, || feed.fail(last.receipt, "x"))
659        }));
660        assert!(unwound.is_err(), "the subscriber did not panic");
661        assert_eq!(
662            feed.counts(),
663            FeedCounts {
664                visible: 0,
665                held: 0,
666                dead_lettered: 1,
667                acknowledged: 0,
668            }
669        );
670        assert!(
671            !feed.state.is_poisoned(),
672            "the warning unwound through the lock"
673        );
674    }
675
676    /// Pins that a later `set_waker` replaces the waker: a publish signals
677    /// only the newest one.
678    #[test]
679    fn a_later_set_waker_replaces_the_waker() {
680        let feed = MemoryFeed::new();
681        let (first, first_park) = wake_channel();
682        feed.set_waker(first);
683        let (second, second_park) = wake_channel();
684        feed.set_waker(second);
685        drop(feed.publish(unit("a")));
686        assert!(
687            second_park.park(Duration::ZERO),
688            "publish signalled the replaced waker"
689        );
690        assert!(!first_park.park(Duration::ZERO));
691    }
692}