1mod keys;
20mod post;
21
22pub use keys::CachingSenderKeys;
23pub use post::{DeliveryError, deliver, deliver_with_headers, permanent_status};
24
25use crate::client::Client;
26use crate::portable::ApUri;
27use crate::queue::{Job, Queue, QueueError, RetryPolicy};
28use crate::sig::{Scheme, SenderKey};
29use futures_util::StreamExt as _;
30use futures_util::stream;
31use serde_json::{Value, json};
32use std::collections::{BTreeSet, HashMap};
33use std::future::Future;
34use std::sync::{Arc, Mutex};
35use std::time::{Duration, Instant};
36use tokio::sync::{Notify, Semaphore};
37use url::Url;
38
39pub trait SenderKeys: Send + Sync + 'static {
41 fn key(
43 &self,
44 sender: &str,
45 ) -> impl Future<Output = Result<Option<SenderKey>, QueueError>> + Send;
46
47 fn gone(&self, sender: &str) -> impl Future<Output = bool> + Send {
54 let _ = sender;
55 std::future::ready(false)
56 }
57}
58
59impl SenderKeys for SenderKey {
61 fn key(
62 &self,
63 _sender: &str,
64 ) -> impl Future<Output = Result<Option<SenderKey>, QueueError>> + Send {
65 std::future::ready(Ok(Some(self.clone())))
66 }
67}
68
69impl<S: std::hash::BuildHasher + Send + Sync + 'static> SenderKeys
71 for HashMap<String, SenderKey, S>
72{
73 fn key(
74 &self,
75 sender: &str,
76 ) -> impl Future<Output = Result<Option<SenderKey>, QueueError>> + Send {
77 std::future::ready(Ok(self.get(sender).cloned()))
78 }
79}
80
81impl SenderKeys for std::collections::BTreeMap<String, SenderKey> {
83 fn key(
84 &self,
85 sender: &str,
86 ) -> impl Future<Output = Result<Option<SenderKey>, QueueError>> + Send {
87 std::future::ready(Ok(self.get(sender).cloned()))
88 }
89}
90
91#[derive(Clone, Debug)]
93pub struct DeliveryFailure {
94 pub inbox: Url,
95 pub sender: String,
96 pub status: Option<u16>,
99 pub error: String,
100 pub tag: Option<String>,
102 pub deadline: bool,
105}
106
107#[derive(Clone, Debug)]
109pub struct DeliveryAttempt {
110 pub inbox: Url,
112 pub sender: String,
113 pub outcome: AttemptOutcome,
114}
115
116#[derive(Clone, Debug, Eq, PartialEq)]
118pub enum AttemptOutcome {
119 Delivered,
121 Failed {
125 status: Option<u16>,
127 permanent: bool,
130 error: String,
131 },
132 Held,
135}
136
137#[derive(Clone, Copy, Debug, Eq, PartialEq)]
162pub struct CircuitBreaker {
163 pub threshold: u32,
165 pub cool_off: Duration,
168 pub scope: BreakerScope,
170}
171
172#[derive(Clone, Copy, Debug, Eq, PartialEq)]
174pub enum BreakerScope {
175 Host,
177 Inbox,
179}
180
181impl Default for CircuitBreaker {
182 fn default() -> Self {
185 Self {
186 threshold: 10,
187 cool_off: Duration::from_secs(60),
188 scope: BreakerScope::Host,
189 }
190 }
191}
192
193#[derive(Debug)]
195pub enum Admission {
196 Pass,
198 Probe(Probe),
201 Held(Duration),
204}
205
206pub struct Probe(Box<dyn std::any::Any + Send + Sync>);
210
211impl Probe {
212 #[must_use]
215 pub fn new(guard: impl std::any::Any + Send + Sync) -> Self {
216 Self(Box::new(guard))
217 }
218
219 #[must_use]
221 pub fn guard(&self) -> &(dyn std::any::Any + Send + Sync) {
222 &*self.0
223 }
224}
225
226impl std::fmt::Debug for Probe {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 f.write_str("Probe")
229 }
230}
231
232pub type BreakerFuture<'a, T> = std::pin::Pin<Box<dyn Future<Output = T> + Send + 'a>>;
234
235pub trait BreakerStore: Send + Sync + 'static {
239 fn admit<'a>(
243 &'a self,
244 breaker: &'a CircuitBreaker,
245 key: &'a str,
246 ) -> BreakerFuture<'a, Admission>;
247
248 fn record<'a>(
252 &'a self,
253 breaker: &'a CircuitBreaker,
254 key: &'a str,
255 failed: bool,
256 probe: Option<Probe>,
257 ) -> BreakerFuture<'a, ()>;
258}
259
260#[derive(Clone, Copy, Debug)]
262struct Breaker {
263 failures: u32,
265 open_until: Option<Instant>,
267 probe: Option<(u64, Instant)>,
269 last: Instant,
271}
272
273#[derive(Default)]
275pub struct MemoryBreakers {
276 breakers: Mutex<HashMap<String, Breaker>>,
277 probes: std::sync::atomic::AtomicU64,
278}
279
280impl MemoryBreakers {
281 fn admit_now(&self, breaker: &CircuitBreaker, key: &str) -> Admission {
282 let mut breakers = self.breakers.lock().expect("breaker lock");
283 let Some(state) = breakers.get_mut(key) else {
284 return Admission::Pass;
285 };
286 let now = Instant::now();
287 let Some(open_until) = state.open_until else {
288 return Admission::Pass;
289 };
290 if now < open_until {
291 return Admission::Held(open_until - now);
292 }
293 if let Some((_, until)) = state.probe
294 && now < until
295 {
296 return Admission::Held(until - now);
297 }
298 let id = self
299 .probes
300 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
301 state.probe = Some((id, now + breaker.cool_off));
302 state.failures = 0;
304 state.last = now;
305 Admission::Probe(Probe::new(id))
306 }
307
308 fn record_now(&self, breaker: &CircuitBreaker, key: &str, failed: bool, probe: Option<&Probe>) {
309 let mut breakers = self.breakers.lock().expect("breaker lock");
310 let now = Instant::now();
311 if breakers.len() >= 4096 {
314 let forget = (breaker.cool_off * 2).max(Duration::from_secs(600));
315 breakers.retain(|_, state| state.last.elapsed() < forget);
316 }
317 if let Some(probe) = probe {
318 let Some(state) = breakers.get_mut(key) else {
319 return;
320 };
321 if failed {
322 state.open_until = Some(now + breaker.cool_off);
323 } else {
324 state.open_until = None;
325 }
326 state.last = now;
327 let id = probe.guard().downcast_ref::<u64>().copied();
328 if state.probe.map(|(held, _)| held) == id {
329 state.probe = None;
330 }
331 if state.open_until.is_none() && state.failures == 0 {
332 breakers.remove(key);
333 }
334 return;
335 }
336 if !failed {
337 if let Some(state) = breakers.get_mut(key) {
338 state.failures = 0;
339 state.last = now;
340 if state.open_until.is_none() {
341 breakers.remove(key);
342 }
343 }
344 return;
345 }
346 let state = breakers.entry(key.to_owned()).or_insert(Breaker {
347 failures: 0,
348 open_until: None,
349 probe: None,
350 last: now,
351 });
352 state.failures = state.failures.saturating_add(1);
353 state.last = now;
354 if state.failures >= breaker.threshold.max(1) {
355 state.open_until = Some(now + breaker.cool_off);
356 }
357 }
358}
359
360impl BreakerStore for MemoryBreakers {
361 fn admit<'a>(
362 &'a self,
363 breaker: &'a CircuitBreaker,
364 key: &'a str,
365 ) -> BreakerFuture<'a, Admission> {
366 Box::pin(std::future::ready(self.admit_now(breaker, key)))
367 }
368
369 fn record<'a>(
370 &'a self,
371 breaker: &'a CircuitBreaker,
372 key: &'a str,
373 failed: bool,
374 probe: Option<Probe>,
375 ) -> BreakerFuture<'a, ()> {
376 self.record_now(breaker, key, failed, probe.as_ref());
377 Box::pin(std::future::ready(()))
378 }
379}
380
381#[derive(Clone, Debug)]
384pub struct Settled {
385 pub inbox: Url,
386 pub sender: String,
387 pub tag: Option<String>,
389 pub outcome: SettledOutcome,
390}
391
392#[derive(Clone, Debug, Eq, PartialEq)]
394pub enum SettledOutcome {
395 Delivered,
397 Refused { status: Option<u16> },
400 Skipped,
402}
403
404#[derive(Clone, Debug)]
406pub struct DelivererConfig {
407 pub queue: String,
409 pub retry: RetryPolicy,
410 pub batch: usize,
412 pub lease: Duration,
414 pub concurrency: usize,
416 pub per_host: usize,
418 pub first_scheme: Scheme,
420 pub idle_poll: Duration,
425 pub shared_limit: Option<Arc<Semaphore>>,
429 pub priority: Option<Priority>,
437 pub breaker: Option<CircuitBreaker>,
440 pub permanent: fn(u16) -> bool,
444 pub low_priority: Option<String>,
448 pub permanent_if_sender_gone: fn(u16) -> bool,
451 pub wait_as_asked: bool,
456}
457
458#[derive(Clone, Debug)]
460pub struct Priority {
461 pub queue: String,
463 pub max_inboxes: usize,
465}
466
467impl Default for DelivererConfig {
468 fn default() -> Self {
469 Self {
470 queue: "delivery".to_owned(),
471 retry: RetryPolicy::default(),
472 batch: 64,
473 lease: Duration::from_secs(300),
474 concurrency: 16,
475 per_host: 2,
476 first_scheme: Scheme::DraftCavage,
477 idle_poll: Duration::from_secs(30),
478 shared_limit: None,
479 priority: None,
480 breaker: Some(CircuitBreaker::default()),
481 permanent: permanent_status,
482 low_priority: None,
483 permanent_if_sender_gone: |_| false,
484 wait_as_asked: true,
485 }
486 }
487}
488
489#[derive(Clone, Debug)]
492pub struct PortableInbox {
493 pub inbox: ApUri,
494 pub gateways: Vec<Url>,
495}
496
497#[derive(Clone, Debug, Default)]
501pub struct Batch {
502 pub tag: Option<String>,
505 pub deadline: Option<std::time::SystemTime>,
509 pub ordering_key: Option<String>,
515 pub max_attempts: Option<u32>,
518 pub low_priority: bool,
520 pub synchronize_collection: bool,
524}
525
526struct Delivery {
528 activity: Value,
529 inbox: Url,
531 via: Vec<Url>,
534 sender: String,
535 tag: Option<String>,
536 deadline: Option<u64>,
538 max_attempts: Option<u32>,
539 synchronize_collection: bool,
540}
541
542impl Delivery {
543 fn new(activity: &Value, inbox: Url, via: Vec<Url>, sender: &str, batch: &Batch) -> Self {
544 Self {
545 activity: activity.clone(),
546 inbox,
547 via,
548 sender: sender.to_owned(),
549 tag: batch.tag.clone(),
550 deadline: batch.deadline.map(unix),
551 max_attempts: batch.max_attempts,
552 synchronize_collection: batch.synchronize_collection,
553 }
554 }
555
556 fn expired(&self, now: std::time::SystemTime) -> bool {
558 self.deadline.is_some_and(|deadline| unix(now) >= deadline)
559 }
560
561 fn payload(&self) -> Value {
562 let mut payload = json!({
563 "activity": self.activity,
564 "inbox": self.inbox.as_str(),
565 "sender": self.sender,
566 });
567 if !self.via.is_empty() {
568 payload["via"] = self.via.iter().map(Url::as_str).collect::<Vec<_>>().into();
569 }
570 if let Some(tag) = &self.tag {
571 payload["tag"] = tag.as_str().into();
572 }
573 if let Some(deadline) = self.deadline {
574 payload["deadline"] = deadline.into();
575 }
576 if let Some(max_attempts) = self.max_attempts {
577 payload["max_attempts"] = max_attempts.into();
578 }
579 if self.synchronize_collection {
580 payload["synchronize_collection"] = true.into();
581 }
582 payload
583 }
584
585 fn from_payload(payload: &Value) -> Option<Self> {
586 let via = match payload.get("via") {
587 Some(Value::Array(via)) => via
588 .iter()
589 .map(|url| Url::parse(url.as_str()?).ok())
590 .collect::<Option<Vec<_>>>()?,
591 _ => Vec::new(),
592 };
593 Some(Self {
594 activity: payload.get("activity")?.clone(),
595 inbox: Url::parse(payload.get("inbox")?.as_str()?).ok()?,
596 via,
597 sender: payload.get("sender")?.as_str()?.to_owned(),
598 tag: payload
599 .get("tag")
600 .and_then(Value::as_str)
601 .map(str::to_owned),
602 deadline: payload.get("deadline").and_then(Value::as_u64),
603 max_attempts: payload
604 .get("max_attempts")
605 .and_then(Value::as_u64)
606 .and_then(|n| u32::try_from(n).ok()),
607 synchronize_collection: payload
608 .get("synchronize_collection")
609 .and_then(Value::as_bool)
610 .unwrap_or(false),
611 })
612 }
613
614 fn targets(&self) -> Vec<&Url> {
616 if self.via.is_empty() {
617 vec![&self.inbox]
618 } else {
619 self.via.iter().collect()
620 }
621 }
622}
623
624type FailureHandler = Arc<dyn Fn(&DeliveryFailure) + Send + Sync>;
625type AttemptHandler = Arc<dyn Fn(&DeliveryAttempt) + Send + Sync>;
626type SkipPredicate = Arc<dyn Fn(&Url, &Value) -> bool + Send + Sync>;
627type SynchronizationFn = Arc<
628 dyn Fn(String, Url) -> std::pin::Pin<Box<dyn Future<Output = Option<String>> + Send>>
629 + Send
630 + Sync,
631>;
632type SettledHandler =
633 Arc<dyn Fn(Settled) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>;
634
635pub struct Deliverer<Q, K> {
637 queue: Q,
638 keys: K,
639 client: Client,
640 config: DelivererConfig,
641 wake: Notify,
642 schemes: Mutex<HashMap<String, Scheme>>,
644 hosts: Mutex<HashMap<String, Arc<Semaphore>>>,
645 breakers: Arc<dyn BreakerStore>,
647 on_failure: Option<FailureHandler>,
648 on_attempt: Option<AttemptHandler>,
649 on_settled: Option<SettledHandler>,
650 skip_if: Option<SkipPredicate>,
651 synchronization: Option<SynchronizationFn>,
652}
653
654impl<Q: Queue, K: SenderKeys> Deliverer<Q, K> {
655 pub fn new(queue: Q, keys: K, client: Client, config: DelivererConfig) -> Self {
656 Self {
657 queue,
658 keys,
659 client,
660 config,
661 wake: Notify::new(),
662 schemes: Mutex::new(HashMap::new()),
663 hosts: Mutex::new(HashMap::new()),
664 breakers: Arc::new(MemoryBreakers::default()),
665 on_failure: None,
666 on_attempt: None,
667 on_settled: None,
668 skip_if: None,
669 synchronization: None,
670 }
671 }
672
673 #[must_use]
681 pub fn collection_synchronization<F, Fut>(mut self, header: F) -> Self
682 where
683 F: Fn(String, Url) -> Fut + Send + Sync + 'static,
684 Fut: Future<Output = Option<String>> + Send + 'static,
685 {
686 self.synchronization = Some(Arc::new(move |sender, inbox| {
687 Box::pin(header(sender, inbox))
688 }));
689 self
690 }
691
692 #[must_use]
695 pub fn breaker_store(mut self, store: impl BreakerStore) -> Self {
696 self.breakers = Arc::new(store);
697 self
698 }
699
700 #[must_use]
708 pub fn on_settled<F, Fut>(mut self, handler: F) -> Self
709 where
710 F: Fn(Settled) -> Fut + Send + Sync + 'static,
711 Fut: Future<Output = ()> + Send + 'static,
712 {
713 self.on_settled = Some(Arc::new(move |settled| Box::pin(handler(settled))));
714 self
715 }
716
717 #[must_use]
725 pub fn skip_if(mut self, skip: impl Fn(&Url, &Value) -> bool + Send + Sync + 'static) -> Self {
726 self.skip_if = Some(Arc::new(skip));
727 self
728 }
729
730 #[must_use]
734 pub fn on_attempt(
735 mut self,
736 handler: impl Fn(&DeliveryAttempt) + Send + Sync + 'static,
737 ) -> Self {
738 self.on_attempt = Some(Arc::new(handler));
739 self
740 }
741
742 #[must_use]
745 pub fn on_failure(
746 mut self,
747 handler: impl Fn(&DeliveryFailure) + Send + Sync + 'static,
748 ) -> Self {
749 self.on_failure = Some(Arc::new(handler));
750 self
751 }
752
753 pub fn queue(&self) -> &Q {
755 &self.queue
756 }
757
758 pub async fn send(
764 &self,
765 sender: &str,
766 activity: &Value,
767 inboxes: impl IntoIterator<Item = Url>,
768 ) -> Result<(), QueueError> {
769 self.send_batch(sender, activity, inboxes, &Batch::default())
770 .await
771 }
772
773 pub async fn send_batch(
780 &self,
781 sender: &str,
782 activity: &Value,
783 inboxes: impl IntoIterator<Item = Url>,
784 batch: &Batch,
785 ) -> Result<(), QueueError> {
786 let inboxes: BTreeSet<Url> = inboxes.into_iter().collect();
787 if inboxes.is_empty() {
788 return Ok(());
789 }
790 let queue = match (&self.config.low_priority, batch.low_priority) {
791 (Some(low), true) => low.as_str(),
792 _ => self.queue_for(inboxes.len()),
793 };
794 match &batch.ordering_key {
795 Some(key) => {
796 let jobs = inboxes
797 .into_iter()
798 .map(|inbox| {
799 let ordering = format!("{key} {inbox}");
801 let payload =
802 Delivery::new(activity, inbox, Vec::new(), sender, batch).payload();
803 (ordering, payload)
804 })
805 .collect();
806 self.queue.enqueue_ordered(queue, jobs).await?;
807 }
808 None => {
809 let payloads = inboxes
810 .into_iter()
811 .map(|inbox| {
812 Delivery::new(activity, inbox, Vec::new(), sender, batch).payload()
813 })
814 .collect();
815 self.queue.enqueue(queue, payloads).await?;
816 }
817 }
818 self.wake.notify_one();
819 Ok(())
820 }
821
822 pub async fn send_portable(
831 &self,
832 sender: &str,
833 activity: &Value,
834 inboxes: impl IntoIterator<Item = PortableInbox>,
835 ) -> Result<(), QueueError> {
836 let mut seen = BTreeSet::new();
837 let mut payloads = Vec::new();
838 for PortableInbox { inbox, gateways } in inboxes {
839 if !seen.insert(inbox.canonical()) {
840 continue;
841 }
842 let mut via: Vec<Url> = Vec::new();
843 for gateway in gateways
844 .iter()
845 .filter(|gateway| crate::portable::is_gateway(gateway.as_str()))
846 {
847 if let Ok(target) = Url::parse(&inbox.at_gateway(gateway.as_str()))
848 && !via.contains(&target)
849 {
850 via.push(target);
851 }
852 }
853 via.truncate(crate::portable::MAX_GATEWAYS);
854 let Ok(encoded) = Url::parse(&inbox.encoded()) else {
855 continue;
856 };
857 if via.is_empty() {
858 continue;
859 }
860 payloads
861 .push(Delivery::new(activity, encoded, via, sender, &Batch::default()).payload());
862 }
863 if payloads.is_empty() {
864 return Ok(());
865 }
866 self.queue
867 .enqueue(self.queue_for(payloads.len()), payloads)
868 .await?;
869 self.wake.notify_one();
870 Ok(())
871 }
872
873 pub async fn run(&self) {
879 self.run_until(std::future::pending()).await;
880 }
881
882 pub async fn run_until(&self, stop: impl Future<Output = ()>) {
895 tokio::pin!(stop);
896 let concurrency = self.config.concurrency.max(1);
897 let refill = (concurrency / 4).max(1);
899 let mut in_flight = stream::FuturesUnordered::new();
900 let mut claiming: Option<(usize, Claim<'_>)> = None;
906 let mut stopping = false;
907 let mut dry = false;
909 loop {
910 let free = concurrency.saturating_sub(in_flight.len());
911 if claiming.is_none()
916 && !stopping
917 && !dry
918 && free > 0
919 && (in_flight.is_empty() || free >= refill)
920 {
921 let asked = free.min(self.config.batch.max(1));
922 claiming = Some((asked, Box::pin(self.claim_due(asked))));
923 }
924 if in_flight.is_empty() && claiming.is_none() {
925 if stopping {
926 return;
927 }
928 tokio::select! {
929 () = &mut stop => return,
930 () = self.idle() => {}
931 }
932 dry = false;
933 continue;
934 }
935 tokio::select! {
936 biased;
937 () = &mut stop, if !stopping => stopping = true,
938 result = poll_claim(&mut claiming), if claiming.is_some() => {
939 let (asked, _) = claiming.take().expect("a claim was under way");
940 match result {
941 Ok(jobs) => {
944 dry = jobs.len() < asked;
945 for job in jobs {
946 in_flight.push(self.process(job));
947 }
948 }
949 Err(_) if in_flight.is_empty() => {
950 tokio::select! {
951 () = &mut stop, if !stopping => return,
952 () = tokio::time::sleep(Duration::from_secs(5)) => {}
953 }
954 }
955 Err(_) => dry = true,
956 }
957 }
958 Some(()) = in_flight.next(), if !in_flight.is_empty() => dry = false,
961 () = self.wake.notified(), if dry && !stopping && claiming.is_none() => dry = false,
964 }
965 }
966 }
967
968 pub async fn run_once(&self) -> Result<usize, QueueError> {
974 let batch = self.claim_due(self.config.batch).await?;
975 let count = batch.len();
976 stream::iter(batch)
977 .for_each_concurrent(self.config.concurrency, |job| self.process(job))
978 .await;
979 Ok(count)
980 }
981
982 fn queue_for(&self, inboxes: usize) -> &str {
984 match &self.config.priority {
985 Some(priority) if inboxes <= priority.max_inboxes => &priority.queue,
986 _ => &self.config.queue,
987 }
988 }
989
990 async fn claim_due(&self, limit: usize) -> Result<Vec<Job>, QueueError> {
992 let mut jobs = match &self.config.priority {
993 Some(priority) => {
994 self.queue
995 .claim(&priority.queue, limit, self.config.lease)
996 .await?
997 }
998 None => Vec::new(),
999 };
1000 if jobs.len() < limit {
1001 jobs.extend(
1002 self.queue
1003 .claim(&self.config.queue, limit - jobs.len(), self.config.lease)
1004 .await?,
1005 );
1006 }
1007 if let Some(low) = &self.config.low_priority
1008 && jobs.len() < limit
1009 {
1010 jobs.extend(
1011 self.queue
1012 .claim(low, limit - jobs.len(), self.config.lease)
1013 .await?,
1014 );
1015 }
1016 Ok(jobs)
1017 }
1018
1019 async fn idle(&self) {
1020 let mut due = self.queue.next_due(&self.config.queue).await.ok().flatten();
1021 let lanes = self
1022 .config
1023 .priority
1024 .iter()
1025 .map(|priority| priority.queue.as_str())
1026 .chain(self.config.low_priority.as_deref());
1027 for lane in lanes {
1028 let next = self.queue.next_due(lane).await.ok().flatten();
1029 due = match (due, next) {
1030 (Some(a), Some(b)) => Some(a.min(b)),
1031 (a, b) => a.or(b),
1032 };
1033 }
1034 let sleep = jitter(due.map_or(self.config.idle_poll, |due| due.min(self.config.idle_poll)));
1035 tokio::select! {
1036 () = tokio::time::sleep(sleep) => {}
1037 () = self.wake.notified() => {}
1038 }
1039 }
1040
1041 async fn process(&self, job: Job) {
1042 let Some(delivery) = Delivery::from_payload(&job.payload) else {
1043 let _ = self.queue.fail(&job.id, "not a delivery").await;
1044 return;
1045 };
1046 if delivery.expired(std::time::SystemTime::now()) {
1049 let _ = self
1050 .give_up(
1051 &job,
1052 &delivery,
1053 None,
1054 "the batch's deadline passed before it was sent",
1055 true,
1056 )
1057 .await;
1058 return;
1059 }
1060 if self
1061 .skip_if
1062 .as_ref()
1063 .is_some_and(|skip| skip(&delivery.inbox, &delivery.activity))
1064 {
1065 self.settle(&delivery, SettledOutcome::Skipped).await;
1066 let _ = self.queue.complete(&job.id).await;
1067 return;
1068 }
1069 let outcome = self.attempt(&delivery).await;
1070 let permanent = match &outcome {
1071 Ok(()) => false,
1072 Err(error) => self.ends(&delivery.sender, error).await,
1073 };
1074 if let Some(handler) = &self.on_attempt {
1075 handler(&DeliveryAttempt {
1076 inbox: delivery.inbox.clone(),
1077 sender: delivery.sender.clone(),
1078 outcome: match &outcome {
1079 Ok(()) => AttemptOutcome::Delivered,
1080 Err(DeliveryError::Held { .. }) => AttemptOutcome::Held,
1081 Err(error) => AttemptOutcome::Failed {
1082 status: error.status(),
1083 permanent,
1084 error: error.to_string(),
1085 },
1086 },
1087 });
1088 }
1089 let _ = match outcome {
1092 Ok(()) => {
1093 self.settle(&delivery, SettledOutcome::Delivered).await;
1094 self.queue.complete(&job.id).await
1095 }
1096 Err(error) => {
1097 if permanent {
1098 let status = error.status();
1099 self.settle(&delivery, SettledOutcome::Refused { status })
1100 .await;
1101 }
1102 self.record_failure(&job, &delivery, &error, permanent)
1103 .await
1104 }
1105 };
1106 }
1107
1108 async fn settle(&self, delivery: &Delivery, outcome: SettledOutcome) {
1109 if let Some(handler) = &self.on_settled {
1110 handler(Settled {
1111 inbox: delivery.inbox.clone(),
1112 sender: delivery.sender.clone(),
1113 tag: delivery.tag.clone(),
1114 outcome,
1115 })
1116 .await;
1117 }
1118 }
1119
1120 async fn ends(&self, sender: &str, error: &DeliveryError) -> bool {
1125 if error.is_permanent_by(self.config.permanent) {
1126 return true;
1127 }
1128 match error.status() {
1129 Some(status) if (self.config.permanent_if_sender_gone)(status) => {
1130 self.keys.gone(sender).await
1131 }
1132 _ => false,
1133 }
1134 }
1135
1136 async fn attempt(&self, delivery: &Delivery) -> Result<(), DeliveryError> {
1137 let key = match self.keys.key(&delivery.sender).await {
1138 Ok(Some(key)) => key,
1139 Ok(None) => {
1140 return Err(DeliveryError::Signing(format!(
1141 "no key for sender {}",
1142 delivery.sender
1143 )));
1144 }
1145 Err(error) => {
1148 return Err(DeliveryError::Unavailable(format!("key lookup: {error}")));
1149 }
1150 };
1151 let body = serde_json::to_vec(&delivery.activity)
1152 .map_err(|error| DeliveryError::Signing(error.to_string()))?;
1153 let mut extra = Vec::new();
1156 if delivery.synchronize_collection
1157 && let Some(synchronization) = &self.synchronization
1158 && let Some(header) =
1159 synchronization(delivery.sender.clone(), delivery.inbox.clone()).await
1160 {
1161 extra.push(("collection-synchronization".to_owned(), header));
1162 }
1163 let mut last = None;
1164 for target in delivery.targets() {
1165 match self
1166 .attempt_at(target, &body, &key, &delivery.sender, &extra)
1167 .await
1168 {
1169 Ok(()) => return Ok(()),
1170 Err(error) => last = Some(error),
1171 }
1172 }
1173 Err(last.unwrap_or_else(|| DeliveryError::Signing("nowhere to deliver".into())))
1174 }
1175
1176 async fn attempt_at(
1177 &self,
1178 target: &Url,
1179 body: &[u8],
1180 key: &SenderKey,
1181 sender: &str,
1182 extra: &[(String, String)],
1183 ) -> Result<(), DeliveryError> {
1184 let host = target.host_str().unwrap_or_default().to_owned();
1185 let breaker = self.config.breaker.map(|breaker| {
1186 let key = match breaker.scope {
1187 BreakerScope::Host => host.clone(),
1188 BreakerScope::Inbox => target.as_str().to_owned(),
1189 };
1190 (breaker, key)
1191 });
1192 let mut probe = None;
1193 if let Some((breaker, key)) = &breaker {
1194 match self.breakers.admit(breaker, key).await {
1195 Admission::Pass => {}
1196 Admission::Probe(taken) => probe = Some(taken),
1197 Admission::Held(retry_after) => {
1198 return Err(DeliveryError::Held { retry_after });
1199 }
1200 }
1201 }
1202 let permit = self.host_semaphore(&host).acquire_owned().await;
1203 let shared_permit = match &self.config.shared_limit {
1204 Some(limit) => Some(limit.clone().acquire_owned().await),
1205 None => None,
1206 };
1207 let first = self
1208 .schemes
1209 .lock()
1210 .expect("scheme lock")
1211 .get(&host)
1212 .copied()
1213 .unwrap_or(self.config.first_scheme);
1214 let result =
1215 post::deliver_with_headers(&self.client, target, body, key, first, extra).await;
1216 drop(shared_permit);
1217 drop(permit);
1218 if let Some((breaker, key)) = &breaker {
1219 let failed = match &result {
1220 Ok(_) => false,
1221 Err(error) => !self.ends(sender, error).await,
1222 };
1223 self.breakers.record(breaker, key, failed, probe).await;
1224 }
1225 let accepted = result?;
1226 self.schemes
1227 .lock()
1228 .expect("scheme lock")
1229 .insert(host, accepted);
1230 Ok(())
1231 }
1232
1233 fn host_semaphore(&self, host: &str) -> Arc<Semaphore> {
1234 self.hosts
1235 .lock()
1236 .expect("host lock")
1237 .entry(host.to_owned())
1238 .or_insert_with(|| Arc::new(Semaphore::new(self.config.per_host)))
1239 .clone()
1240 }
1241
1242 async fn record_failure(
1243 &self,
1244 job: &Job,
1245 delivery: &Delivery,
1246 error: &DeliveryError,
1247 permanent: bool,
1248 ) -> Result<(), QueueError> {
1249 let attempts = job.attempts + 1;
1250 let delay = if permanent || delivery.max_attempts.is_some_and(|max| attempts >= max) {
1251 None
1252 } else {
1253 self.config.retry.delay(attempts)
1254 };
1255 let message = error.to_string();
1256 match delay {
1257 Some(delay) => {
1258 let delay = match error.retry_after() {
1259 Some(asked) if self.config.wait_as_asked => asked.max(delay),
1260 _ => delay,
1261 };
1262 if delivery.expired(std::time::SystemTime::now() + delay) {
1266 let message = format!("the batch's deadline passed: {message}");
1267 return self
1268 .give_up(job, delivery, error.status(), &message, true)
1269 .await;
1270 }
1271 self.queue.retry(&job.id, delay, &message).await
1272 }
1273 None => {
1274 self.give_up(job, delivery, error.status(), &message, false)
1275 .await
1276 }
1277 }
1278 }
1279
1280 async fn give_up(
1282 &self,
1283 job: &Job,
1284 delivery: &Delivery,
1285 status: Option<u16>,
1286 message: &str,
1287 deadline: bool,
1288 ) -> Result<(), QueueError> {
1289 if let Some(handler) = &self.on_failure {
1290 handler(&DeliveryFailure {
1291 inbox: delivery.inbox.clone(),
1292 sender: delivery.sender.clone(),
1293 status,
1294 error: message.to_owned(),
1295 tag: delivery.tag.clone(),
1296 deadline,
1297 });
1298 }
1299 self.queue.fail(&job.id, message).await
1300 }
1301}
1302
1303fn unix(time: std::time::SystemTime) -> u64 {
1305 time.duration_since(std::time::UNIX_EPOCH)
1306 .map_or(0, |since| since.as_secs())
1307}
1308
1309fn jitter(duration: Duration) -> Duration {
1311 use std::hash::{BuildHasher as _, RandomState};
1312
1313 let random = RandomState::new().hash_one(std::time::SystemTime::now());
1316 let quarter = duration / 4;
1317 let cut = quarter.mul_f64((random % 1_000_000) as f64 / 1_000_000.0);
1318 duration.saturating_sub(cut)
1319}
1320
1321type Claim<'a> = std::pin::Pin<Box<dyn Future<Output = Result<Vec<Job>, QueueError>> + Send + 'a>>;
1323
1324async fn poll_claim(claiming: &mut Option<(usize, Claim<'_>)>) -> Result<Vec<Job>, QueueError> {
1327 match claiming {
1328 Some((_, claim)) => claim.as_mut().await,
1329 None => std::future::pending().await,
1330 }
1331}
1332
1333#[cfg(test)]
1334mod tests {
1335 use super::jitter;
1336 use std::time::Duration;
1337
1338 #[test]
1339 fn jitter_shortens_by_at_most_a_quarter() {
1340 let base = Duration::from_secs(300);
1341 for _ in 0..100 {
1342 let slept = jitter(base);
1343 assert!(slept <= base && slept >= base * 3 / 4, "{slept:?}");
1344 }
1345 }
1346
1347 mod breakers {
1348 use super::super::{Admission, BreakerScope, CircuitBreaker, MemoryBreakers};
1349 use std::time::Duration;
1350
1351 const BREAKER: CircuitBreaker = CircuitBreaker {
1352 threshold: 2,
1353 cool_off: Duration::from_millis(50),
1354 scope: BreakerScope::Inbox,
1355 };
1356
1357 fn open(breakers: &MemoryBreakers) {
1358 breakers.record_now(&BREAKER, "inbox", true, None);
1359 breakers.record_now(&BREAKER, "inbox", true, None);
1360 }
1361
1362 #[test]
1363 fn a_success_while_closed_starts_the_count_again() {
1364 let breakers = MemoryBreakers::default();
1365 breakers.record_now(&BREAKER, "inbox", true, None);
1366 breakers.record_now(&BREAKER, "inbox", false, None);
1367 breakers.record_now(&BREAKER, "inbox", true, None);
1368 assert!(matches!(
1369 breakers.admit_now(&BREAKER, "inbox"),
1370 Admission::Pass
1371 ));
1372 breakers.record_now(&BREAKER, "inbox", true, None);
1373 assert!(matches!(
1374 breakers.admit_now(&BREAKER, "inbox"),
1375 Admission::Held(_)
1376 ));
1377 }
1378
1379 #[test]
1380 fn a_success_sent_before_it_opened_does_not_close_it() {
1381 let breakers = MemoryBreakers::default();
1382 open(&breakers);
1383 breakers.record_now(&BREAKER, "inbox", false, None);
1384 assert!(matches!(
1385 breakers.admit_now(&BREAKER, "inbox"),
1386 Admission::Held(_)
1387 ));
1388 }
1389
1390 #[test]
1391 fn after_the_cool_off_one_probe_at_a_time_is_let_through() {
1392 let breakers = MemoryBreakers::default();
1393 open(&breakers);
1394 std::thread::sleep(BREAKER.cool_off);
1395 let Admission::Probe(probe) = breakers.admit_now(&BREAKER, "inbox") else {
1396 panic!("the first delivery after the cool-off probes");
1397 };
1398 assert!(
1399 matches!(breakers.admit_now(&BREAKER, "inbox"), Admission::Held(_)),
1400 "the others are held while the probe is under way"
1401 );
1402 breakers.record_now(&BREAKER, "inbox", true, Some(&probe));
1404 assert!(matches!(
1405 breakers.admit_now(&BREAKER, "inbox"),
1406 Admission::Held(_)
1407 ));
1408 std::thread::sleep(BREAKER.cool_off);
1409 let Admission::Probe(probe) = breakers.admit_now(&BREAKER, "inbox") else {
1410 panic!("probed again after another cool-off");
1411 };
1412 breakers.record_now(&BREAKER, "inbox", false, Some(&probe));
1414 assert!(matches!(
1415 breakers.admit_now(&BREAKER, "inbox"),
1416 Admission::Pass
1417 ));
1418 breakers.record_now(&BREAKER, "inbox", true, None);
1419 assert!(
1420 matches!(breakers.admit_now(&BREAKER, "inbox"), Admission::Pass),
1421 "closed, it counts its failures from nought"
1422 );
1423 }
1424
1425 #[test]
1426 fn a_probe_that_never_reports_back_is_given_up_after_the_cool_off() {
1427 let breakers = MemoryBreakers::default();
1428 open(&breakers);
1429 std::thread::sleep(BREAKER.cool_off);
1430 assert!(matches!(
1431 breakers.admit_now(&BREAKER, "inbox"),
1432 Admission::Probe(_)
1433 ));
1434 std::thread::sleep(BREAKER.cool_off);
1435 assert!(matches!(
1436 breakers.admit_now(&BREAKER, "inbox"),
1437 Admission::Probe(_)
1438 ));
1439 }
1440 }
1441}