1use 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
23const ATTEMPT_CAP: NonZeroU32 = NonZeroU32::new(10).expect("non-zero");
25
26const BACKEND: &str = "memory";
28
29#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
31#[non_exhaustive]
32pub struct FeedCounts {
33 pub visible: u64,
35 pub held: u64,
37 pub dead_lettered: u64,
39 pub acknowledged: u64,
41}
42
43#[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 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 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 #[must_use]
83 pub fn new() -> MemoryFeed {
84 MemoryFeed::default()
85 }
86
87 #[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 pub fn from_section(section: &FeedSection) -> Result<MemoryFeed, ConfigError> {
127 #[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 #[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 #[must_use]
179 pub fn dead_letters(&self) -> Vec<Unit> {
180 self.lock().dead.clone()
181 }
182
183 fn lock(&self) -> MutexGuard<'_, State> {
184 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 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 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 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 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 #[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 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 #[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(§ion).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 #[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(§ion).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 #[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(§ion).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 #[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 #[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(§ion).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(§ion).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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}