Skip to main content

commonware_resolver/
opaque.rs

1//! Resolve keys from an opaque asynchronous fetcher.
2//!
3//! This module owns the generic resolver actor used when fetching data only
4//! requires asking an application-provided source for raw bytes or objects.
5//! Implementations provide [`Fetcher::fetch`]; this module handles request
6//! coalescing, retain pruning, retry scheduling, consumer delivery, and
7//! cached-response redelivery. An ignored consumer outcome retires the key
8//! without accepting the value or retrying the source. A verdict the consumer
9//! drops without answering hands the response to the remaining subscribers, or
10//! retires the key when none remain.
11//!
12//! Target hints supplied through [`crate::TargetedResolver::fetch_targeted`] and
13//! [`crate::TargetedResolver::fetch_all_targeted`] are ignored because opaque
14//! fetchers do not have peer-specific routing.
15
16use 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
44/// Fetches raw values for resolver keys.
45pub trait Fetcher {
46    /// Key requested by the resolver.
47    type Key: Span;
48
49    /// Raw value delivered to the consumer for validation.
50    type Value;
51
52    /// Fetch the value for `key`.
53    ///
54    /// Return `None` for transient failures, missing data, or unexpected source
55    /// responses. The resolver will retry while the key still has retained
56    /// subscribers.
57    fn fetch(&self, key: Self::Key) -> impl Future<Output = Option<Self::Value>> + Send;
58}
59
60/// Handle to an opaque-fetcher resolver actor.
61pub 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
168/// Spawn an opaque-fetcher resolver actor.
169pub 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
196/// Actor that coalesces opaque fetches, retries failures, and delivers accepted values.
197struct 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
216/// Current attempt for a tracked key.
217///
218/// The delivery tracker holds it as the key's state, so removing the key's
219/// delivery entry drops the attempt and aborts any active fetch.
220enum Attempt {
221    /// Fetch future is active for this key.
222    Fetching { _aborter: Aborter },
223
224    /// Consumer validation is active for this key.
225    Delivering,
226
227    /// Fetch is sleeping until the retry deadline.
228    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    /// Apply a mailbox message to active resolver state.
292    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    /// Add subscribers for a key and start the first fetch if needed.
304    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    /// Prune subscribers, deliveries, active fetches, and scheduled retries.
320    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    /// Spawn one fetch for `key` and return the attempt that owns it.
334    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    /// Deliver a fetched value to currently retained subscribers.
341    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    /// Deliver the cached response to subscribers that arrived later.
362    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    /// Deliver fetched values or schedule a retry when the source had no data.
370    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    /// Complete, redeliver, or retry a key after consumer validation.
385    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        // Count useful source responses once, even if their fetch was canceled.
399        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        // A dropped verdict says nothing about the response, only that the consumer
411        // did not judge it for these subscribers. Hand the response to the
412        // remaining subscribers, or retire the key when none remain.
413        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        // Resolution reports demand within the delivered subscriber snapshot.
429        // Ignored cancels demand for the entire key, including later subscribers.
430        match outcome {
431            Outcome::Resolved(Resolution::Complete) => {
432                let remaining = self
433                    .subscribers
434                    .remove_delivered(&key, delivered.map_into(|(subscriber, _)| subscriber));
435
436                // The first accepted response is reused for subscribers that joined
437                // while validation was pending, avoiding a duplicate source fetch
438                // for the same key.
439                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                // Valid data can leave subscribers unresolved. Discard it and retry
451                // without recounting an accepted response.
452                self.deliveries.discard_response(&key);
453                self.schedule_retry(key);
454            }
455            Outcome::Invalid => {
456                // A cached response already satisfied at least one subscriber. Treat a
457                // later rejection during redelivery as stale application feedback rather
458                // than re-fetching data that was accepted once.
459                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                // Subscribers still need valid data, so discard the rejected response
470                // and schedule another attempt.
471                warn!(?key, "consumer rejected opaque resolver delivery");
472                self.deliveries.discard_response(&key);
473                self.schedule_retry(key);
474            }
475            Outcome::Ignored => {
476                // The consumer no longer needs this key. Retire it without accepting the
477                // response or scheduling another source fetch.
478                self.fetch.inc(Status::Dropped);
479                self.subscribers.remove(&key);
480                self.deliveries.remove(&key);
481            }
482        }
483    }
484
485    /// Schedule the next fetch attempt for `key`.
486    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    /// Start all retry attempts whose deadlines have passed.
497    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    /// Run the user-supplied fetcher and tag the result with its key.
515    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                // Add two late subscribers while the original snapshot is being judged.
707                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                // Acceptance reuses the same value for the late snapshot and counts it once.
746                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                    // A fresh fetch confirms the cached verdict was processed without
777                    // another count. The new response can earn a separate success.
778                    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                // A value becomes available only after two failed fetches.
818                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                // A missing value schedules a retry for the original request.
839                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                // Cancel halfway to its deadline; a replacement gets a later retry deadline.
845                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                // Reaching the canceled deadline must not start another fetch.
855                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                // At the later deadline, only a reinserted key may fetch again.
861                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    /// Canceled verdicts retain source feedback without changing a replacement's demand.
879    #[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                    // Hold a verdict from either a fresh response or an accepted redelivery.
891                    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                    // Remove the old entry and reuse its key and subscriber before polling
928                    // the accepted verdict. The new response stays unjudged throughout.
929                    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                    // No old outcome may retire subscribers, discard the new response,
942                    // or schedule another attempt for the replacement.
943                    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                    // A late subscriber receives the replacement's cached response
979                    // after its first verdict, without another source fetch.
980                    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            // The first attempt finds nothing, so the retry waits for its deadline.
1025            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            // A valid response that resolves neither subscriber counts as Ambiguous.
1073            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            // Resolving one subscriber earns success while the remaining subscriber retries.
1086            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            // A second useful response completes the fetch and contributes its own success.
1109            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            // Ignoring retires the old fetch rather than leaving a tombstone.
1158            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 the verdict, as a consumer does when it stops with the delivery
1200            // still queued. No one is waiting on the key, so the fetch is retired
1201            // rather than retried.
1202            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            // The retired key is not deduplicated against, so a fresh fetch runs.
1214            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            // A late subscriber joins while the first delivery is unjudged, then
1255            // that delivery's verdict is dropped. The late subscriber is handed
1256            // the same response without another source fetch.
1257            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}