1use crate::{
17 Consumer, Delivery, Fetch, Outcome, Resolution, TargetedResolver,
18 delivery::{Completion as DeliveryCompletion, Tracker as DeliveryTracker},
19 ingress::{self, FetchKey, Message},
20 subscribers,
21};
22use commonware_actor::{Feedback, mailbox};
23use commonware_cryptography::PublicKey;
24use commonware_macros::select_loop;
25use commonware_runtime::{
26 Clock, ContextCell, Handle, Metrics, Spawner, spawn_cell,
27 telemetry::metrics::{MetricsExt as _, status, status::Status},
28};
29use commonware_utils::{
30 Span,
31 futures::{AbortablePool, Aborter},
32 vec::NonEmptyVec,
33};
34use futures::future::{self, Either};
35use std::{
36 collections::BTreeSet,
37 future::Future,
38 marker::PhantomData,
39 num::NonZeroUsize,
40 time::{Duration, SystemTime},
41};
42use tracing::{debug, warn};
43
44pub trait Fetcher {
46 type Key: Span;
48
49 type Value;
51
52 fn fetch(&self, key: Self::Key) -> impl Future<Output = Option<Self::Value>> + Send;
58}
59
60pub struct Resolver<K, S, P>
62where
63 K: Span,
64 S: Clone + Eq + Send + 'static,
65 P: PublicKey,
66{
67 mailbox: mailbox::Sender<Message<K, S>>,
68 _peer: PhantomData<P>,
69}
70
71impl<K, S, P> Clone for Resolver<K, S, P>
72where
73 K: Span,
74 S: Clone + Eq + Send + 'static,
75 P: PublicKey,
76{
77 fn clone(&self) -> Self {
78 Self {
79 mailbox: self.mailbox.clone(),
80 _peer: PhantomData,
81 }
82 }
83}
84
85impl<K, S, P> crate::Resolver for Resolver<K, S, P>
86where
87 K: Span,
88 S: Clone + Eq + Send + 'static,
89 P: PublicKey,
90{
91 type Key = K;
92 type Subscriber = S;
93
94 fn fetch<F>(&mut self, fetch: F) -> Feedback
95 where
96 F: Into<Fetch<Self::Key, Self::Subscriber>> + Send,
97 {
98 self.send(Message::Fetch(vec![FetchKey::from(fetch.into())]))
99 }
100
101 fn fetch_all<F>(&mut self, fetches: Vec<F>) -> Feedback
102 where
103 F: Into<Fetch<Self::Key, Self::Subscriber>> + Send,
104 {
105 self.send(Message::Fetch(
106 fetches
107 .into_iter()
108 .map(|fetch| FetchKey::from(fetch.into()))
109 .collect(),
110 ))
111 }
112
113 fn retain(
114 &mut self,
115 predicate: impl Fn(&Self::Key, &Self::Subscriber) -> bool + Send + 'static,
116 ) -> Feedback {
117 self.send(Message::Retain {
118 predicate: Box::new(predicate),
119 })
120 }
121}
122
123impl<K, S, P> TargetedResolver for Resolver<K, S, P>
124where
125 K: Span,
126 S: Clone + Eq + Send + 'static,
127 P: PublicKey,
128{
129 type PublicKey = P;
130
131 fn fetch_targeted(
132 &mut self,
133 fetch: impl Into<Fetch<Self::Key, Self::Subscriber>> + Send,
134 _targets: NonEmptyVec<Self::PublicKey>,
135 ) -> Feedback {
136 <Self as crate::Resolver>::fetch(self, fetch)
137 }
138
139 fn fetch_all_targeted<F>(&mut self, fetches: Vec<(F, NonEmptyVec<Self::PublicKey>)>) -> Feedback
140 where
141 F: Into<Fetch<Self::Key, Self::Subscriber>> + Send,
142 {
143 <Self as crate::Resolver>::fetch_all(
144 self,
145 fetches.into_iter().map(|(fetch, _)| fetch).collect(),
146 )
147 }
148}
149
150impl<K, S, P> Resolver<K, S, P>
151where
152 K: Span,
153 S: Clone + Eq + Send + 'static,
154 P: PublicKey,
155{
156 const fn new(mailbox: mailbox::Sender<Message<K, S>>) -> Self {
157 Self {
158 mailbox,
159 _peer: PhantomData,
160 }
161 }
162
163 fn send(&self, message: Message<K, S>) -> Feedback {
164 self.mailbox.enqueue(message)
165 }
166}
167
168pub fn init<E, F, Con, P>(
170 context: E,
171 fetcher: F,
172 consumer: Con,
173 mailbox_size: NonZeroUsize,
174 fetch_retry_delay: Duration,
175) -> Resolver<F::Key, Con::Subscriber, P>
176where
177 E: Clock + Spawner + Metrics,
178 F: Fetcher + Clone + Send + 'static,
179 F::Value: Clone + Send + 'static,
180 Con: Consumer<Key = F::Key, Value = F::Value>,
181 Con::Subscriber: Ord,
182 P: PublicKey,
183{
184 let (mailbox_tx, mailbox_rx) = mailbox::new(context.child("mailbox"), mailbox_size);
185 Actor::new(
186 context.child("actor"),
187 fetcher,
188 mailbox_rx,
189 consumer,
190 fetch_retry_delay,
191 )
192 .start();
193 Resolver::new(mailbox_tx)
194}
195
196struct Actor<E, F, Con>
198where
199 E: Clock + Spawner + Metrics,
200 F: Fetcher,
201 F::Value: Clone + Send + 'static,
202 Con: Consumer<Key = F::Key, Value = F::Value>,
203 Con::Subscriber: Ord,
204{
205 context: ContextCell<E>,
206 fetcher: F,
207 mailbox: mailbox::Receiver<Message<F::Key, Con::Subscriber>>,
208 fetches: AbortablePool<'static, FetchCompletion<F::Key, F::Value>>,
209 deliveries: DeliveryTracker<Con, (), Attempt>,
210 subscribers: subscribers::Tracker<F::Key, Con::Subscriber>,
211 fetch: status::Counter,
212 retry_schedule: BTreeSet<(SystemTime, F::Key)>,
213 fetch_retry_delay: Duration,
214}
215
216enum Attempt {
221 Fetching { _aborter: Aborter },
223
224 Delivering,
226
227 Scheduled(SystemTime),
229}
230
231struct FetchCompletion<K, V> {
232 key: K,
233 value: Option<V>,
234}
235
236impl<E, F, Con> Actor<E, F, Con>
237where
238 E: Clock + Spawner + Metrics,
239 F: Fetcher + Clone + Send + 'static,
240 F::Value: Clone + Send + 'static,
241 Con: Consumer<Key = F::Key, Value = F::Value>,
242 Con::Subscriber: Ord,
243{
244 fn new(
245 context: E,
246 fetcher: F,
247 mailbox: mailbox::Receiver<Message<F::Key, Con::Subscriber>>,
248 consumer: Con,
249 fetch_retry_delay: Duration,
250 ) -> Self {
251 let fetch = context.family("fetch", "Number of fetches by status");
252 Self {
253 context: ContextCell::new(context),
254 fetcher,
255 mailbox,
256 fetches: AbortablePool::default(),
257 deliveries: DeliveryTracker::new(consumer),
258 subscribers: subscribers::Tracker::new(),
259 fetch,
260 retry_schedule: BTreeSet::new(),
261 fetch_retry_delay,
262 }
263 }
264
265 fn start(mut self) -> Handle<()> {
266 spawn_cell!(self.context, self.run())
267 }
268
269 async fn run(mut self) {
270 select_loop! {
271 self.context,
272 on_stopped => {},
273 Ok(FetchCompletion { key, value }) = self.fetches.next_completed() else continue => {
274 self.handle_fetched(key, value);
275 },
276 Ok(completed) = self.deliveries.next_completion() else continue => {
277 self.handle_delivered(completed);
278 },
279 _ = match self.retry_schedule.first() {
280 Some((deadline, _)) => Either::Left(self.context.sleep_until(*deadline)),
281 None => Either::Right(future::pending()),
282 } => {
283 self.process_retries();
284 },
285 Some(message) = self.mailbox.recv() else break => {
286 self.handle_message(message);
287 },
288 }
289 }
290
291 fn handle_message(&mut self, message: Message<F::Key, Con::Subscriber>) {
293 match message {
294 Message::Fetch(fetches) => {
295 for fetch in fetches {
296 self.add_fetch(fetch);
297 }
298 }
299 Message::Retain { predicate } => self.retain(predicate),
300 }
301 }
302
303 fn add_fetch(&mut self, fetch: FetchKey<F::Key, Con::Subscriber>) {
305 let FetchKey {
306 key, subscribers, ..
307 } = fetch;
308 let is_new = self.subscribers.insert(key.clone(), subscribers);
309
310 if is_new {
311 let attempt = self.start_fetch(key.clone());
312 assert!(
313 self.deliveries.insert_with_state(key, attempt),
314 "new key must not have a delivery entry"
315 );
316 }
317 }
318
319 fn retain(&mut self, predicate: ingress::Predicate<F::Key, Con::Subscriber>) {
321 for key in self
322 .subscribers
323 .retain(|key, subscriber| predicate(key, subscriber))
324 {
325 if let Some(Some(Attempt::Scheduled(deadline))) =
326 self.deliveries.remove_with_state(&key)
327 {
328 self.retry_schedule.remove(&(deadline, key));
329 }
330 }
331 }
332
333 fn start_fetch(&mut self, key: F::Key) -> Attempt {
335 let future = Self::fetch(key, self.fetcher.clone());
336 let aborter = self.fetches.push(future);
337 Attempt::Fetching { _aborter: aborter }
338 }
339
340 fn start_delivery(
342 &mut self,
343 key: F::Key,
344 value: F::Value,
345 delivered: NonEmptyVec<(Con::Subscriber, tracing::Span)>,
346 ) {
347 *self
348 .deliveries
349 .state_mut(&key)
350 .expect("key must have an attempt") = Attempt::Delivering;
351 self.deliveries.deliver(
352 Delivery {
353 key,
354 subscribers: delivered,
355 },
356 (),
357 value,
358 );
359 }
360
361 fn redeliver(&mut self, key: F::Key, delivered: NonEmptyVec<(Con::Subscriber, tracing::Span)>) {
363 self.deliveries.redeliver(Delivery {
364 key,
365 subscribers: delivered,
366 });
367 }
368
369 fn handle_fetched(&mut self, key: F::Key, value: Option<F::Value>) {
371 match value {
372 None => self.schedule_retry(key),
373 Some(value) => {
374 if let Some(subscribers) = self.subscribers.pending(&key) {
375 self.start_delivery(key, value, subscribers);
376 } else {
377 self.subscribers.remove(&key);
378 self.deliveries.remove(&key);
379 }
380 }
381 }
382 }
383
384 fn handle_delivered(&mut self, completed: DeliveryCompletion<F::Key, Con::Subscriber>) {
386 let DeliveryCompletion {
387 delivery:
388 Delivery {
389 key,
390 subscribers: delivered,
391 },
392 outcome,
393 canceled,
394 previously_accepted: accepted,
395 ..
396 } = completed;
397
398 if !accepted {
400 match outcome {
401 Some(Outcome::Resolved(_)) => self.fetch.inc(Status::Success),
402 Some(Outcome::Unresolved) => self.fetch.inc(Status::Ambiguous),
403 _ => {}
404 }
405 }
406 if canceled {
407 return;
408 }
409
410 let Some(outcome) = outcome else {
414 let remaining = self
415 .subscribers
416 .remove_delivered(&key, delivered.map_into(|(subscriber, _)| subscriber));
417 if let Some(subscribers) = remaining {
418 self.redeliver(key, subscribers);
419 return;
420 }
421 if !accepted {
422 self.fetch.inc(Status::Dropped);
423 }
424 self.deliveries.remove(&key);
425 return;
426 };
427
428 match outcome {
431 Outcome::Resolved(Resolution::Complete) => {
432 let remaining = self
433 .subscribers
434 .remove_delivered(&key, delivered.map_into(|(subscriber, _)| subscriber));
435
436 if let Some(subscribers) = remaining {
440 if !accepted {
441 self.deliveries.accept_response(&key);
442 }
443 self.redeliver(key, subscribers);
444 } else {
445 self.subscribers.remove(&key);
446 self.deliveries.remove(&key);
447 }
448 }
449 Outcome::Resolved(Resolution::Partial) | Outcome::Unresolved => {
450 self.deliveries.discard_response(&key);
453 self.schedule_retry(key);
454 }
455 Outcome::Invalid => {
456 if accepted {
460 warn!(
461 ?key,
462 "previously accepted resolver response rejected during opaque redelivery"
463 );
464 self.subscribers.remove(&key);
465 self.deliveries.remove(&key);
466 return;
467 }
468
469 warn!(?key, "consumer rejected opaque resolver delivery");
472 self.deliveries.discard_response(&key);
473 self.schedule_retry(key);
474 }
475 Outcome::Ignored => {
476 self.fetch.inc(Status::Dropped);
479 self.subscribers.remove(&key);
480 self.deliveries.remove(&key);
481 }
482 }
483 }
484
485 fn schedule_retry(&mut self, key: F::Key) {
487 let deadline = self.context.current() + self.fetch_retry_delay;
488 *self
489 .deliveries
490 .state_mut(&key)
491 .expect("key must have an attempt") = Attempt::Scheduled(deadline);
492 debug!(?key, ?deadline, "scheduled opaque resolver retry");
493 self.retry_schedule.insert((deadline, key));
494 }
495
496 fn process_retries(&mut self) {
498 let now = self.context.current();
499 while let Some((deadline, key)) = self.retry_schedule.pop_first() {
500 if deadline > now {
501 self.retry_schedule.insert((deadline, key));
502 break;
503 }
504
505 debug!(?key, "retrying opaque resolver fetch");
506 let attempt = self.start_fetch(key.clone());
507 *self
508 .deliveries
509 .state_mut(&key)
510 .expect("scheduled key must have an attempt") = attempt;
511 }
512 }
513
514 async fn fetch(key: F::Key, fetcher: F) -> FetchCompletion<F::Key, F::Value> {
516 let value = fetcher.fetch(key.clone()).await;
517 FetchCompletion { key, value }
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524 use crate::Resolver as _;
525 use bytes::Bytes;
526 use commonware_cryptography::{
527 Signer,
528 ed25519::{PrivateKey, PublicKey},
529 };
530 use commonware_runtime::{Runner as _, Supervisor as _, deterministic, deterministic::Runner};
531 use commonware_utils::{channel::oneshot, non_empty_vec, sync::Mutex};
532 use futures::FutureExt as _;
533 use std::{
534 collections::{HashMap, VecDeque},
535 sync::{
536 Arc,
537 atomic::{AtomicU32, Ordering},
538 },
539 };
540
541 const RETRY_DELAY: Duration = Duration::from_millis(100);
542
543 #[derive(Clone, Default)]
544 struct MockFetcher {
545 responses: Arc<Mutex<HashMap<u8, VecDeque<Option<Bytes>>>>>,
546 calls: Arc<AtomicU32>,
547 }
548
549 impl MockFetcher {
550 fn push(&self, key: u8, response: Option<Bytes>) {
551 self.responses
552 .lock()
553 .entry(key)
554 .or_default()
555 .push_back(response);
556 }
557
558 fn calls(&self) -> u32 {
559 self.calls.load(Ordering::Relaxed)
560 }
561 }
562
563 impl Fetcher for MockFetcher {
564 type Key = u8;
565 type Value = Bytes;
566
567 fn fetch(&self, key: Self::Key) -> impl Future<Output = Option<Self::Value>> + Send {
568 let responses = self.responses.clone();
569 let calls = self.calls.clone();
570 async move {
571 calls.fetch_add(1, Ordering::Relaxed);
572 responses
573 .lock()
574 .get_mut(&key)
575 .and_then(VecDeque::pop_front)
576 .flatten()
577 }
578 }
579 }
580
581 #[derive(Clone)]
582 struct BlockingFetcher {
583 started: Arc<Mutex<Option<oneshot::Sender<()>>>>,
584 response: Arc<Mutex<Option<oneshot::Receiver<Option<Bytes>>>>>,
585 }
586
587 impl BlockingFetcher {
588 fn new() -> (Self, oneshot::Receiver<()>, oneshot::Sender<Option<Bytes>>) {
589 let (started_tx, started_rx) = oneshot::channel();
590 let (response_tx, response_rx) = oneshot::channel();
591 (
592 Self {
593 started: Arc::new(Mutex::new(Some(started_tx))),
594 response: Arc::new(Mutex::new(Some(response_rx))),
595 },
596 started_rx,
597 response_tx,
598 )
599 }
600 }
601
602 impl Fetcher for BlockingFetcher {
603 type Key = u8;
604 type Value = Bytes;
605
606 fn fetch(&self, _key: Self::Key) -> impl Future<Output = Option<Self::Value>> + Send {
607 let started = self.started.clone();
608 let response = self.response.clone();
609 async move {
610 if let Some(started) = started.lock().take() {
611 let _ = started.send(());
612 }
613 let response = response.lock().take().expect("missing response");
614 response.await.unwrap_or(None)
615 }
616 }
617 }
618
619 struct CapturedDelivery {
620 delivery: Delivery<u8, u16>,
621 value: Bytes,
622 response: oneshot::Sender<Outcome>,
623 }
624
625 #[derive(Clone, Default)]
626 struct MockConsumer {
627 deliveries: Arc<Mutex<VecDeque<CapturedDelivery>>>,
628 }
629
630 impl MockConsumer {
631 fn pop(&self) -> Option<CapturedDelivery> {
632 self.deliveries.lock().pop_front()
633 }
634
635 fn len(&self) -> usize {
636 self.deliveries.lock().len()
637 }
638 }
639
640 impl Consumer for MockConsumer {
641 type Key = u8;
642 type Value = Bytes;
643 type Subscriber = u16;
644 type Outcome = Outcome;
645
646 fn deliver(
647 &mut self,
648 delivery: Delivery<Self::Key, Self::Subscriber>,
649 value: Self::Value,
650 ) -> oneshot::Receiver<Self::Outcome> {
651 let (response, receiver) = oneshot::channel();
652 self.deliveries.lock().push_back(CapturedDelivery {
653 delivery,
654 value,
655 response,
656 });
657 receiver
658 }
659 }
660
661 fn start_resolver<F>(
662 context: deterministic::Context,
663 fetcher: F,
664 consumer: MockConsumer,
665 ) -> Resolver<u8, u16, PublicKey>
666 where
667 F: Fetcher<Key = u8, Value = Bytes> + Clone + Send + 'static,
668 {
669 init(
670 context,
671 fetcher,
672 consumer,
673 NonZeroUsize::new(16).unwrap(),
674 RETRY_DELAY,
675 )
676 }
677
678 async fn wait_for_delivery(
679 context: &deterministic::Context,
680 consumer: &MockConsumer,
681 ) -> CapturedDelivery {
682 for _ in 0..50 {
683 if let Some(delivery) = consumer.pop() {
684 return delivery;
685 }
686 context.sleep(Duration::from_millis(10)).await;
687 }
688 panic!("timed out waiting for delivery");
689 }
690
691 #[test]
692 fn fetch_during_validation_reuses_response_after_success() {
693 for outcome in [
694 Outcome::Resolved(Resolution::Complete),
695 Outcome::Resolved(Resolution::Partial),
696 Outcome::Unresolved,
697 ] {
698 Runner::default().start(move |context| async move {
699 let fetcher = MockFetcher::default();
700 fetcher.push(1, Some(Bytes::from_static(b"value")));
701 fetcher.push(1, Some(Bytes::from_static(b"refetched")));
702 let consumer = MockConsumer::default();
703 let mut resolver =
704 start_resolver(context.child("resolver"), fetcher.clone(), consumer.clone());
705
706 assert!(
708 resolver
709 .fetch(Fetch {
710 key: 1,
711 subscriber: 10,
712 span: tracing::Span::none(),
713 })
714 .accepted()
715 );
716 let first = wait_for_delivery(&context, &consumer).await;
717 assert_eq!(
718 first.delivery,
719 Delivery {
720 key: 1,
721 subscribers: non_empty_vec![(10, tracing::Span::none())],
722 }
723 );
724 assert_eq!(first.value, Bytes::from_static(b"value"));
725 assert!(
726 resolver
727 .fetch_all(
728 [11, 12]
729 .into_iter()
730 .map(|subscriber| Fetch {
731 key: 1,
732 subscriber,
733 span: tracing::Span::none(),
734 })
735 .collect()
736 )
737 .accepted()
738 );
739 context.sleep(Duration::from_millis(10)).await;
740 first
741 .response
742 .send(Outcome::Resolved(Resolution::Complete))
743 .unwrap();
744
745 let second = wait_for_delivery(&context, &consumer).await;
747 assert_eq!(second.value, Bytes::from_static(b"value"));
748 assert_eq!(
749 second.delivery,
750 Delivery {
751 key: 1,
752 subscribers: non_empty_vec![
753 (11, tracing::Span::none()),
754 (12, tracing::Span::none()),
755 ],
756 }
757 );
758 assert_eq!(fetcher.calls(), 1);
759 assert!(
760 context
761 .encode()
762 .contains("resolver_actor_fetch_total{status=\"Success\"} 1")
763 );
764 if outcome == Outcome::Resolved(Resolution::Partial) {
765 assert!(
766 resolver
767 .retain(|_, subscriber| *subscriber != 11)
768 .accepted()
769 );
770 }
771 second.response.send(outcome).unwrap();
772
773 let expected_successes = if outcome == Outcome::Resolved(Resolution::Complete) {
774 1
775 } else {
776 let third = wait_for_delivery(&context, &consumer).await;
779 assert_eq!(third.value, Bytes::from_static(b"refetched"));
780 let expected = if outcome == Outcome::Resolved(Resolution::Partial) {
781 non_empty_vec![(12, tracing::Span::none())]
782 } else {
783 non_empty_vec![(11, tracing::Span::none()), (12, tracing::Span::none())]
784 };
785 assert_eq!(
786 third.delivery,
787 Delivery {
788 key: 1,
789 subscribers: expected
790 }
791 );
792 let metrics = context.encode();
793 assert!(metrics.contains("resolver_actor_fetch_total{status=\"Success\"} 1"));
794 assert!(!metrics.contains("resolver_actor_fetch_total{status=\"Ambiguous\"}"));
795 third
796 .response
797 .send(Outcome::Resolved(Resolution::Complete))
798 .unwrap();
799 2
800 };
801 context.sleep(Duration::from_millis(10)).await;
802 assert_eq!(consumer.len(), 0);
803 assert_eq!(fetcher.calls(), expected_successes);
804 let metrics = context.encode();
805 assert!(metrics.contains(&format!(
806 "resolver_actor_fetch_total{{status=\"Success\"}} {expected_successes}"
807 )));
808 assert!(!metrics.contains("resolver_actor_fetch_total{status=\"Ambiguous\"}"));
809 });
810 }
811 }
812
813 #[test]
814 fn canceled_retry_cannot_restart_reinserted_key_early() {
815 for reinsert in [false, true] {
816 Runner::default().start(move |context| async move {
817 let fetcher = MockFetcher::default();
819 fetcher.push(1, None);
820 fetcher.push(1, None);
821 fetcher.push(1, Some(Bytes::from_static(b"new")));
822 let consumer = MockConsumer::default();
823 let (_sender, receiver) =
824 mailbox::new(context.child("mailbox"), NonZeroUsize::new(16).unwrap());
825 let mut actor = Actor::new(
826 context.child("actor"),
827 fetcher.clone(),
828 receiver,
829 consumer.clone(),
830 RETRY_DELAY,
831 );
832 let fetch = || FetchKey {
833 key: 1,
834 metadata: (),
835 subscribers: non_empty_vec![(10, tracing::Span::none())],
836 };
837
838 actor.add_fetch(fetch());
840 let completed = actor.fetches.next_completed().await.unwrap();
841 actor.handle_fetched(completed.key, completed.value);
842 assert_eq!(fetcher.calls(), 1);
843
844 context.sleep(RETRY_DELAY / 2).await;
846 actor.retain(Box::new(|_, _| false));
847 if reinsert {
848 actor.add_fetch(fetch());
849 let completed = actor.fetches.next_completed().await.unwrap();
850 actor.handle_fetched(completed.key, completed.value);
851 assert_eq!(fetcher.calls(), 2);
852 }
853
854 context.sleep(RETRY_DELAY / 2).await;
856 actor.process_retries();
857 assert!(actor.fetches.next_completed().now_or_never().is_none());
858 assert_eq!(fetcher.calls(), if reinsert { 2 } else { 1 });
859
860 context.sleep(RETRY_DELAY / 2).await;
862 actor.process_retries();
863 if reinsert {
864 let completed = actor.fetches.next_completed().await.unwrap();
865 actor.handle_fetched(completed.key, completed.value);
866 assert_eq!(fetcher.calls(), 3);
867 let delivered = consumer.pop().unwrap();
868 assert_eq!(delivered.value, Bytes::from_static(b"new"));
869 assert_eq!(delivered.delivery.subscribers.first().0, 10);
870 } else {
871 assert!(actor.fetches.next_completed().now_or_never().is_none());
872 assert_eq!(fetcher.calls(), 1);
873 }
874 });
875 }
876 }
877
878 #[test]
880 fn canceled_delivery_cannot_change_reinserted_key() {
881 for previously_accepted in [false, true] {
882 for outcome in [
883 Outcome::Resolved(Resolution::Complete),
884 Outcome::Resolved(Resolution::Partial),
885 Outcome::Unresolved,
886 Outcome::Invalid,
887 Outcome::Ignored,
888 ] {
889 Runner::default().start(move |context| async move {
890 let fetcher = MockFetcher::default();
892 fetcher.push(1, Some(Bytes::from_static(b"old")));
893 fetcher.push(1, Some(Bytes::from_static(b"new")));
894 let consumer = MockConsumer::default();
895 let (_sender, receiver) =
896 mailbox::new(context.child("mailbox"), NonZeroUsize::new(16).unwrap());
897 let mut actor = Actor::new(
898 context.child("actor"),
899 fetcher.clone(),
900 receiver,
901 consumer.clone(),
902 RETRY_DELAY,
903 );
904 let fetch = |subscriber| FetchKey {
905 key: 1,
906 metadata: (),
907 subscribers: non_empty_vec![(subscriber, tracing::Span::none())],
908 };
909 actor.add_fetch(fetch(10));
910 actor.add_fetch(fetch(12));
911 let completed = actor.fetches.next_completed().await.unwrap();
912 actor.handle_fetched(completed.key, completed.value);
913 let mut old = consumer.pop().unwrap();
914 if previously_accepted {
915 actor.add_fetch(fetch(11));
916 actor.add_fetch(fetch(13));
917 old.response
918 .send(Outcome::Resolved(Resolution::Complete))
919 .unwrap();
920 let completed = actor.deliveries.next_completion().await.unwrap();
921 actor.handle_delivered(completed);
922 old = consumer.pop().unwrap();
923 assert_eq!(old.value, Bytes::from_static(b"old"));
924 }
925 assert_eq!(old.delivery.subscribers.len().get(), 2);
926
927 old.response.send(outcome).unwrap();
930 actor.retain(Box::new(|_, _| false));
931 actor.add_fetch(fetch(if previously_accepted { 11 } else { 10 }));
932 let completed = actor.fetches.next_completed().await.unwrap();
933 actor.handle_fetched(completed.key, completed.value);
934 let new = consumer.pop().unwrap();
935 assert_eq!(new.value, Bytes::from_static(b"new"));
936 let completed = actor.deliveries.next_completion().await.unwrap();
937 assert!(completed.canceled);
938 assert_eq!(completed.previously_accepted, previously_accepted);
939 actor.handle_delivered(completed);
940
941 assert_eq!(
944 actor
945 .subscribers
946 .pending(&1)
947 .unwrap()
948 .map_into(|(subscriber, _)| subscriber),
949 new.delivery
950 .subscribers
951 .clone()
952 .map_into(|(subscriber, _)| subscriber)
953 );
954 assert!(matches!(
955 actor.deliveries.state_mut(&1),
956 Some(Attempt::Delivering)
957 ));
958 assert!(actor.retry_schedule.is_empty());
959 assert!(actor.deliveries.next_completion().now_or_never().is_none());
960 assert_eq!(fetcher.calls(), 2);
961 let successes =
962 u64::from(previously_accepted || matches!(outcome, Outcome::Resolved(_)));
963 let unresolved =
964 u64::from(!previously_accepted && outcome == Outcome::Unresolved);
965 for (status, count) in [("Success", successes), ("Ambiguous", unresolved)] {
966 let prefix = format!("actor_fetch_total{{status=\"{status}\"}}");
967 let metrics = context.encode();
968 let actual = metrics
969 .lines()
970 .find_map(|line| {
971 line.strip_prefix(&prefix)
972 .map(|value| value.trim().parse::<u64>().unwrap())
973 })
974 .unwrap_or(0);
975 assert_eq!(actual, count);
976 }
977
978 actor.add_fetch(fetch(20));
981 new.response
982 .send(Outcome::Resolved(Resolution::Complete))
983 .unwrap();
984 let completed = actor.deliveries.next_completion().await.unwrap();
985 assert!(!completed.canceled);
986 actor.handle_delivered(completed);
987 let late = consumer.pop().unwrap();
988 assert_eq!(late.value, Bytes::from_static(b"new"));
989 assert_eq!(late.delivery.subscribers.first().0, 20);
990 assert_eq!(late.delivery.subscribers.len().get(), 1);
991 assert_eq!(fetcher.calls(), 2);
992 late.response
993 .send(Outcome::Resolved(Resolution::Complete))
994 .unwrap();
995 let completed = actor.deliveries.next_completion().await.unwrap();
996 actor.handle_delivered(completed);
997 assert!(!actor.subscribers.contains(&1));
998 assert!(!actor.deliveries.contains(&1));
999 });
1000 }
1001 }
1002 }
1003
1004 #[test]
1005 fn missing_fetch_retries_until_value_is_available() {
1006 Runner::default().start(|context| async move {
1007 let fetcher = MockFetcher::default();
1008 fetcher.push(1, None);
1009 fetcher.push(1, Some(Bytes::from_static(b"value")));
1010 let consumer = MockConsumer::default();
1011 let mut resolver =
1012 start_resolver(context.child("resolver"), fetcher.clone(), consumer.clone());
1013
1014 assert!(
1015 resolver
1016 .fetch(Fetch {
1017 key: 1,
1018 subscriber: 10,
1019 span: tracing::Span::none(),
1020 })
1021 .accepted()
1022 );
1023
1024 context.sleep(RETRY_DELAY - Duration::from_millis(10)).await;
1026 assert_eq!(fetcher.calls(), 1);
1027 assert_eq!(consumer.len(), 0);
1028
1029 let delivery = wait_for_delivery(&context, &consumer).await;
1030 assert_eq!(delivery.value, Bytes::from_static(b"value"));
1031 delivery
1032 .response
1033 .send(Outcome::Resolved(Resolution::Complete))
1034 .expect("response dropped");
1035 assert_eq!(fetcher.calls(), 2);
1036 });
1037 }
1038
1039 #[test]
1040 fn unresolved_delivery_retries_without_retiring_subscribers() {
1041 Runner::default().start(|context| async move {
1042 let fetcher = MockFetcher::default();
1043 fetcher.push(1, Some(Bytes::from_static(b"unhelpful")));
1044 fetcher.push(1, Some(Bytes::from_static(b"partial")));
1045 fetcher.push(1, Some(Bytes::from_static(b"complete")));
1046 let consumer = MockConsumer::default();
1047 let mut resolver =
1048 start_resolver(context.child("resolver"), fetcher.clone(), consumer.clone());
1049
1050 assert!(
1051 resolver
1052 .fetch_all(
1053 [10, 11]
1054 .into_iter()
1055 .map(|subscriber| Fetch {
1056 key: 1,
1057 subscriber,
1058 span: tracing::Span::none(),
1059 })
1060 .collect()
1061 )
1062 .accepted()
1063 );
1064 let expected = Delivery {
1065 key: 1,
1066 subscribers: non_empty_vec![
1067 (10, tracing::Span::none()),
1068 (11, tracing::Span::none())
1069 ],
1070 };
1071
1072 let first = wait_for_delivery(&context, &consumer).await;
1074 assert_eq!(first.value, Bytes::from_static(b"unhelpful"));
1075 assert_eq!(first.delivery, expected);
1076 first.response.send(Outcome::Unresolved).unwrap();
1077 let second = wait_for_delivery(&context, &consumer).await;
1078 assert_eq!(second.value, Bytes::from_static(b"partial"));
1079 assert_eq!(second.delivery, expected);
1080 assert_eq!(fetcher.calls(), 2);
1081 let metrics = context.encode();
1082 assert!(metrics.contains("resolver_actor_fetch_total{status=\"Ambiguous\"} 1"));
1083 assert!(!metrics.contains("resolver_actor_fetch_total{status=\"Success\"}"));
1084
1085 assert!(
1087 resolver
1088 .retain(|_, subscriber| *subscriber != 10)
1089 .accepted()
1090 );
1091 second
1092 .response
1093 .send(Outcome::Resolved(Resolution::Partial))
1094 .unwrap();
1095 let third = wait_for_delivery(&context, &consumer).await;
1096 assert_eq!(third.value, Bytes::from_static(b"complete"));
1097 assert_eq!(
1098 third.delivery,
1099 Delivery {
1100 key: 1,
1101 subscribers: non_empty_vec![(11, tracing::Span::none())],
1102 }
1103 );
1104 let metrics = context.encode();
1105 assert!(metrics.contains("resolver_actor_fetch_total{status=\"Ambiguous\"} 1"));
1106 assert!(metrics.contains("resolver_actor_fetch_total{status=\"Success\"} 1"));
1107
1108 third
1110 .response
1111 .send(Outcome::Resolved(Resolution::Complete))
1112 .unwrap();
1113 context.sleep(Duration::from_millis(10)).await;
1114 assert_eq!(consumer.len(), 0);
1115 assert_eq!(fetcher.calls(), 3);
1116 let metrics = context.encode();
1117 assert!(metrics.contains("resolver_actor_fetch_total{status=\"Ambiguous\"} 1"));
1118 assert!(metrics.contains("resolver_actor_fetch_total{status=\"Success\"} 2"));
1119 });
1120 }
1121
1122 #[test]
1123 fn ignored_delivery_retires_without_retry() {
1124 Runner::default().start(|context| async move {
1125 let fetcher = MockFetcher::default();
1126 fetcher.push(1, Some(Bytes::from_static(b"obsolete")));
1127 fetcher.push(1, Some(Bytes::from_static(b"fresh")));
1128 let consumer = MockConsumer::default();
1129 let mut resolver =
1130 start_resolver(context.child("resolver"), fetcher.clone(), consumer.clone());
1131
1132 assert!(
1133 resolver
1134 .fetch(Fetch {
1135 key: 1,
1136 subscriber: 10,
1137 span: tracing::Span::none(),
1138 })
1139 .accepted()
1140 );
1141 let delivery = wait_for_delivery(&context, &consumer).await;
1142 assert_eq!(delivery.value, Bytes::from_static(b"obsolete"));
1143 delivery
1144 .response
1145 .send(Outcome::Ignored)
1146 .expect("response dropped");
1147
1148 context.sleep(RETRY_DELAY + Duration::from_millis(10)).await;
1149 assert_eq!(fetcher.calls(), 1);
1150 assert_eq!(consumer.len(), 0);
1151 assert!(
1152 context
1153 .encode()
1154 .contains("resolver_actor_fetch_total{status=\"Dropped\"} 1")
1155 );
1156
1157 assert!(
1159 resolver
1160 .fetch(Fetch {
1161 key: 1,
1162 subscriber: 10,
1163 span: tracing::Span::none(),
1164 })
1165 .accepted()
1166 );
1167 let delivery = wait_for_delivery(&context, &consumer).await;
1168 assert_eq!(delivery.value, Bytes::from_static(b"fresh"));
1169 delivery
1170 .response
1171 .send(Outcome::Resolved(Resolution::Complete))
1172 .expect("response dropped");
1173 assert_eq!(fetcher.calls(), 2);
1174 });
1175 }
1176
1177 #[test]
1178 fn dropped_verdict_retires_without_retry() {
1179 Runner::default().start(|context| async move {
1180 let fetcher = MockFetcher::default();
1181 fetcher.push(1, Some(Bytes::from_static(b"unjudged")));
1182 fetcher.push(1, Some(Bytes::from_static(b"fresh")));
1183 let consumer = MockConsumer::default();
1184 let mut resolver =
1185 start_resolver(context.child("resolver"), fetcher.clone(), consumer.clone());
1186
1187 assert!(
1188 resolver
1189 .fetch(Fetch {
1190 key: 1,
1191 subscriber: 10,
1192 span: tracing::Span::none(),
1193 })
1194 .accepted()
1195 );
1196 let delivery = wait_for_delivery(&context, &consumer).await;
1197 assert_eq!(delivery.value, Bytes::from_static(b"unjudged"));
1198
1199 drop(delivery);
1203
1204 context.sleep(RETRY_DELAY + Duration::from_millis(10)).await;
1205 assert_eq!(fetcher.calls(), 1);
1206 assert_eq!(consumer.len(), 0);
1207 assert!(
1208 context
1209 .encode()
1210 .contains("resolver_actor_fetch_total{status=\"Dropped\"} 1")
1211 );
1212
1213 assert!(
1215 resolver
1216 .fetch(Fetch {
1217 key: 1,
1218 subscriber: 10,
1219 span: tracing::Span::none(),
1220 })
1221 .accepted()
1222 );
1223 let delivery = wait_for_delivery(&context, &consumer).await;
1224 assert_eq!(delivery.value, Bytes::from_static(b"fresh"));
1225 delivery
1226 .response
1227 .send(Outcome::Resolved(Resolution::Complete))
1228 .expect("response dropped");
1229 assert_eq!(fetcher.calls(), 2);
1230 });
1231 }
1232
1233 #[test]
1234 fn dropped_verdict_hands_response_to_late_subscriber() {
1235 Runner::default().start(|context| async move {
1236 let fetcher = MockFetcher::default();
1237 fetcher.push(1, Some(Bytes::from_static(b"unjudged")));
1238 let consumer = MockConsumer::default();
1239 let mut resolver =
1240 start_resolver(context.child("resolver"), fetcher.clone(), consumer.clone());
1241
1242 assert!(
1243 resolver
1244 .fetch(Fetch {
1245 key: 1,
1246 subscriber: 10,
1247 span: tracing::Span::none(),
1248 })
1249 .accepted()
1250 );
1251 let first = wait_for_delivery(&context, &consumer).await;
1252 assert_eq!(first.value, Bytes::from_static(b"unjudged"));
1253
1254 assert!(
1258 resolver
1259 .fetch(Fetch {
1260 key: 1,
1261 subscriber: 11,
1262 span: tracing::Span::none(),
1263 })
1264 .accepted()
1265 );
1266 context.sleep(Duration::from_millis(10)).await;
1267 drop(first);
1268 let second = wait_for_delivery(&context, &consumer).await;
1269 assert_eq!(
1270 second
1271 .delivery
1272 .subscribers
1273 .iter()
1274 .map(|(subscriber, _)| *subscriber)
1275 .collect::<Vec<_>>(),
1276 vec![11]
1277 );
1278 assert_eq!(second.value, Bytes::from_static(b"unjudged"));
1279 second
1280 .response
1281 .send(Outcome::Resolved(Resolution::Complete))
1282 .expect("response dropped");
1283
1284 context.sleep(RETRY_DELAY + Duration::from_millis(10)).await;
1285 assert_eq!(fetcher.calls(), 1);
1286 assert_eq!(consumer.len(), 0);
1287 });
1288 }
1289
1290 #[test]
1291 fn accepted_redelivery_rejection_does_not_refetch() {
1292 Runner::default().start(|context| async move {
1293 let fetcher = MockFetcher::default();
1294 fetcher.push(1, Some(Bytes::from_static(b"value")));
1295 let consumer = MockConsumer::default();
1296 let mut resolver =
1297 start_resolver(context.child("resolver"), fetcher.clone(), consumer.clone());
1298
1299 assert!(
1300 resolver
1301 .fetch(Fetch {
1302 key: 1,
1303 subscriber: 10,
1304 span: tracing::Span::none(),
1305 })
1306 .accepted()
1307 );
1308 let first = wait_for_delivery(&context, &consumer).await;
1309
1310 assert!(
1311 resolver
1312 .fetch(Fetch {
1313 key: 1,
1314 subscriber: 11,
1315 span: tracing::Span::none(),
1316 })
1317 .accepted()
1318 );
1319 context.sleep(Duration::from_millis(10)).await;
1320 first
1321 .response
1322 .send(Outcome::Resolved(Resolution::Complete))
1323 .expect("response dropped");
1324
1325 let second = wait_for_delivery(&context, &consumer).await;
1326 second
1327 .response
1328 .send(Outcome::Invalid)
1329 .expect("response dropped");
1330
1331 context.sleep(RETRY_DELAY + Duration::from_millis(10)).await;
1332 assert_eq!(fetcher.calls(), 1);
1333 assert_eq!(consumer.len(), 0);
1334 });
1335 }
1336
1337 #[test]
1338 fn retain_prunes_active_fetch_subscribers() {
1339 Runner::default().start(|context| async move {
1340 let (fetcher, started, response) = BlockingFetcher::new();
1341 let consumer = MockConsumer::default();
1342 let mut resolver = start_resolver(context.child("resolver"), fetcher, consumer.clone());
1343
1344 assert!(
1345 resolver
1346 .fetch(Fetch {
1347 key: 1,
1348 subscriber: 10,
1349 span: tracing::Span::none(),
1350 })
1351 .accepted()
1352 );
1353 assert!(
1354 resolver
1355 .fetch(Fetch {
1356 key: 1,
1357 subscriber: 11,
1358 span: tracing::Span::none(),
1359 })
1360 .accepted()
1361 );
1362 started.await.expect("fetch did not start");
1363 assert!(
1364 resolver
1365 .retain(|_, subscriber| *subscriber == 11)
1366 .accepted()
1367 );
1368 context.sleep(Duration::from_millis(10)).await;
1369 response
1370 .send(Some(Bytes::from_static(b"value")))
1371 .expect("fetcher dropped");
1372
1373 let delivery = wait_for_delivery(&context, &consumer).await;
1374 assert_eq!(
1375 delivery
1376 .delivery
1377 .subscribers
1378 .iter()
1379 .map(|(subscriber, _)| *subscriber)
1380 .collect::<Vec<_>>(),
1381 vec![11]
1382 );
1383 delivery
1384 .response
1385 .send(Outcome::Resolved(Resolution::Complete))
1386 .expect("response dropped");
1387 });
1388 }
1389
1390 #[test]
1391 fn retain_drops_last_subscriber_aborts_active_fetch() {
1392 Runner::default().start(|context| async move {
1393 let (fetcher, started, response) = BlockingFetcher::new();
1394 let consumer = MockConsumer::default();
1395 let mut resolver = start_resolver(context.child("resolver"), fetcher, consumer.clone());
1396
1397 assert!(
1398 resolver
1399 .fetch(Fetch {
1400 key: 1,
1401 subscriber: 10,
1402 span: tracing::Span::none(),
1403 })
1404 .accepted()
1405 );
1406 started.await.expect("fetch did not start");
1407 assert!(resolver.retain(|_, _| false).accepted());
1408 context.sleep(Duration::from_millis(10)).await;
1409
1410 assert!(
1411 response.send(Some(Bytes::from_static(b"value"))).is_err(),
1412 "fetch future should be aborted after its last subscriber is pruned"
1413 );
1414 context.sleep(RETRY_DELAY + Duration::from_millis(10)).await;
1415 assert_eq!(consumer.len(), 0);
1416 });
1417 }
1418
1419 #[test]
1420 fn retain_drops_last_subscriber_aborts_active_delivery() {
1421 Runner::default().start(|context| async move {
1422 let fetcher = MockFetcher::default();
1423 fetcher.push(1, Some(Bytes::from_static(b"value")));
1424 let consumer = MockConsumer::default();
1425 let mut resolver =
1426 start_resolver(context.child("resolver"), fetcher.clone(), consumer.clone());
1427
1428 assert!(
1429 resolver
1430 .fetch(Fetch {
1431 key: 1,
1432 subscriber: 10,
1433 span: tracing::Span::none(),
1434 })
1435 .accepted()
1436 );
1437 let delivery = wait_for_delivery(&context, &consumer).await;
1438 assert!(resolver.retain(|_, _| false).accepted());
1439 context.sleep(Duration::from_millis(10)).await;
1440
1441 assert!(
1442 delivery.response.send(Outcome::Invalid).is_err(),
1443 "delivery future should be aborted after its last subscriber is pruned"
1444 );
1445 context.sleep(RETRY_DELAY + Duration::from_millis(10)).await;
1446 assert_eq!(fetcher.calls(), 1);
1447 assert_eq!(consumer.len(), 0);
1448 });
1449 }
1450
1451 #[test]
1452 fn targeted_fetch_uses_same_opaque_fetch_path() {
1453 Runner::default().start(|context| async move {
1454 let fetcher = MockFetcher::default();
1455 fetcher.push(1, Some(Bytes::from_static(b"value")));
1456 let consumer = MockConsumer::default();
1457 let mut resolver =
1458 start_resolver(context.child("resolver"), fetcher.clone(), consumer.clone());
1459 let target = PrivateKey::from_seed(0).public_key();
1460
1461 assert!(
1462 resolver
1463 .fetch_targeted(
1464 Fetch {
1465 key: 1,
1466 subscriber: 10,
1467 span: tracing::Span::none(),
1468 },
1469 non_empty_vec![target]
1470 )
1471 .accepted()
1472 );
1473 let delivery = wait_for_delivery(&context, &consumer).await;
1474 assert_eq!(delivery.value, Bytes::from_static(b"value"));
1475 delivery
1476 .response
1477 .send(Outcome::Resolved(Resolution::Complete))
1478 .expect("response dropped");
1479 assert_eq!(fetcher.calls(), 1);
1480 });
1481 }
1482}