1#![allow(clippy::new_without_default)]
18
19use http::{method::Method, request::Parts as ReqHeader, response::Parts as RespHeader};
20use key::{CacheHashKey, CompactCacheKey, HashBinary};
21use lock::WritePermit;
22use log::warn;
23use pingora_error::Result;
24use pingora_http::ResponseHeader;
25use pingora_timeout::timeout;
26use std::time::{Duration, Instant, SystemTime};
27use storage::MissFinishType;
28use strum::IntoStaticStr;
29use trace::{CacheTraceCTX, Span, Tag};
30
31pub mod admission;
32pub mod cache_control;
33pub mod eviction;
34pub mod filters;
35pub mod hashtable;
36pub mod key;
37pub mod lock;
38pub mod max_file_size;
39mod memory;
40pub mod meta;
41pub mod predictor;
42pub mod put;
43pub mod storage;
44pub mod trace;
45mod variance;
46
47use crate::max_file_size::MaxFileSizeTracker;
48use admission::{AdmissionPolicy, Decision};
49pub use eviction::{CacheEntryId, CacheEntryKey, CacheEntryKeyRef};
50pub use key::CacheKey;
51use lock::{CacheKeyLockImpl, LockStatus, LockWaitOutcome, Locked, UnusableFills, WaitOutcome};
52pub use memory::MemCache;
53pub use meta::{set_compression_dict_content, set_compression_dict_path};
54pub use meta::{CacheMeta, CacheMetaDefaults};
55pub use storage::{
56 HitHandler, MissHandler, PurgeAction, PurgeOutcome, PurgeTarget, PurgeType, Storage,
57};
58pub use variance::VarianceBuilder;
59
60pub mod prelude {}
61
62pub struct HttpCache {
67 phase: CachePhase,
68 inner: Option<Box<HttpCacheInner>>,
70 digest: HttpCacheDigest,
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub enum CachePhase {
76 Disabled(NoCacheReason),
78 Uninit,
80 Bypass,
83 CacheKey,
85 Hit,
87 Miss,
89 Stale,
91 StaleUpdating,
93 Expired,
95 Revalidated,
97 RevalidatedNoCache(NoCacheReason),
99}
100
101impl CachePhase {
102 pub fn as_str(&self) -> &'static str {
104 match self {
105 CachePhase::Disabled(_) => "disabled",
106 CachePhase::Uninit => "uninitialized",
107 CachePhase::Bypass => "bypass",
108 CachePhase::CacheKey => "key",
109 CachePhase::Hit => "hit",
110 CachePhase::Miss => "miss",
111 CachePhase::Stale => "stale",
112 CachePhase::StaleUpdating => "stale-updating",
113 CachePhase::Expired => "expired",
114 CachePhase::Revalidated => "revalidated",
115 CachePhase::RevalidatedNoCache(_) => "revalidated-nocache",
116 }
117 }
118}
119
120#[derive(Copy, Clone, Debug, PartialEq, Eq)]
122pub enum NoCacheReason {
123 NeverEnabled,
125 OriginNotCache,
127 ResponseTooLarge,
129 PredictedResponseTooLarge,
133 StorageError,
135 InternalError,
137 Deferred,
142 DeclinedToUpstream,
144 UpstreamError,
146 CacheLockGiveUp,
148 CacheLockTimeout,
151 CacheLockRetryLimit,
154 Custom(&'static str),
156}
157
158impl NoCacheReason {
159 pub fn as_str(&self) -> &'static str {
161 use NoCacheReason::*;
162 match self {
163 NeverEnabled => "NeverEnabled",
164 OriginNotCache => "OriginNotCache",
165 ResponseTooLarge => "ResponseTooLarge",
166 PredictedResponseTooLarge => "PredictedResponseTooLarge",
167 StorageError => "StorageError",
168 InternalError => "InternalError",
169 Deferred => "Deferred",
170 DeclinedToUpstream => "DeclinedToUpstream",
171 UpstreamError => "UpstreamError",
172 CacheLockGiveUp => "CacheLockGiveUp",
173 CacheLockTimeout => "CacheLockTimeout",
174 CacheLockRetryLimit => "CacheLockRetryLimit",
175 Custom(s) => s,
176 }
177 }
178}
179
180#[derive(Debug, Default)]
182pub struct HttpCacheDigest {
183 pub lock_duration: Option<Duration>,
184 pub lookup_duration: Option<Duration>,
186 pub admission: Option<Decision>,
188 pub lock_abandon: Option<LockAbandon>,
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub struct LockAbandon {
197 pub reason: NoCacheReason,
201 pub token: u64,
203}
204
205fn add_duration_to_opt(target_opt: &mut Option<Duration>, to_add: Duration) {
207 *target_opt = Some(target_opt.map_or(to_add, |existing| existing + to_add));
208}
209
210impl HttpCacheDigest {
211 fn add_lookup_duration(&mut self, extra_lookup_duration: Duration) {
212 add_duration_to_opt(&mut self.lookup_duration, extra_lookup_duration)
213 }
214
215 fn add_lock_duration(&mut self, extra_lock_duration: Duration) {
216 add_duration_to_opt(&mut self.lock_duration, extra_lock_duration)
217 }
218}
219
220#[derive(Debug)]
224pub enum RespCacheable {
225 Cacheable(CacheMeta),
226 Uncacheable(NoCacheReason),
227}
228
229impl RespCacheable {
230 #[inline]
232 pub fn is_cacheable(&self) -> bool {
233 matches!(*self, Self::Cacheable(_))
234 }
235
236 pub fn unwrap_meta(self) -> CacheMeta {
240 match self {
241 Self::Cacheable(meta) => meta,
242 Self::Uncacheable(_) => panic!("expected Cacheable value"),
243 }
244 }
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub enum ForcedFreshness {
252 ForceExpired,
256
257 ForceExpiredServeStale { expired_at: Option<SystemTime> },
268
269 ForceMiss,
272
273 ForceFresh,
275}
276
277#[derive(Debug, Copy, Clone, IntoStaticStr, PartialEq, Eq)]
281#[strum(serialize_all = "snake_case")]
282pub enum HitStatus {
283 Expired,
285
286 ForceExpired,
288
289 ForceExpiredServeStale,
292
293 ForceMiss,
295
296 FailedHitFilter,
299
300 Fresh,
302
303 ForceFresh,
305}
306
307impl HitStatus {
308 pub fn as_str(&self) -> &'static str {
310 self.into()
311 }
312
313 pub fn is_fresh(&self) -> bool {
315 *self == HitStatus::Fresh || *self == HitStatus::ForceFresh
316 }
317
318 pub fn is_treated_as_miss(self) -> bool {
324 matches!(self, HitStatus::ForceMiss | HitStatus::FailedHitFilter)
325 }
326}
327
328pub struct LockCtx {
329 pub lock: Option<Locked>,
330 pub cache_lock: &'static CacheKeyLockImpl,
331 pub wait_timeout: Option<Duration>,
332 pub max_retries: Option<usize>,
333}
334
335struct HttpCacheInnerEnabled {
337 pub meta: Option<CacheMeta>,
338 pub valid_after: Option<SystemTime>,
340 stale_meta_variance: Option<HashBinary>,
343 pub miss_handler: Option<MissHandler>,
344 pub body_reader: Option<HitHandler>,
345 pub storage: &'static (dyn storage::Storage + Sync), pub eviction: Option<&'static (dyn eviction::EvictionManager + Sync)>,
347 pub admission: Option<&'static dyn AdmissionPolicy>,
348 pub lock_ctx: Option<LockCtx>,
349 pub traces: trace::CacheTraceCTX,
350}
351
352struct HttpCacheInner {
353 pub enabled_ctx: Option<Box<HttpCacheInnerEnabled>>,
357 pub key: Option<CacheKey>,
358 pub max_file_size_tracker: Option<MaxFileSizeTracker>,
360 pub predictor: Option<&'static (dyn predictor::CacheablePredictor + Sync)>,
361 pub predicted_uncacheable_reason: Option<NoCacheReason>,
365}
366
367#[derive(Debug, Default)]
368#[non_exhaustive]
369pub struct CacheOptionOverrides {
370 pub wait_timeout: Option<Duration>,
372 pub max_lock_retries: Option<usize>,
374}
375
376impl HttpCache {
377 pub fn new() -> Self {
381 HttpCache {
382 phase: CachePhase::Disabled(NoCacheReason::NeverEnabled),
383 inner: None,
384 digest: HttpCacheDigest::default(),
385 }
386 }
387
388 pub fn enabled(&self) -> bool {
390 !matches!(self.phase, CachePhase::Disabled(_) | CachePhase::Bypass)
391 }
392
393 pub fn bypassing(&self) -> bool {
395 matches!(self.phase, CachePhase::Bypass)
396 }
397
398 pub fn phase(&self) -> CachePhase {
400 self.phase
401 }
402
403 pub fn upstream_used(&self) -> bool {
407 use CachePhase::*;
408 match self.phase {
409 Disabled(_) | Bypass | Miss | Expired | Revalidated | RevalidatedNoCache(_) => true,
410 Hit | Stale | StaleUpdating => false,
411 Uninit | CacheKey => false, }
413 }
414
415 pub fn storage_type_is<T: 'static>(&self) -> bool {
417 self.inner
418 .as_ref()
419 .and_then(|inner| {
420 inner
421 .enabled_ctx
422 .as_ref()
423 .and_then(|ie| ie.storage.as_any().downcast_ref::<T>())
424 })
425 .is_some()
426 }
427
428 pub fn lock_publish_fill_tokens(&self, tokens: &[u64]) {
435 if let Some(Locked::Write(permit)) = self
436 .inner
437 .as_ref()
438 .and_then(|inner| inner.enabled_ctx.as_ref())
439 .and_then(|enabled| enabled.lock_ctx.as_ref())
440 .and_then(|lock_ctx| lock_ctx.lock.as_ref())
441 {
442 permit.publish(tokens);
443 }
444 }
445
446 pub fn release_write_lock(&mut self, reason: NoCacheReason) {
452 use NoCacheReason::*;
453 if let Some(inner) = self.inner.as_mut() {
454 if let Some(lock_ctx) = inner
455 .enabled_ctx
456 .as_mut()
457 .and_then(|ie| ie.lock_ctx.as_mut())
458 {
459 let lock = lock_ctx.lock.take();
460 if let Some(Locked::Write(permit)) = lock {
461 let lock_status = match reason {
462 InternalError | StorageError | Deferred | UpstreamError => {
464 LockStatus::TransientError
465 }
466 DeclinedToUpstream => LockStatus::TransientError,
469 OriginNotCache | ResponseTooLarge | PredictedResponseTooLarge => {
471 LockStatus::GiveUp
472 }
473 Custom(reason) => lock_ctx.cache_lock.custom_lock_status(reason),
474 NeverEnabled => panic!("NeverEnabled holds a write lock"),
476 CacheLockGiveUp | CacheLockTimeout | CacheLockRetryLimit => {
477 panic!("CacheLock* are for cache lock readers only")
478 }
479 };
480 lock_ctx
481 .cache_lock
482 .release(inner.key.as_ref().unwrap(), permit, lock_status);
483 }
484 }
485 }
486 }
487
488 pub fn disable(&mut self, reason: NoCacheReason) {
490 assert!(
492 reason != NoCacheReason::NeverEnabled,
493 "NeverEnabled not allowed as a disable reason"
494 );
495 match self.phase {
496 CachePhase::Disabled(old_reason) => {
497 if old_reason == NoCacheReason::NeverEnabled {
499 warn!("Tried to replace cache NeverEnabled with reason: {reason:?}");
502 return;
503 }
504 self.phase = CachePhase::Disabled(reason);
505 }
506 _ => {
507 self.phase = CachePhase::Disabled(reason);
508 self.release_write_lock(reason);
509 #[cfg_attr(not(feature = "trace"), allow(unused_mut))]
511 let mut inner_enabled = self
512 .inner_mut()
513 .enabled_ctx
514 .take()
515 .expect("could remove enabled_ctx on disable");
516 inner_enabled
518 .traces
519 .cache_span
520 .set_tag(|| trace::Tag::new("disable_reason", reason.as_str()));
521 }
522 }
523 }
524
525 pub fn bypass(&mut self) {
536 match self.phase {
537 CachePhase::CacheKey => {
538 self.phase = CachePhase::Bypass;
540 let predicted_reason = self
543 .inner()
544 .predictor
545 .and_then(|predictor| predictor.predicted_uncacheable_reason(self.cache_key()));
546 self.inner_mut().predicted_uncacheable_reason = predicted_reason;
547
548 let traces = &mut self.inner_enabled_mut().traces;
549 traces
550 .cache_span
551 .set_tag(|| trace::Tag::new("bypassed", true));
552 if let Some(reason) = predicted_reason {
553 traces
554 .cache_span
555 .set_tag(|| trace::Tag::new("bypass_reason", reason.as_str()));
556 }
557 }
558 _ => panic!("wrong phase to bypass HttpCache {:?}", self.phase),
559 }
560 }
561
562 pub fn enable(
572 &mut self,
573 storage: &'static (dyn storage::Storage + Sync),
574 eviction: Option<&'static (dyn eviction::EvictionManager + Sync)>,
575 predictor: Option<&'static (dyn predictor::CacheablePredictor + Sync)>,
576 cache_lock: Option<&'static CacheKeyLockImpl>,
577 option_overrides: Option<CacheOptionOverrides>,
578 ) {
579 match self.phase {
580 CachePhase::Disabled(_) => {
581 self.phase = CachePhase::Uninit;
582
583 let wait_timeout = option_overrides
584 .as_ref()
585 .and_then(|overrides| overrides.wait_timeout);
586 let max_retries = option_overrides
587 .as_ref()
588 .and_then(|overrides| overrides.max_lock_retries);
589 let lock_ctx = cache_lock.map(|cache_lock| LockCtx {
590 cache_lock,
591 lock: None,
592 wait_timeout,
593 max_retries,
594 });
595
596 self.inner = Some(Box::new(HttpCacheInner {
597 enabled_ctx: Some(Box::new(HttpCacheInnerEnabled {
598 meta: None,
599 valid_after: None,
600 stale_meta_variance: None,
601 miss_handler: None,
602 body_reader: None,
603 storage,
604 eviction,
605 admission: None,
606 lock_ctx,
607 traces: CacheTraceCTX::new(),
608 })),
609 key: None,
610 max_file_size_tracker: None,
611 predictor,
612 predicted_uncacheable_reason: None,
613 }));
614 }
615 _ => panic!("Cannot enable already enabled HttpCache {:?}", self.phase),
616 }
617 }
618
619 pub fn set_cache_lock(
624 &mut self,
625 cache_lock: Option<&'static CacheKeyLockImpl>,
626 option_overrides: Option<CacheOptionOverrides>,
627 ) {
628 match self.phase {
629 CachePhase::Disabled(_)
630 | CachePhase::CacheKey
631 | CachePhase::Stale
632 | CachePhase::Hit => {
633 let inner_enabled = self.inner_enabled_mut();
634 if inner_enabled
635 .lock_ctx
636 .as_ref()
637 .is_some_and(|ctx| ctx.lock.is_some())
638 {
639 panic!("lock already set when resetting cache lock")
640 } else {
641 let wait_timeout = option_overrides
642 .as_ref()
643 .and_then(|overrides| overrides.wait_timeout);
644 let max_retries = option_overrides
645 .as_ref()
646 .and_then(|overrides| overrides.max_lock_retries);
647 let lock_ctx = cache_lock.map(|cache_lock| LockCtx {
648 cache_lock,
649 lock: None,
650 wait_timeout,
651 max_retries,
652 });
653 inner_enabled.lock_ctx = lock_ctx;
654 }
655 }
656 _ => panic!("wrong phase: {:?}", self.phase),
657 }
658 }
659
660 pub fn set_admission_policy(&mut self, policy: &'static dyn AdmissionPolicy) {
669 match self.phase {
670 CachePhase::Uninit | CachePhase::CacheKey => {
671 self.inner_enabled_mut().admission = Some(policy);
672 }
673 _ => panic!("wrong phase to set admission policy: {:?}", self.phase),
674 }
675 }
676
677 pub fn enable_tracing(&mut self, parent_span: trace::Span) {
679 if let Some(inner_enabled) = self.inner.as_mut().and_then(|i| i.enabled_ctx.as_mut()) {
680 inner_enabled.traces.enable(parent_span);
681 }
682 }
683
684 pub fn get_cache_span(&self) -> Option<trace::SpanHandle> {
686 self.inner
687 .as_ref()
688 .and_then(|i| i.enabled_ctx.as_ref().map(|ie| ie.traces.get_cache_span()))
689 }
690
691 pub fn get_miss_span(&self) -> Option<trace::SpanHandle> {
693 self.inner
694 .as_ref()
695 .and_then(|i| i.enabled_ctx.as_ref().map(|ie| ie.traces.get_miss_span()))
696 }
697
698 pub fn get_hit_span(&self) -> Option<trace::SpanHandle> {
700 self.inner
701 .as_ref()
702 .and_then(|i| i.enabled_ctx.as_ref().map(|ie| ie.traces.get_hit_span()))
703 }
704
705 #[inline]
707 fn inner_enabled_mut(&mut self) -> &mut HttpCacheInnerEnabled {
708 self.inner.as_mut().unwrap().enabled_ctx.as_mut().unwrap()
709 }
710
711 #[inline]
712 fn inner_enabled(&self) -> &HttpCacheInnerEnabled {
713 self.inner.as_ref().unwrap().enabled_ctx.as_ref().unwrap()
714 }
715
716 #[inline]
718 fn inner_mut(&mut self) -> &mut HttpCacheInner {
719 self.inner.as_mut().unwrap()
720 }
721
722 #[inline]
723 fn inner(&self) -> &HttpCacheInner {
724 self.inner.as_ref().unwrap()
725 }
726
727 pub fn set_cache_key(&mut self, key: CacheKey) {
731 match self.phase {
732 CachePhase::Uninit | CachePhase::CacheKey => {
733 self.phase = CachePhase::CacheKey;
734 self.inner_mut().key = Some(key);
735 }
736 _ => panic!("wrong phase {:?}", self.phase),
737 }
738 }
739
740 pub fn cache_key(&self) -> &CacheKey {
744 match self.phase {
745 CachePhase::Disabled(NoCacheReason::NeverEnabled) | CachePhase::Uninit => {
746 panic!("wrong phase {:?}", self.phase)
747 }
748 _ => self
749 .inner()
750 .key
751 .as_ref()
752 .expect("cache key should be set (set_cache_key not called?)"),
753 }
754 }
755
756 pub fn max_file_size_bytes(&self) -> Option<usize> {
758 assert!(
759 !matches!(
760 self.phase,
761 CachePhase::Disabled(NoCacheReason::NeverEnabled)
762 ),
763 "tried to access max file size bytes when cache never enabled"
764 );
765 self.inner()
766 .max_file_size_tracker
767 .as_ref()
768 .map(|t| t.max_file_size_bytes())
769 }
770
771 pub fn set_max_file_size_bytes(&mut self, max_file_size_bytes: usize) {
777 match self.phase {
778 CachePhase::Disabled(_) => panic!("wrong phase {:?}", self.phase),
779 _ => {
780 self.inner_mut().max_file_size_tracker =
781 Some(MaxFileSizeTracker::new(max_file_size_bytes));
782 }
783 }
784 }
785
786 pub fn track_body_bytes_for_max_file_size(&mut self, bytes_len: usize) -> bool {
796 assert!(
799 !matches!(
800 self.phase,
801 CachePhase::Disabled(NoCacheReason::NeverEnabled)
802 ),
803 "tried to access max file size bytes when cache never enabled"
804 );
805 self.inner_mut()
806 .max_file_size_tracker
807 .as_mut()
808 .is_none_or(|t| t.add_body_bytes(bytes_len))
809 }
810
811 pub fn exceeded_max_file_size(&self) -> bool {
815 assert!(
816 !matches!(
817 self.phase,
818 CachePhase::Disabled(NoCacheReason::NeverEnabled)
819 ),
820 "tried to access max file size bytes when cache never enabled"
821 );
822 self.inner()
823 .max_file_size_tracker
824 .as_ref()
825 .is_some_and(|t| !t.allow_caching())
826 }
827
828 pub fn cache_found(&mut self, meta: CacheMeta, hit_handler: HitHandler, hit_status: HitStatus) {
835 if !matches!(self.phase, CachePhase::CacheKey | CachePhase::Stale) {
837 panic!("wrong phase {:?}", self.phase)
838 }
839
840 self.phase = match hit_status {
841 HitStatus::Fresh | HitStatus::ForceFresh => CachePhase::Hit,
842 HitStatus::Expired | HitStatus::ForceExpired | HitStatus::ForceExpiredServeStale => {
843 CachePhase::Stale
844 }
845 HitStatus::FailedHitFilter | HitStatus::ForceMiss => self.phase,
846 };
847
848 let phase = self.phase;
849 let inner = self.inner_mut();
850
851 let key = inner.key.as_ref().expect("key must be set on hit");
852 let inner_enabled = inner
853 .enabled_ctx
854 .as_mut()
855 .expect("cache_found must be called while cache enabled");
856
857 let stale = phase == CachePhase::Stale;
860 if stale || hit_status.is_treated_as_miss() {
861 if let Some(lock_ctx) = inner_enabled.lock_ctx.as_mut() {
862 lock_ctx.lock = Some(lock_ctx.cache_lock.lock(key, stale));
863 }
864 }
865
866 if hit_status.is_treated_as_miss() {
867 inner_enabled.body_reader = None;
869 inner_enabled.meta = None;
870 } else {
871 inner_enabled.traces.start_hit_span(phase, hit_status);
873 inner_enabled.traces.log_meta_in_hit_span(&meta);
874 if let Some(eviction) = inner_enabled.eviction {
875 let cache_key = key.to_compact();
876 if hit_handler.should_count_access() {
877 let size = hit_handler.get_eviction_weight();
878 let entry_key =
879 eviction::CacheEntryKey::from_entry_id(cache_key, hit_handler.entry_id());
880 eviction.access(&entry_key, size, meta.0.internal.fresh_until);
881 }
882 }
883 inner_enabled.meta = Some(meta);
884 inner_enabled.body_reader = Some(hit_handler);
885 }
886 }
887
888 pub fn cache_miss(&mut self) {
895 match self.phase {
896 CachePhase::CacheKey | CachePhase::Bypass | CachePhase::Stale => {
900 self.phase = CachePhase::Miss;
901 let inner_enabled = self.inner_enabled_mut();
906 inner_enabled.meta = None;
907 inner_enabled.stale_meta_variance = None;
908 inner_enabled.traces.start_miss_span();
909 }
910 _ => panic!("wrong phase {:?}", self.phase),
911 }
912 }
913
914 pub fn hit_handler(&mut self) -> &mut HitHandler {
918 match self.phase {
919 CachePhase::Hit
920 | CachePhase::Stale
921 | CachePhase::StaleUpdating
922 | CachePhase::Revalidated
923 | CachePhase::RevalidatedNoCache(_) => {
924 self.inner_enabled_mut().body_reader.as_mut().unwrap()
925 }
926 _ => panic!("wrong phase {:?}", self.phase),
927 }
928 }
929
930 pub fn miss_body_reader(&mut self) -> Option<&mut HitHandler> {
933 match self.phase {
934 CachePhase::Miss | CachePhase::Expired => {
935 let inner_enabled = self.inner_enabled_mut();
936 if inner_enabled.storage.support_streaming_partial_write() {
937 inner_enabled.body_reader.as_mut()
938 } else {
939 None
942 }
943 }
944 _ => None,
945 }
946 }
947
948 pub fn support_streaming_partial_write(&self) -> Option<bool> {
952 self.inner.as_ref().and_then(|inner| {
953 inner
954 .enabled_ctx
955 .as_ref()
956 .map(|c| c.storage.support_streaming_partial_write())
957 })
958 }
959
960 pub async fn finish_hit_handler(&mut self) -> Result<()> {
966 match self.phase {
967 CachePhase::Hit
968 | CachePhase::Miss
969 | CachePhase::Expired
970 | CachePhase::Stale
971 | CachePhase::StaleUpdating
972 | CachePhase::Revalidated
973 | CachePhase::RevalidatedNoCache(_) => {
974 let inner = self.inner_mut();
975 let inner_enabled = inner.enabled_ctx.as_mut().expect("cache enabled");
976 if inner_enabled.body_reader.is_none() {
977 return Ok(());
979 }
980 let body_reader = inner_enabled.body_reader.take().unwrap();
981 let key = inner.key.as_ref().unwrap();
982 let result = body_reader
983 .finish(
984 inner_enabled.storage,
985 key,
986 &inner_enabled.traces.hit_span.handle(),
987 )
988 .await;
989 inner_enabled.traces.finish_hit_span();
990 result
991 }
992 _ => panic!("wrong phase {:?}", self.phase),
993 }
994 }
995
996 pub async fn set_miss_handler(&mut self) -> Result<()> {
998 match self.phase {
999 CachePhase::Miss | CachePhase::Expired => {
1002 let inner = self.inner_mut();
1003 let inner_enabled = inner
1004 .enabled_ctx
1005 .as_mut()
1006 .expect("cache enabled on miss and expired");
1007 if inner_enabled.miss_handler.is_some() {
1008 panic!("write handler is already set")
1009 }
1010 let meta = inner_enabled.meta.as_ref().unwrap();
1011 let key = inner.key.as_ref().unwrap();
1012 let miss_handler = inner_enabled
1013 .storage
1014 .get_miss_handler(key, meta, &inner_enabled.traces.get_miss_span())
1015 .await?;
1016
1017 inner_enabled.miss_handler = Some(miss_handler);
1018
1019 if inner_enabled.storage.support_streaming_partial_write() {
1020 if let Some(lock_ctx) = inner_enabled.lock_ctx.as_mut() {
1023 let lock = lock_ctx.lock.take();
1024 if let Some(Locked::Write(permit)) = lock {
1025 lock_ctx.cache_lock.release(key, permit, LockStatus::Done);
1026 }
1027 }
1028 let body_reader = inner_enabled
1030 .storage
1031 .lookup_streaming_write(
1032 key,
1033 inner_enabled
1034 .miss_handler
1035 .as_ref()
1036 .expect("miss handler already set")
1037 .streaming_write_tag(),
1038 &inner_enabled.traces.get_miss_span(),
1039 )
1040 .await?;
1041
1042 if let Some((_meta, body_reader)) = body_reader {
1043 inner_enabled.body_reader = Some(body_reader);
1044 } else {
1045 panic!("unable to get body_reader for {:?}", meta);
1047 }
1048 }
1049 Ok(())
1050 }
1051 _ => panic!("wrong phase {:?}", self.phase),
1052 }
1053 }
1054
1055 pub fn miss_handler(&mut self) -> Option<&mut MissHandler> {
1059 match self.phase {
1060 CachePhase::Miss | CachePhase::Expired => {
1061 self.inner_enabled_mut().miss_handler.as_mut()
1062 }
1063 _ => panic!("wrong phase {:?}", self.phase),
1064 }
1065 }
1066
1067 pub async fn finish_miss_handler(&mut self) -> Result<()> {
1074 match self.phase {
1075 CachePhase::Miss | CachePhase::Expired => {
1076 let inner = self.inner_mut();
1077 let inner_enabled = inner
1078 .enabled_ctx
1079 .as_mut()
1080 .expect("cache enabled on miss and expired");
1081 let Some(miss_handler) = inner_enabled.miss_handler.take() else {
1082 return Ok(());
1084 };
1085 let entry_id = miss_handler.entry_id();
1087 let finish_result = miss_handler.finish().await;
1088 let key = inner
1089 .key
1090 .as_ref()
1091 .expect("key set by miss or expired phase");
1092 if let Some(lock_ctx) = inner_enabled.lock_ctx.as_mut() {
1093 let lock = lock_ctx.lock.take();
1094 if let Some(Locked::Write(permit)) = lock {
1095 let lock_status = if finish_result.is_ok() {
1098 LockStatus::Done
1099 } else {
1100 LockStatus::TransientError
1101 };
1102 lock_ctx.cache_lock.release(key, permit, lock_status);
1103 }
1104 }
1105 let size = match finish_result {
1106 Ok(size) => size,
1107 Err(e) => {
1108 inner_enabled.traces.finish_miss_span();
1109 return Err(e);
1110 }
1111 };
1112 if let Some(eviction) = inner_enabled.eviction {
1113 let cache_key = key.to_compact();
1114 let meta = inner_enabled.meta.as_ref().unwrap();
1115 let entry_key = eviction::CacheEntryKey::from_entry_id(cache_key, entry_id);
1116 let evicted = match size {
1117 MissFinishType::Created(size) => {
1118 eviction.admit(entry_key, size, meta.0.internal.fresh_until)
1119 }
1120 MissFinishType::Appended(size, max_size) => {
1121 eviction.increment_weight(&entry_key, size, max_size)
1122 }
1123 };
1124 let span = inner_enabled.traces.child("eviction");
1126 let handle = span.handle();
1127 let storage = inner_enabled.storage;
1128 tokio::task::spawn(async move {
1129 for item in evicted {
1130 let target = storage::PurgeTarget::Exact(&item);
1131 if let Err(e) =
1132 storage.purge(target, PurgeType::Eviction, &handle).await
1133 {
1134 warn!(
1135 "Failed to purge {target} during eviction for finish miss handler: {e}"
1136 );
1137 }
1138 }
1139 });
1140 }
1141 inner_enabled.traces.finish_miss_span();
1142 Ok(())
1143 }
1144 _ => panic!("wrong phase {:?}", self.phase),
1145 }
1146 }
1147
1148 pub fn set_cache_meta(&mut self, mut meta: CacheMeta) {
1155 match self.phase {
1156 CachePhase::Stale => {
1158 let inner_enabled = self.inner_enabled_mut();
1159 let old_meta = inner_enabled
1160 .meta
1161 .as_ref()
1162 .expect("stale phase has cache meta");
1163 inner_enabled.stale_meta_variance = old_meta.variance();
1164 meta.set_provenance(old_meta.provenance());
1165 inner_enabled.traces.log_meta_in_miss_span(&meta);
1167 inner_enabled.meta = Some(meta);
1168 }
1169 CachePhase::Miss => {
1170 let inner_enabled = self.inner_enabled_mut();
1171 inner_enabled.stale_meta_variance = None;
1172 inner_enabled.traces.log_meta_in_miss_span(&meta);
1174 inner_enabled.meta = Some(meta);
1175 }
1176 _ => panic!("wrong phase {:?}", self.phase),
1177 }
1178 if self.phase == CachePhase::Stale {
1179 self.phase = CachePhase::Expired;
1180 }
1181 }
1182
1183 pub async fn revalidate_cache_meta(&mut self, mut meta: CacheMeta) -> Result<bool> {
1188 let result = match self.phase {
1189 CachePhase::Stale => {
1190 let inner = self.inner_mut();
1191 let inner_enabled = inner
1192 .enabled_ctx
1193 .as_mut()
1194 .expect("stale phase has cache enabled");
1195 let old_meta = inner_enabled.meta.take().unwrap();
1200 let created = old_meta.0.internal.created;
1201 let provenance = old_meta.provenance();
1202 meta.0.internal.created = created;
1203 meta.set_provenance(provenance);
1204 let mut extensions = old_meta.0.extensions;
1208 extensions.extend(meta.0.extensions);
1209 meta.0.extensions = extensions;
1210 inner_enabled.stale_meta_variance = None;
1211
1212 inner_enabled.meta.replace(meta);
1213
1214 #[cfg_attr(not(feature = "trace"), allow(unused_mut))]
1215 let mut span = inner_enabled.traces.child("update_meta");
1216 let result = inner_enabled
1217 .storage
1218 .update_meta(
1219 inner.key.as_ref().unwrap(),
1220 inner_enabled.meta.as_ref().unwrap(),
1221 &span.handle(),
1222 )
1223 .await;
1224 span.set_tag(|| trace::Tag::new("updated", result.is_ok()));
1225
1226 if let Some(lock_ctx) = inner_enabled.lock_ctx.as_mut() {
1228 let lock = lock_ctx.lock.take();
1229 if let Some(Locked::Write(permit)) = lock {
1230 lock_ctx.cache_lock.release(
1231 inner.key.as_ref().expect("key set by stale phase"),
1232 permit,
1233 LockStatus::Done,
1234 );
1235 }
1236 }
1237
1238 result
1239 }
1240 _ => panic!("wrong phase {:?}", self.phase),
1241 };
1242 self.phase = CachePhase::Revalidated;
1243 result
1244 }
1245
1246 pub fn revalidate_merge_header(&mut self, resp: &RespHeader) -> ResponseHeader {
1249 match self.phase {
1250 CachePhase::Stale => {
1251 let mut old_header = self.inner_enabled().meta.as_ref().unwrap().0.header.clone();
1258 let mut clone_header = |header_name: &'static str| {
1259 for (i, value) in resp.headers.get_all(header_name).iter().enumerate() {
1260 if i == 0 {
1261 old_header
1262 .insert_header(header_name, value)
1263 .expect("can add valid header");
1264 } else {
1265 old_header
1266 .append_header(header_name, value)
1267 .expect("can add valid header");
1268 }
1269 }
1270 };
1271 clone_header("cache-control");
1272 clone_header("expires");
1273 clone_header("cache-tag");
1274 clone_header("cdn-cache-control");
1275 clone_header("etag");
1276 old_header
1287 }
1288 _ => panic!("wrong phase {:?}", self.phase),
1289 }
1290 }
1291
1292 pub fn revalidate_uncacheable(&mut self, header: ResponseHeader, reason: NoCacheReason) {
1294 match self.phase {
1295 CachePhase::Stale => {
1296 self.inner_enabled_mut().meta.as_mut().unwrap().0.header = header;
1298 self.release_write_lock(reason);
1300 }
1301 _ => panic!("wrong phase {:?}", self.phase),
1302 }
1303 self.phase = CachePhase::RevalidatedNoCache(reason);
1304 }
1306
1307 pub fn set_stale_updating(&mut self) {
1309 match self.phase {
1310 CachePhase::Stale => self.phase = CachePhase::StaleUpdating,
1311 _ => panic!("wrong phase {:?}", self.phase),
1312 }
1313 }
1314
1315 pub fn update_variance(&mut self, variance: Option<HashBinary>) {
1321 let phase = self.phase;
1343 let inner = match phase {
1344 CachePhase::Miss | CachePhase::Expired => self.inner_mut(),
1345 _ => panic!("wrong phase {:?}", self.phase),
1346 };
1347 let inner_enabled = inner
1348 .enabled_ctx
1349 .as_mut()
1350 .expect("cache enabled on miss and expired");
1351 let old_key_variance = inner.key.as_ref().unwrap().get_variance_key().copied();
1352 let stale_meta_variance = if phase == CachePhase::Expired {
1353 inner_enabled.stale_meta_variance.take()
1354 } else {
1355 inner_enabled.stale_meta_variance = None;
1356 None
1357 };
1358 let reset_provenance_to_created = if phase == CachePhase::Expired {
1359 match old_key_variance {
1360 Some(old_variance) => Some(old_variance) != variance,
1361 None => stale_meta_variance != variance,
1362 }
1363 } else {
1364 false
1365 };
1366
1367 if let Some(variance_hash) = variance.as_ref() {
1369 inner_enabled
1370 .meta
1371 .as_mut()
1372 .unwrap()
1373 .set_variance_key(*variance_hash);
1374 } else {
1375 inner_enabled.meta.as_mut().unwrap().remove_variance();
1376 }
1377 if reset_provenance_to_created {
1378 inner_enabled
1379 .meta
1380 .as_mut()
1381 .unwrap()
1382 .reset_provenance_to_created();
1383 }
1384
1385 let key = inner.key.as_ref().unwrap();
1388 if let Some(old_variance) = old_key_variance {
1389 if Some(old_variance) != variance {
1391 if let Some(lock_ctx) = inner_enabled.lock_ctx.as_mut() {
1399 if let Some(Locked::Write(permit)) = lock_ctx.lock.take() {
1400 lock_ctx.cache_lock.release(key, permit, LockStatus::Done);
1401 }
1402 }
1403 inner.key.as_mut().unwrap().remove_variance_key();
1406 }
1407 }
1408 }
1409
1410 pub fn cache_meta(&self) -> &CacheMeta {
1415 match self.phase {
1416 CachePhase::Stale
1418 | CachePhase::StaleUpdating
1419 | CachePhase::Expired
1420 | CachePhase::Hit
1421 | CachePhase::Revalidated
1422 | CachePhase::RevalidatedNoCache(_) => self.inner_enabled().meta.as_ref().unwrap(),
1423 CachePhase::Miss => {
1424 if self.inner_enabled().body_reader.is_some() {
1427 self.inner_enabled().meta.as_ref().unwrap()
1428 } else {
1429 panic!("wrong phase {:?}", self.phase);
1430 }
1431 }
1432
1433 _ => panic!("wrong phase {:?}", self.phase),
1434 }
1435 }
1436
1437 pub fn maybe_cache_meta(&self) -> Option<&CacheMeta> {
1446 match self.phase {
1447 CachePhase::Miss
1448 | CachePhase::Stale
1449 | CachePhase::StaleUpdating
1450 | CachePhase::Expired
1451 | CachePhase::Hit
1452 | CachePhase::Revalidated
1453 | CachePhase::RevalidatedNoCache(_) => self.inner_enabled().meta.as_ref(),
1454 _ => None,
1455 }
1456 }
1457
1458 pub fn maybe_cache_key(&self) -> Option<&CacheKey> {
1463 (!matches!(
1464 self.phase(),
1465 CachePhase::Disabled(NoCacheReason::NeverEnabled) | CachePhase::Uninit
1466 ))
1467 .then(|| self.cache_key())
1468 }
1469
1470 pub async fn cache_lookup(&mut self) -> Result<Option<(CacheMeta, HitHandler)>> {
1490 match self.phase {
1491 CachePhase::CacheKey | CachePhase::Stale => {
1493 let observe_admission =
1494 self.phase == CachePhase::CacheKey && self.digest.admission.is_none();
1495 let (result, admission) = {
1496 let inner = self
1497 .inner
1498 .as_mut()
1499 .expect("Cache phase is checked and should have inner");
1500 let inner_enabled = inner
1501 .enabled_ctx
1502 .as_mut()
1503 .expect("Cache enabled on cache_lookup");
1504 #[cfg_attr(not(feature = "trace"), allow(unused_mut))]
1505 let mut span = inner_enabled.traces.child("lookup");
1506 let key = inner.key.as_ref().unwrap(); let now = Instant::now();
1508 let result = inner_enabled.storage.lookup(key, &span.handle()).await?;
1509 self.digest.add_lookup_duration(now.elapsed());
1511 let storage_miss = result.is_none();
1512 let result = result.and_then(|(meta, header)| {
1513 if let Some(ts) = inner_enabled.valid_after {
1514 if meta.created() < ts {
1519 span.set_tag(|| trace::Tag::new("not valid", true));
1520 return None;
1521 }
1522 }
1523 Some((meta, header))
1524 });
1525 let admission = (storage_miss && observe_admission)
1526 .then(|| inner_enabled.admission.map(|policy| policy.observe(key)))
1527 .flatten();
1528 if let Some(decision) = admission {
1529 span.set_tag(|| {
1530 trace::Tag::new("admission.observed", decision.observed() as i64)
1531 });
1532 span.set_tag(|| {
1533 trace::Tag::new("admission.deferred", decision.is_deferred())
1534 });
1535 }
1536 if result.is_none() && admission.is_none_or(|decision| !decision.is_deferred())
1537 {
1538 if let Some(lock_ctx) = inner_enabled.lock_ctx.as_mut() {
1539 lock_ctx.lock = Some(lock_ctx.cache_lock.lock(key, false));
1540 }
1541 }
1542 span.set_tag(|| trace::Tag::new("found", result.is_some()));
1543 (result, admission)
1544 };
1545 if let Some(decision) = admission {
1546 self.digest.admission = Some(decision);
1547 if decision.is_deferred() {
1548 self.disable(NoCacheReason::Deferred);
1549 }
1550 }
1551 Ok(result)
1552 }
1553 _ => panic!("wrong phase {:?}", self.phase),
1554 }
1555 }
1556
1557 pub fn cache_vary_lookup(&mut self, variance: HashBinary, meta: &CacheMeta) -> bool {
1565 match self.phase {
1566 CachePhase::CacheKey | CachePhase::Stale => {
1568 let inner = self.inner_mut();
1569 inner
1573 .enabled_ctx
1574 .as_mut()
1575 .expect("cache enabled")
1576 .valid_after = Some(meta.provenance());
1577
1578 let key = inner.key.as_mut().unwrap();
1580 let is_initial_cache_hit = key.get_variance_key().is_none();
1582 key.set_variance_key(variance);
1583 let variance_binary = key.variance_bin();
1584 let matches_variance = meta.variance() == variance_binary;
1585
1586 if matches_variance && is_initial_cache_hit {
1596 inner.key.as_mut().unwrap().remove_variance_key();
1597 }
1598
1599 matches_variance
1600 }
1601 _ => panic!("wrong phase {:?}", self.phase),
1602 }
1603 }
1604
1605 pub fn is_cache_locked(&self) -> bool {
1608 matches!(
1609 self.inner_enabled()
1610 .lock_ctx
1611 .as_ref()
1612 .and_then(|l| l.lock.as_ref()),
1613 Some(Locked::Read(_))
1614 )
1615 }
1616
1617 pub fn is_cache_lock_writer(&self) -> bool {
1620 matches!(
1621 self.inner_enabled()
1622 .lock_ctx
1623 .as_ref()
1624 .and_then(|l| l.lock.as_ref()),
1625 Some(Locked::Write(_))
1626 )
1627 }
1628
1629 pub fn cache_lock_max_retries(&self) -> Option<usize> {
1631 self.inner_enabled()
1632 .lock_ctx
1633 .as_ref()
1634 .and_then(|l| l.max_retries)
1635 }
1636
1637 pub fn take_write_lock(&mut self) -> (WritePermit, &'static CacheKeyLockImpl) {
1641 let lock_ctx = self
1642 .inner_enabled_mut()
1643 .lock_ctx
1644 .as_mut()
1645 .expect("take_write_lock() called without cache lock");
1646 let lock = lock_ctx
1647 .lock
1648 .take()
1649 .expect("take_write_lock() called without lock");
1650 match lock {
1651 Locked::Write(w) => (w, lock_ctx.cache_lock),
1652 Locked::Read(_) => panic!("take_write_lock() called on read lock"),
1653 }
1654 }
1655
1656 pub fn set_write_lock(&mut self, write_lock: WritePermit) {
1665 if let Some(lock_ctx) = self.inner_enabled_mut().lock_ctx.as_mut() {
1666 lock_ctx.lock.replace(Locked::Write(write_lock));
1667 }
1668 }
1669
1670 fn has_staled_asset(&self) -> bool {
1672 matches!(self.phase, CachePhase::Stale | CachePhase::StaleUpdating)
1673 }
1674
1675 pub fn can_serve_stale_error(&self) -> bool {
1677 self.has_staled_asset() && self.cache_meta().serve_stale_if_error(SystemTime::now())
1678 }
1679
1680 pub fn can_serve_stale_updating(&self) -> bool {
1682 self.has_staled_asset()
1683 && self
1684 .cache_meta()
1685 .serve_stale_while_revalidate(SystemTime::now())
1686 }
1687
1688 pub async fn cache_lock_wait(&mut self) -> LockWaitOutcome {
1697 let unusable = self
1699 .maybe_cache_key()
1700 .and_then(|key| key.extensions.get::<UnusableFills>())
1701 .cloned();
1702
1703 let inner_enabled = self.inner_enabled_mut();
1704 #[cfg_attr(not(feature = "trace"), allow(unused_mut))]
1705 let mut span = inner_enabled.traces.child("cache_lock");
1706 let (read_lock, outcome) = if let Some(lock_ctx) = inner_enabled.lock_ctx.as_mut() {
1709 let lock = lock_ctx.lock.take(); if let Some(Locked::Read(r)) = lock {
1711 let now = Instant::now();
1712 let wait = async {
1715 match unusable.as_ref() {
1716 Some(unusable) => r.wait_unless_published(unusable).await,
1717 None => {
1718 r.wait().await;
1719 WaitOutcome::Released
1720 }
1721 }
1722 };
1723 let outcome = if let Some(wait_timeout) = lock_ctx.wait_timeout {
1724 let wait_timeout =
1725 wait_timeout.saturating_sub(self.lock_duration().unwrap_or(Duration::ZERO));
1726 match timeout(wait_timeout, wait).await {
1727 Ok(outcome) => Self::wait_result(&r, outcome),
1728 Err(_) => LockWaitOutcome::WaitTimeout,
1729 }
1730 } else {
1731 Self::wait_result(&r, wait.await)
1732 };
1733 self.digest.add_lock_duration(now.elapsed());
1734 if let LockWaitOutcome::Abandoned { reason, token } = outcome {
1737 self.digest.lock_abandon = Some(LockAbandon { reason, token });
1738 }
1739 (r, outcome)
1740 } else {
1741 panic!("cache_lock_wait on wrong type of lock")
1742 }
1743 } else {
1744 panic!("cache_lock_wait without cache lock")
1745 };
1746 if let Some(lock_ctx) = self.inner_enabled().lock_ctx.as_ref() {
1747 lock_ctx
1748 .cache_lock
1749 .trace_lock_wait(&mut span, &read_lock, outcome.lock_status());
1750 }
1751 outcome
1752 }
1753
1754 pub fn lock_duration(&self) -> Option<Duration> {
1756 self.digest.lock_duration
1757 }
1758
1759 fn wait_result(lock: &lock::ReadLock, outcome: WaitOutcome) -> LockWaitOutcome {
1762 match outcome {
1763 WaitOutcome::Abandoned(matched) => LockWaitOutcome::Abandoned {
1764 reason: NoCacheReason::Custom(matched.reason),
1765 token: matched.token,
1766 },
1767 WaitOutcome::Released | WaitOutcome::AgeTimeout => {
1768 Self::released_result(lock.lock_status())
1769 }
1770 }
1771 }
1772
1773 fn released_result(status: LockStatus) -> LockWaitOutcome {
1776 match status {
1777 LockStatus::Done => LockWaitOutcome::Done,
1778 LockStatus::TransientError => LockWaitOutcome::TransientError,
1779 LockStatus::Dangling => LockWaitOutcome::Dangling,
1780 LockStatus::WaitTimeout => LockWaitOutcome::WaitTimeout,
1781 LockStatus::AgeTimeout => LockWaitOutcome::AgeTimeout,
1782 LockStatus::GiveUp => LockWaitOutcome::GiveUp,
1783 LockStatus::Waiting => {
1784 debug_assert!(false, "a released lock cannot still be Waiting");
1785 LockWaitOutcome::Dangling
1786 }
1787 }
1788 }
1789
1790 pub fn lock_abandon(&self) -> Option<LockAbandon> {
1797 self.digest.lock_abandon
1798 }
1799
1800 pub fn lookup_duration(&self) -> Option<Duration> {
1802 self.digest.lookup_duration
1803 }
1804
1805 pub fn admission_decision(&self) -> Option<Decision> {
1807 self.digest.admission
1808 }
1809
1810 pub async fn purge(&self) -> Result<bool> {
1814 self.purge_action(PurgeAction::Delete).await
1815 }
1816
1817 pub async fn expire(&self) -> Result<bool> {
1825 self.purge_action(PurgeAction::Expire).await
1826 }
1827
1828 async fn purge_action(&self, action: PurgeAction) -> Result<bool> {
1829 match self.phase {
1830 CachePhase::CacheKey => {
1831 let inner = self.inner();
1832 let inner_enabled = self.inner_enabled();
1833 let span = inner_enabled.traces.child("purge");
1834 let key = inner.key.as_ref().unwrap().to_compact();
1835 Self::purge_impl(
1836 inner_enabled.storage,
1837 inner_enabled.eviction,
1838 &key,
1839 action,
1840 span,
1841 )
1842 .await
1843 }
1844 _ => panic!("wrong phase {:?}", self.phase),
1845 }
1846 }
1847
1848 pub fn spawn_async_purge(
1853 &self,
1854 context: &'static str,
1855 ) -> tokio::task::JoinHandle<Result<bool>> {
1856 if matches!(self.phase, CachePhase::Disabled(_) | CachePhase::Uninit) {
1857 panic!("wrong phase {:?}", self.phase);
1858 }
1859
1860 let inner_enabled = self.inner_enabled();
1861 let span = inner_enabled.traces.child("purge");
1862 let key = self.inner().key.as_ref().unwrap().to_compact();
1863 let storage = inner_enabled.storage;
1864 let eviction = inner_enabled.eviction;
1865 tokio::task::spawn(async move {
1866 Self::purge_impl(storage, eviction, &key, PurgeAction::Delete, span)
1867 .await
1868 .map_err(|e| {
1869 warn!("Failed to purge {key} (context: {context}): {e}");
1870 e
1871 })
1872 })
1873 }
1874
1875 #[cfg_attr(not(feature = "trace"), allow(unused_mut))]
1876 async fn purge_impl(
1877 storage: &'static (dyn storage::Storage + Sync),
1878 eviction: Option<&'static (dyn eviction::EvictionManager + Sync)>,
1879 key: &CompactCacheKey,
1880 action: PurgeAction,
1881 mut span: Span,
1882 ) -> Result<bool> {
1883 let target = storage::PurgeTarget::Active(key);
1884 let result = match action {
1885 PurgeAction::Delete => {
1886 storage
1887 .purge(target, PurgeType::Invalidation, &span.handle())
1888 .await
1889 }
1890 PurgeAction::Expire => storage.expire(target, &span.handle()).await,
1891 };
1892 let purged = match result.as_ref() {
1893 Ok(storage::PurgeOutcome::NotFound) | Err(_) => false,
1894 Ok(storage::PurgeOutcome::Purged(entry_id)) => {
1895 if let Some(eviction) = eviction {
1896 eviction.remove(target.removed_entry(*entry_id));
1897 }
1898 true
1899 }
1900 Ok(storage::PurgeOutcome::Expired) => true,
1902 };
1903 span.set_tag(|| trace::Tag::new("purged", purged));
1904 span.set_tag(|| {
1907 trace::Tag::new(
1908 "purge_outcome",
1909 match result.as_ref() {
1910 Ok(storage::PurgeOutcome::NotFound) => "not_found",
1911 Ok(storage::PurgeOutcome::Purged(_)) => "deleted",
1912 Ok(storage::PurgeOutcome::Expired) => "expired",
1913 Err(_) => "error",
1914 },
1915 )
1916 });
1917 result?;
1918 Ok(purged)
1919 }
1920
1921 pub fn cacheable_prediction(&self) -> bool {
1925 if let Some(predictor) = self.inner().predictor {
1926 predictor.cacheable_prediction(self.cache_key())
1927 } else {
1928 true
1929 }
1930 }
1931
1932 pub fn predicted_uncacheable_reason(&self) -> Option<NoCacheReason> {
1938 self.inner
1939 .as_ref()
1940 .and_then(|inner| inner.predicted_uncacheable_reason)
1941 }
1942
1943 pub fn response_became_cacheable(&self) {
1946 if let Some(predictor) = self.inner().predictor {
1947 predictor.mark_cacheable(self.cache_key());
1948 }
1949 }
1950
1951 pub fn response_became_uncacheable(&self, reason: NoCacheReason) {
1954 if let Some(predictor) = self.inner().predictor {
1955 predictor.mark_uncacheable(self.cache_key(), reason);
1956 }
1957 }
1958
1959 pub fn tag_as_subrequest(&mut self) {
1961 self.inner_enabled_mut()
1962 .traces
1963 .cache_span
1964 .set_tag(|| Tag::new("is_subrequest", true))
1965 }
1966}
1967
1968#[cfg(test)]
1969mod tests {
1970 use super::*;
1971 use crate::lock::{CacheLock, UnusableFill};
1972 use async_trait::async_trait;
1973 use http::StatusCode;
1974 use std::any::Any;
1975 use std::num::NonZeroU32;
1976 use std::sync::atomic::{AtomicUsize, Ordering};
1977 use std::sync::{LazyLock, Mutex};
1978
1979 struct UpdateOkStorage {
1981 purge_ok: bool,
1982 }
1983 struct IdentifiedEntryStorage {
1984 append: bool,
1985 }
1986 struct OneShotLookupStorage {
1987 entries: Mutex<Vec<(CompactCacheKey, CacheMeta)>>,
1988 }
1989 struct ExpiringStorage {
1992 expired: Mutex<Option<CompactCacheKey>>,
1993 }
1994 struct EmptyHitHandler {
1995 entry_id: Option<u64>,
1996 }
1997 struct IdentifiedMissHandler {
1998 finish: MissFinishType,
1999 }
2000 struct CountingDeferPolicy(AtomicUsize);
2001 struct CountingReadyPolicy(AtomicUsize);
2002 #[derive(Default)]
2003 struct RecordingEviction {
2004 removed: Mutex<Option<eviction::CacheEntryKey>>,
2005 accessed: Mutex<Option<eviction::CacheEntryKey>>,
2006 admitted: Mutex<Option<eviction::CacheEntryKey>>,
2007 incremented: Mutex<Option<(eviction::CacheEntryKey, usize, Option<usize>)>>,
2008 }
2009
2010 static UPDATE_OK_STORAGE: UpdateOkStorage = UpdateOkStorage { purge_ok: false };
2011 static PURGE_OK_STORAGE: UpdateOkStorage = UpdateOkStorage { purge_ok: true };
2012 static IDENTIFIED_CREATED_STORAGE: IdentifiedEntryStorage =
2013 IdentifiedEntryStorage { append: false };
2014 static IDENTIFIED_APPENDED_STORAGE: IdentifiedEntryStorage =
2015 IdentifiedEntryStorage { append: true };
2016 static ONE_SHOT_LOOKUP_STORAGE: OneShotLookupStorage = OneShotLookupStorage {
2019 entries: Mutex::new(Vec::new()),
2020 };
2021 static EXPIRING_STORAGE: ExpiringStorage = ExpiringStorage {
2022 expired: Mutex::new(None),
2023 };
2024 static RAW_MISS_DEFER_POLICY: CountingDeferPolicy = CountingDeferPolicy(AtomicUsize::new(0));
2025 static VALID_AFTER_DEFER_POLICY: CountingDeferPolicy = CountingDeferPolicy(AtomicUsize::new(0));
2026 static STALE_DEFER_POLICY: CountingDeferPolicy = CountingDeferPolicy(AtomicUsize::new(0));
2027 static RAW_MISS_READY_POLICY: CountingReadyPolicy = CountingReadyPolicy(AtomicUsize::new(0));
2028 static TWO_USE_ADMISSION_POLICY: LazyLock<admission::MinUsesAdmissionPolicy> =
2029 LazyLock::new(|| admission::MinUsesAdmissionPolicy::new(NonZeroU32::new(2).unwrap()));
2030 impl AdmissionPolicy for CountingDeferPolicy {
2031 fn observe(&self, _key: &CacheKey) -> Decision {
2032 self.0.fetch_add(1, Ordering::Relaxed);
2033 Decision::Defer { observed: 1 }
2034 }
2035 }
2036
2037 impl AdmissionPolicy for CountingReadyPolicy {
2038 fn observe(&self, _key: &CacheKey) -> Decision {
2039 let observed = self.0.fetch_add(1, Ordering::Relaxed) + 1;
2040 Decision::Ready {
2041 observed: observed as u32,
2042 }
2043 }
2044 }
2045
2046 #[async_trait]
2047 impl storage::HandleHit for EmptyHitHandler {
2048 async fn read_body(&mut self) -> Result<Option<bytes::Bytes>> {
2049 Ok(None)
2050 }
2051
2052 async fn finish(
2053 self: Box<Self>,
2054 _storage: &'static (dyn Storage + Sync),
2055 _key: &CacheKey,
2056 _trace: &trace::SpanHandle,
2057 ) -> Result<()> {
2058 Ok(())
2059 }
2060
2061 fn as_any(&self) -> &(dyn Any + Send + Sync) {
2062 self
2063 }
2064
2065 fn as_any_mut(&mut self) -> &mut (dyn Any + Send + Sync) {
2066 self
2067 }
2068
2069 fn entry_id(&self) -> Option<eviction::CacheEntryId> {
2070 self.entry_id.map(eviction::CacheEntryId::new)
2071 }
2072 }
2073
2074 #[async_trait]
2075 impl storage::HandleMiss for IdentifiedMissHandler {
2076 async fn write_body(&mut self, _data: bytes::Bytes, _eof: bool) -> Result<()> {
2077 Ok(())
2078 }
2079
2080 async fn finish(self: Box<Self>) -> Result<MissFinishType> {
2081 Ok(self.finish)
2082 }
2083
2084 fn entry_id(&self) -> Option<eviction::CacheEntryId> {
2085 Some(eviction::CacheEntryId::new(7))
2086 }
2087 }
2088
2089 #[async_trait]
2090 impl Storage for UpdateOkStorage {
2091 async fn lookup(
2092 &'static self,
2093 _key: &CacheKey,
2094 _trace: &trace::SpanHandle,
2095 ) -> Result<Option<(CacheMeta, HitHandler)>> {
2096 Ok(None)
2097 }
2098
2099 async fn get_miss_handler(
2100 &'static self,
2101 _key: &CacheKey,
2102 _meta: &CacheMeta,
2103 _trace: &trace::SpanHandle,
2104 ) -> Result<MissHandler> {
2105 unreachable!("tests do not write bodies through this storage")
2106 }
2107
2108 async fn purge(
2109 &'static self,
2110 _target: storage::PurgeTarget<'_>,
2111 _purge_type: PurgeType,
2112 _trace: &trace::SpanHandle,
2113 ) -> Result<storage::PurgeOutcome> {
2114 Ok(if self.purge_ok {
2115 storage::PurgeOutcome::Purged(None)
2116 } else {
2117 storage::PurgeOutcome::NotFound
2118 })
2119 }
2120
2121 async fn update_meta(
2122 &'static self,
2123 _key: &CacheKey,
2124 _meta: &CacheMeta,
2125 _trace: &trace::SpanHandle,
2126 ) -> Result<bool> {
2127 Ok(true)
2128 }
2129
2130 fn as_any(&self) -> &(dyn Any + Send + Sync + 'static) {
2131 self
2132 }
2133 }
2134
2135 #[async_trait]
2136 impl Storage for ExpiringStorage {
2137 async fn lookup(
2138 &'static self,
2139 _key: &CacheKey,
2140 _trace: &trace::SpanHandle,
2141 ) -> Result<Option<(CacheMeta, HitHandler)>> {
2142 Ok(None)
2143 }
2144
2145 async fn get_miss_handler(
2146 &'static self,
2147 _key: &CacheKey,
2148 _meta: &CacheMeta,
2149 _trace: &trace::SpanHandle,
2150 ) -> Result<MissHandler> {
2151 unreachable!("tests do not write bodies through this storage")
2152 }
2153
2154 async fn purge(
2155 &'static self,
2156 _target: storage::PurgeTarget<'_>,
2157 _purge_type: PurgeType,
2158 _trace: &trace::SpanHandle,
2159 ) -> Result<storage::PurgeOutcome> {
2160 unreachable!("storage that can expire must not be asked to delete")
2161 }
2162
2163 async fn expire(
2164 &'static self,
2165 target: storage::PurgeTarget<'_>,
2166 _trace: &trace::SpanHandle,
2167 ) -> Result<storage::PurgeOutcome> {
2168 *self.expired.lock().unwrap() = Some(target.key().clone());
2169 Ok(storage::PurgeOutcome::Expired)
2170 }
2171
2172 async fn update_meta(
2173 &'static self,
2174 _key: &CacheKey,
2175 _meta: &CacheMeta,
2176 _trace: &trace::SpanHandle,
2177 ) -> Result<bool> {
2178 Ok(true)
2179 }
2180
2181 fn as_any(&self) -> &(dyn Any + Send + Sync + 'static) {
2182 self
2183 }
2184 }
2185
2186 #[async_trait]
2187 impl Storage for IdentifiedEntryStorage {
2188 async fn lookup(
2189 &'static self,
2190 _key: &CacheKey,
2191 _trace: &trace::SpanHandle,
2192 ) -> Result<Option<(CacheMeta, HitHandler)>> {
2193 Ok(None)
2194 }
2195
2196 async fn get_miss_handler(
2197 &'static self,
2198 _key: &CacheKey,
2199 _meta: &CacheMeta,
2200 _trace: &trace::SpanHandle,
2201 ) -> Result<MissHandler> {
2202 let finish = if self.append {
2203 MissFinishType::Appended(2, Some(9))
2204 } else {
2205 MissFinishType::Created(1)
2206 };
2207 Ok(Box::new(IdentifiedMissHandler { finish }))
2208 }
2209
2210 async fn purge(
2211 &'static self,
2212 target: storage::PurgeTarget<'_>,
2213 _purge_type: PurgeType,
2214 _trace: &trace::SpanHandle,
2215 ) -> Result<storage::PurgeOutcome> {
2216 let entry_id = match target {
2217 storage::PurgeTarget::Active(_) => Some(eviction::CacheEntryId::new(1)),
2218 storage::PurgeTarget::Exact(_) => None,
2219 };
2220 Ok(storage::PurgeOutcome::Purged(entry_id))
2221 }
2222
2223 async fn update_meta(
2224 &'static self,
2225 _key: &CacheKey,
2226 _meta: &CacheMeta,
2227 _trace: &trace::SpanHandle,
2228 ) -> Result<bool> {
2229 Ok(true)
2230 }
2231
2232 fn as_any(&self) -> &(dyn Any + Send + Sync + 'static) {
2233 self
2234 }
2235 }
2236
2237 #[async_trait]
2238 impl eviction::EvictionManager for RecordingEviction {
2239 fn total_size(&self) -> usize {
2240 0
2241 }
2242
2243 fn total_items(&self) -> usize {
2244 0
2245 }
2246
2247 fn evicted_size(&self) -> usize {
2248 0
2249 }
2250
2251 fn evicted_items(&self) -> usize {
2252 0
2253 }
2254
2255 fn admit(
2256 &self,
2257 item: eviction::CacheEntryKey,
2258 _size: usize,
2259 _fresh_until: SystemTime,
2260 ) -> Vec<eviction::CacheEntryKey> {
2261 *self.admitted.lock().unwrap() = Some(item);
2262 Vec::new()
2263 }
2264
2265 fn increment_weight(
2266 &self,
2267 item: &eviction::CacheEntryKey,
2268 delta: usize,
2269 max_weight: Option<usize>,
2270 ) -> Vec<eviction::CacheEntryKey> {
2271 *self.incremented.lock().unwrap() = Some((item.clone(), delta, max_weight));
2272 Vec::new()
2273 }
2274
2275 fn remove(&self, item: eviction::CacheEntryKeyRef<'_>) {
2276 *self.removed.lock().unwrap() = Some(eviction::CacheEntryKey::from_entry_id(
2277 item.key().clone(),
2278 item.entry_id(),
2279 ));
2280 }
2281
2282 fn access(
2283 &self,
2284 item: &eviction::CacheEntryKey,
2285 _size: usize,
2286 _fresh_until: SystemTime,
2287 ) -> bool {
2288 *self.accessed.lock().unwrap() = Some(item.clone());
2289 true
2290 }
2291
2292 fn peek(&self, _item: &eviction::CacheEntryKey) -> bool {
2293 false
2294 }
2295
2296 async fn save(&self, _dir_path: &str) -> Result<()> {
2297 Ok(())
2298 }
2299
2300 async fn load(&self, _dir_path: &str) -> Result<()> {
2301 Ok(())
2302 }
2303 }
2304
2305 #[async_trait]
2306 impl Storage for OneShotLookupStorage {
2307 async fn lookup(
2308 &'static self,
2309 key: &CacheKey,
2310 _trace: &trace::SpanHandle,
2311 ) -> Result<Option<(CacheMeta, HitHandler)>> {
2312 let compact_key = key.to_compact();
2313 let mut entries = self.entries.lock().unwrap();
2314 let Some(pos) = entries
2315 .iter()
2316 .position(|(entry_key, _)| entry_key == &compact_key)
2317 else {
2318 return Ok(None);
2319 };
2320 let (_, meta) = entries.remove(pos);
2321 Ok(Some((meta, Box::new(EmptyHitHandler { entry_id: None }))))
2322 }
2323
2324 async fn get_miss_handler(
2325 &'static self,
2326 _key: &CacheKey,
2327 _meta: &CacheMeta,
2328 _trace: &trace::SpanHandle,
2329 ) -> Result<MissHandler> {
2330 unreachable!("tests do not write bodies through this storage")
2331 }
2332
2333 async fn purge(
2334 &'static self,
2335 _target: storage::PurgeTarget<'_>,
2336 _purge_type: PurgeType,
2337 _trace: &trace::SpanHandle,
2338 ) -> Result<storage::PurgeOutcome> {
2339 Ok(storage::PurgeOutcome::NotFound)
2340 }
2341
2342 async fn update_meta(
2343 &'static self,
2344 _key: &CacheKey,
2345 _meta: &CacheMeta,
2346 _trace: &trace::SpanHandle,
2347 ) -> Result<bool> {
2348 Ok(true)
2349 }
2350
2351 fn as_any(&self) -> &(dyn Any + Send + Sync + 'static) {
2352 self
2353 }
2354 }
2355
2356 fn test_meta(created: SystemTime) -> CacheMeta {
2357 let header = ResponseHeader::build(StatusCode::OK, None).unwrap();
2358 CacheMeta::new(created + Duration::from_secs(60), created, 30, 30, header)
2359 }
2360
2361 fn cache_with_stale_meta(meta: CacheMeta, key: CacheKey) -> HttpCache {
2362 let mut cache = HttpCache::new();
2363 cache.enable(&UPDATE_OK_STORAGE, None, None, None, None);
2364 cache.set_cache_key(key);
2365 cache.phase = CachePhase::Stale;
2366 cache.inner_enabled_mut().meta = Some(meta);
2367 cache
2368 }
2369
2370 static FILL_INTEREST_LOCK: LazyLock<CacheLock> =
2371 LazyLock::new(|| CacheLock::new(Duration::from_secs(30)));
2372
2373 const WRONG_PLACE: u64 = 7;
2374 const SOMEWHERE_ELSE: u64 = 9;
2375
2376 const NO_GOOD: &str = "NoGoodToThisReader";
2379 const NO_GOOD_EITHER: &str = "AlsoNoGood";
2380
2381 fn cannot_use(token: u64) -> UnusableFills {
2382 UnusableFills {
2383 fills: vec![UnusableFill {
2384 token,
2385 reason: NO_GOOD,
2386 }]
2387 .into(),
2388 }
2389 }
2390
2391 fn locked_reader(key: &str, interest: Option<UnusableFills>) -> HttpCache {
2392 let mut cache_key = CacheKey::new(key, "");
2393 if let Some(interest) = interest {
2394 cache_key.extensions.insert(interest);
2395 }
2396 let mut cache = HttpCache::new();
2397 cache.enable(
2398 &UPDATE_OK_STORAGE,
2399 None,
2400 None,
2401 Some(&*FILL_INTEREST_LOCK),
2402 None,
2403 );
2404 cache.set_cache_key(cache_key);
2405 cache
2406 }
2407
2408 #[tokio::test]
2412 async fn a_reader_that_stops_waiting_reports_its_own_reason() {
2413 let key = "stops-waiting";
2414
2415 let mut writer = locked_reader(key, None);
2416 assert!(writer.cache_lookup().await.unwrap().is_none());
2417 assert!(!writer.is_cache_locked(), "the first request is the writer");
2418
2419 let mut reader = locked_reader(key, Some(cannot_use(WRONG_PLACE)));
2420 assert!(reader.cache_lookup().await.unwrap().is_none());
2421 assert!(reader.is_cache_locked(), "the second request coalesces");
2422
2423 let waiting = tokio::spawn(async move {
2425 let status = reader.cache_lock_wait().await;
2426 (status, reader.lock_abandon())
2427 });
2428 tokio::task::yield_now().await;
2429 assert!(!waiting.is_finished(), "nothing published yet");
2430
2431 writer.lock_publish_fill_tokens(&[WRONG_PLACE]);
2432
2433 assert_eq!(
2434 waiting.await.unwrap(),
2435 (
2436 LockWaitOutcome::Abandoned {
2437 reason: NoCacheReason::Custom(NO_GOOD),
2438 token: WRONG_PLACE,
2439 },
2440 Some(LockAbandon {
2441 reason: NoCacheReason::Custom(NO_GOOD),
2442 token: WRONG_PLACE,
2443 })
2444 ),
2445 "its own reason and token, on the outcome and on the digest alike"
2446 );
2447
2448 let mut other = locked_reader(key, None);
2451 assert!(other.cache_lookup().await.unwrap().is_none());
2452 assert!(other.is_cache_locked(), "the lock is untouched");
2453
2454 writer.release_write_lock(NoCacheReason::StorageError);
2455 }
2456
2457 #[tokio::test]
2460 async fn the_reason_comes_from_the_token_that_matched() {
2461 let key = "reason-per-token";
2462
2463 let mut writer = locked_reader(key, None);
2464 assert!(writer.cache_lookup().await.unwrap().is_none());
2465
2466 let interest = UnusableFills {
2467 fills: vec![
2468 UnusableFill {
2469 token: WRONG_PLACE,
2470 reason: NO_GOOD,
2471 },
2472 UnusableFill {
2473 token: SOMEWHERE_ELSE,
2474 reason: NO_GOOD_EITHER,
2475 },
2476 ]
2477 .into(),
2478 };
2479 let mut reader = locked_reader(key, Some(interest));
2480 assert!(reader.cache_lookup().await.unwrap().is_none());
2481 assert!(reader.is_cache_locked());
2482
2483 let waiting = tokio::spawn(async move {
2484 let status = reader.cache_lock_wait().await;
2485 (status, reader.lock_abandon())
2486 });
2487 tokio::task::yield_now().await;
2488
2489 writer.lock_publish_fill_tokens(&[SOMEWHERE_ELSE]);
2491
2492 assert_eq!(
2493 waiting.await.unwrap(),
2494 (
2495 LockWaitOutcome::Abandoned {
2496 reason: NoCacheReason::Custom(NO_GOOD_EITHER),
2497 token: SOMEWHERE_ELSE,
2498 },
2499 Some(LockAbandon {
2500 reason: NoCacheReason::Custom(NO_GOOD_EITHER),
2501 token: SOMEWHERE_ELSE,
2502 })
2503 ),
2504 "the matched token's own reason, not the first in the set"
2505 );
2506
2507 writer.release_write_lock(NoCacheReason::StorageError);
2508 }
2509
2510 #[tokio::test]
2512 async fn a_reader_naming_unpublished_tokens_still_waits() {
2513 let key = "unpublished-tokens";
2514
2515 let mut writer = locked_reader(key, None);
2516 assert!(writer.cache_lookup().await.unwrap().is_none());
2517
2518 let mut reader = locked_reader(key, Some(cannot_use(WRONG_PLACE)));
2519 assert!(reader.cache_lookup().await.unwrap().is_none());
2520 assert!(reader.is_cache_locked());
2521
2522 let waiting = tokio::spawn(async move { reader.cache_lock_wait().await });
2523 tokio::task::yield_now().await;
2524 assert!(!waiting.is_finished(), "the reader is still coalescing");
2525
2526 writer.release_write_lock(NoCacheReason::StorageError);
2527
2528 assert_eq!(waiting.await.unwrap(), LockWaitOutcome::TransientError);
2529 }
2530
2531 fn cache_with_lookup_storage(key: CacheKey) -> HttpCache {
2532 let mut cache = HttpCache::new();
2533 cache.enable(&ONE_SHOT_LOOKUP_STORAGE, None, None, None, None);
2534 cache.set_cache_key(key);
2535 cache
2536 }
2537
2538 #[tokio::test]
2539 async fn purge_removes_identified_entry() {
2540 let recording = Box::leak(Box::new(RecordingEviction::default()));
2541 let key = CacheKey::new("expanded-purge", "").to_compact();
2542
2543 assert!(HttpCache::purge_impl(
2544 &IDENTIFIED_CREATED_STORAGE,
2545 Some(recording),
2546 &key,
2547 PurgeAction::Delete,
2548 trace::Span::inactive(),
2549 )
2550 .await
2551 .unwrap());
2552
2553 let removed = recording
2554 .removed
2555 .lock()
2556 .unwrap()
2557 .take()
2558 .expect("purge should remove the identified entry");
2559 assert_eq!(
2560 removed,
2561 eviction::CacheEntryKey::identified(key, eviction::CacheEntryId::new(1))
2562 );
2563 assert!(recording.accessed.lock().unwrap().is_none());
2564 assert!(recording.admitted.lock().unwrap().is_none());
2565 assert!(recording.incremented.lock().unwrap().is_none());
2566 }
2567
2568 #[tokio::test]
2569 async fn purge_removes_key_only_entry() {
2570 let recording = Box::leak(Box::new(RecordingEviction::default()));
2571 let key = CacheKey::new("key-only-purge", "").to_compact();
2572
2573 assert!(HttpCache::purge_impl(
2574 &PURGE_OK_STORAGE,
2575 Some(recording),
2576 &key,
2577 PurgeAction::Delete,
2578 trace::Span::inactive(),
2579 )
2580 .await
2581 .unwrap());
2582
2583 let removed = recording
2584 .removed
2585 .lock()
2586 .unwrap()
2587 .take()
2588 .expect("purge should remove the key-only entry");
2589 assert_eq!(removed, eviction::CacheEntryKey::key_only(key));
2590 }
2591
2592 #[tokio::test]
2593 async fn expiring_an_entry_keeps_it_tracked_by_eviction() {
2594 let recording = Box::leak(Box::new(RecordingEviction::default()));
2595 let key = CacheKey::new("expire-keeps-entry", "").to_compact();
2596
2597 assert!(HttpCache::purge_impl(
2598 &EXPIRING_STORAGE,
2599 Some(recording),
2600 &key,
2601 PurgeAction::Expire,
2602 trace::Span::inactive(),
2603 )
2604 .await
2605 .unwrap());
2606
2607 assert_eq!(
2608 EXPIRING_STORAGE.expired.lock().unwrap().take(),
2609 Some(key),
2610 "storage should have been asked to expire the target"
2611 );
2612 assert!(
2613 recording.removed.lock().unwrap().is_none(),
2614 "the entry is still stored, so eviction must keep tracking it"
2615 );
2616 }
2617
2618 #[tokio::test]
2619 async fn expiring_falls_back_to_deleting_when_storage_cannot_mark_stale() {
2620 let recording = Box::leak(Box::new(RecordingEviction::default()));
2621 let key = CacheKey::new("expire-falls-back", "").to_compact();
2622
2623 assert!(HttpCache::purge_impl(
2624 &PURGE_OK_STORAGE,
2625 Some(recording),
2626 &key,
2627 PurgeAction::Expire,
2628 trace::Span::inactive(),
2629 )
2630 .await
2631 .unwrap());
2632
2633 let removed = recording
2634 .removed
2635 .lock()
2636 .unwrap()
2637 .take()
2638 .expect("the fallback deletes, so eviction must stop tracking the entry");
2639 assert_eq!(removed, eviction::CacheEntryKey::key_only(key));
2640 }
2641
2642 #[tokio::test]
2643 async fn the_expire_fallback_reports_nothing_purged_for_an_absent_entry() {
2644 let recording = Box::leak(Box::new(RecordingEviction::default()));
2645 let key = CacheKey::new("expire-absent", "").to_compact();
2646
2647 assert!(!HttpCache::purge_impl(
2648 &UPDATE_OK_STORAGE,
2649 Some(recording),
2650 &key,
2651 PurgeAction::Expire,
2652 trace::Span::inactive(),
2653 )
2654 .await
2655 .unwrap());
2656
2657 assert!(recording.removed.lock().unwrap().is_none());
2658 }
2659
2660 #[test]
2661 fn cache_hit_passes_entry_id_to_eviction() {
2662 let recording = Box::leak(Box::new(RecordingEviction::default()));
2663 let key = CacheKey::new("identified-hit", "");
2664 let mut cache = HttpCache::new();
2665 cache.enable(
2666 &IDENTIFIED_CREATED_STORAGE,
2667 Some(recording),
2668 None,
2669 None,
2670 None,
2671 );
2672 cache.set_cache_key(key.clone());
2673 cache.cache_found(
2674 test_meta(SystemTime::now()),
2675 Box::new(EmptyHitHandler { entry_id: Some(7) }),
2676 HitStatus::Fresh,
2677 );
2678
2679 assert_eq!(
2680 recording.accessed.lock().unwrap().take(),
2681 Some(eviction::CacheEntryKey::identified(
2682 key.to_compact(),
2683 eviction::CacheEntryId::new(7)
2684 ))
2685 );
2686 assert!(recording.removed.lock().unwrap().is_none());
2687 assert!(recording.admitted.lock().unwrap().is_none());
2688 assert!(recording.incremented.lock().unwrap().is_none());
2689 }
2690
2691 #[test]
2692 fn a_forced_expiry_that_serves_stale_is_stale_with_its_windows_intact() {
2693 let mut cache = HttpCache::new();
2694 cache.enable(&IDENTIFIED_CREATED_STORAGE, None, None, None, None);
2695 cache.set_cache_key(CacheKey::new("force-expired-serve-stale", ""));
2696 cache.cache_found(
2697 test_meta(SystemTime::now()),
2698 Box::new(EmptyHitHandler { entry_id: None }),
2699 HitStatus::ForceExpiredServeStale,
2700 );
2701
2702 assert_eq!(cache.phase(), CachePhase::Stale);
2703 assert_eq!(cache.cache_meta().stale_while_revalidate_sec(), 30);
2704 assert_eq!(cache.cache_meta().stale_if_error_sec(), 30);
2705 }
2706
2707 #[test]
2708 fn a_forced_expiry_that_serves_stale_is_neither_fresh_nor_a_miss() {
2709 let status = HitStatus::ForceExpiredServeStale;
2710
2711 assert!(!status.is_fresh());
2712 assert!(!status.is_treated_as_miss());
2713 assert_eq!(status.as_str(), "force_expired_serve_stale");
2714 }
2715
2716 #[tokio::test]
2717 async fn cache_miss_passes_entry_id_to_eviction() {
2718 let recording = Box::leak(Box::new(RecordingEviction::default()));
2719 let key = CacheKey::new("identified-miss", "");
2720 let mut cache = HttpCache::new();
2721 cache.enable(
2722 &IDENTIFIED_CREATED_STORAGE,
2723 Some(recording),
2724 None,
2725 None,
2726 None,
2727 );
2728 cache.set_cache_key(key.clone());
2729 cache.cache_miss();
2730 cache.set_cache_meta(test_meta(SystemTime::now()));
2731 cache.set_miss_handler().await.unwrap();
2732 cache.finish_miss_handler().await.unwrap();
2733 assert_eq!(
2734 recording.admitted.lock().unwrap().take(),
2735 Some(eviction::CacheEntryKey::identified(
2736 key.to_compact(),
2737 eviction::CacheEntryId::new(7)
2738 ))
2739 );
2740 assert!(recording.incremented.lock().unwrap().is_none());
2741 assert!(recording.removed.lock().unwrap().is_none());
2742 assert!(recording.accessed.lock().unwrap().is_none());
2743
2744 let mut cache = HttpCache::new();
2745 cache.enable(
2746 &IDENTIFIED_APPENDED_STORAGE,
2747 Some(recording),
2748 None,
2749 None,
2750 None,
2751 );
2752 cache.set_cache_key(key.clone());
2753 cache.cache_miss();
2754 cache.set_cache_meta(test_meta(SystemTime::now()));
2755 cache.set_miss_handler().await.unwrap();
2756 cache.finish_miss_handler().await.unwrap();
2757 assert_eq!(
2758 recording.incremented.lock().unwrap().take(),
2759 Some((
2760 eviction::CacheEntryKey::identified(
2761 key.to_compact(),
2762 eviction::CacheEntryId::new(7)
2763 ),
2764 2,
2765 Some(9)
2766 ))
2767 );
2768 assert!(recording.admitted.lock().unwrap().is_none());
2769 assert!(recording.removed.lock().unwrap().is_none());
2770 assert!(recording.accessed.lock().unwrap().is_none());
2771 }
2772
2773 #[tokio::test]
2774 async fn raw_storage_miss_can_defer_admission() {
2775 RAW_MISS_DEFER_POLICY.0.store(0, Ordering::Relaxed);
2776 let mut cache = HttpCache::new();
2777 cache.enable(&UPDATE_OK_STORAGE, None, None, None, None);
2778 cache.set_admission_policy(&RAW_MISS_DEFER_POLICY);
2779 cache.set_cache_key(CacheKey::new("deferred-storage-miss", ""));
2780
2781 assert!(cache.cache_lookup().await.unwrap().is_none());
2782 assert_eq!(cache.phase(), CachePhase::Disabled(NoCacheReason::Deferred));
2783 assert_eq!(
2784 cache.admission_decision(),
2785 Some(Decision::Defer { observed: 1 })
2786 );
2787 assert_eq!(RAW_MISS_DEFER_POLICY.0.load(Ordering::Relaxed), 1);
2788 }
2789
2790 #[tokio::test]
2791 async fn second_storage_miss_can_proceed_to_fill() {
2792 let key = CacheKey::new("two-use-admission", "");
2793
2794 let mut first = HttpCache::new();
2795 first.enable(&UPDATE_OK_STORAGE, None, None, None, None);
2796 first.set_admission_policy(&*TWO_USE_ADMISSION_POLICY);
2797 first.set_cache_key(key.clone());
2798 assert!(first.cache_lookup().await.unwrap().is_none());
2799 assert_eq!(first.phase(), CachePhase::Disabled(NoCacheReason::Deferred));
2800
2801 let mut second = HttpCache::new();
2802 second.enable(&UPDATE_OK_STORAGE, None, None, None, None);
2803 second.set_admission_policy(&*TWO_USE_ADMISSION_POLICY);
2804 second.set_cache_key(key);
2805 assert!(second.cache_lookup().await.unwrap().is_none());
2806 assert_eq!(
2807 second.admission_decision(),
2808 Some(Decision::Ready { observed: 2 })
2809 );
2810 assert_eq!(second.phase(), CachePhase::CacheKey);
2811 second.cache_miss();
2812 assert_eq!(second.phase(), CachePhase::Miss);
2813 }
2814
2815 #[tokio::test]
2816 async fn repeated_raw_miss_is_observed_once_per_request() {
2817 RAW_MISS_READY_POLICY.0.store(0, Ordering::Relaxed);
2818 let mut cache = HttpCache::new();
2819 cache.enable(&UPDATE_OK_STORAGE, None, None, None, None);
2820 cache.set_admission_policy(&RAW_MISS_READY_POLICY);
2821 cache.set_cache_key(CacheKey::new("repeated-ready-admission", ""));
2822
2823 assert!(cache.cache_lookup().await.unwrap().is_none());
2824 assert!(cache.cache_lookup().await.unwrap().is_none());
2825 assert_eq!(RAW_MISS_READY_POLICY.0.load(Ordering::Relaxed), 1);
2826 assert_eq!(
2827 cache.admission_decision(),
2828 Some(Decision::Ready { observed: 1 })
2829 );
2830 }
2831
2832 #[tokio::test]
2833 async fn stale_refill_does_not_observe_admission() {
2834 STALE_DEFER_POLICY.0.store(0, Ordering::Relaxed);
2835 let key = CacheKey::new("stale-refill-admission", "");
2836 let mut cache = HttpCache::new();
2837 cache.enable(&UPDATE_OK_STORAGE, None, None, None, None);
2838 cache.set_admission_policy(&STALE_DEFER_POLICY);
2839 cache.set_cache_key(key);
2840 cache.phase = CachePhase::Stale;
2841 cache.inner_enabled_mut().meta = Some(test_meta(SystemTime::now()));
2842
2843 assert!(cache.cache_lookup().await.unwrap().is_none());
2844 assert_eq!(STALE_DEFER_POLICY.0.load(Ordering::Relaxed), 0);
2845 assert_eq!(cache.admission_decision(), None);
2846 cache.cache_miss();
2847 assert_eq!(cache.phase(), CachePhase::Miss);
2848 }
2849
2850 #[tokio::test]
2851 async fn valid_after_rejection_does_not_observe_admission() {
2852 VALID_AFTER_DEFER_POLICY.0.store(0, Ordering::Relaxed);
2853 let created = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
2854 let key = CacheKey::new("valid-after-not-admission", "");
2855 ONE_SHOT_LOOKUP_STORAGE
2856 .entries
2857 .lock()
2858 .unwrap()
2859 .push((key.to_compact(), test_meta(created)));
2860
2861 let mut cache = HttpCache::new();
2862 cache.enable(&ONE_SHOT_LOOKUP_STORAGE, None, None, None, None);
2863 cache.set_admission_policy(&VALID_AFTER_DEFER_POLICY);
2864 cache.set_cache_key(key);
2865 cache.inner_enabled_mut().valid_after = Some(created + Duration::from_secs(1));
2866
2867 assert!(cache.cache_lookup().await.unwrap().is_none());
2868 assert_eq!(cache.phase(), CachePhase::CacheKey);
2869 assert_eq!(cache.admission_decision(), None);
2870 assert_eq!(VALID_AFTER_DEFER_POLICY.0.load(Ordering::Relaxed), 0);
2871 }
2872
2873 #[test]
2874 fn test_set_cache_meta_preserves_stale_provenance() {
2875 let created = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
2876 let family_start = SystemTime::UNIX_EPOCH + Duration::from_secs(80);
2877 let refresh = SystemTime::UNIX_EPOCH + Duration::from_secs(200);
2878 let variance = [1; 16];
2879
2880 let mut old_meta = test_meta(created);
2881 old_meta.set_provenance(family_start);
2882 old_meta.set_variance_key(variance);
2883 let mut cache = cache_with_stale_meta(old_meta, CacheKey::new("preserve", ""));
2884
2885 cache.set_cache_meta(test_meta(refresh));
2886
2887 assert_eq!(cache.phase(), CachePhase::Expired);
2888 assert_eq!(cache.cache_meta().created(), refresh);
2889 assert_eq!(cache.cache_meta().provenance(), family_start);
2890 assert_eq!(cache.inner_enabled().stale_meta_variance, Some(variance));
2891 }
2892
2893 #[tokio::test]
2894 async fn test_revalidate_cache_meta_preserves_created_and_provenance() {
2895 let created = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
2896 let family_start = SystemTime::UNIX_EPOCH + Duration::from_secs(80);
2897 let revalidated_at = SystemTime::UNIX_EPOCH + Duration::from_secs(200);
2898
2899 let mut old_meta = test_meta(created);
2900 old_meta.set_provenance(family_start);
2901 let mut cache = cache_with_stale_meta(old_meta, CacheKey::new("revalidate", ""));
2902
2903 cache
2904 .revalidate_cache_meta(test_meta(revalidated_at))
2905 .await
2906 .unwrap();
2907
2908 assert_eq!(cache.phase(), CachePhase::Revalidated);
2909 assert_eq!(cache.cache_meta().created(), created);
2910 assert_eq!(cache.cache_meta().updated(), revalidated_at);
2911 assert_eq!(cache.cache_meta().provenance(), family_start);
2912 }
2913
2914 #[tokio::test]
2915 async fn revalidating_an_expired_meta_makes_it_fresh_again() {
2916 let created = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
2917 let expired_at = created + Duration::from_secs(30);
2918 let revalidated_at = SystemTime::UNIX_EPOCH + Duration::from_secs(200);
2919
2920 let mut old_meta = test_meta(created);
2924 old_meta.expire_at(expired_at);
2925 assert!(!old_meta.is_fresh(expired_at + Duration::from_secs(1)));
2926
2927 let mut cache = cache_with_stale_meta(old_meta, CacheKey::new("revalidate-expired", ""));
2928 cache
2929 .revalidate_cache_meta(test_meta(revalidated_at))
2930 .await
2931 .unwrap();
2932
2933 let meta = cache.cache_meta();
2934 assert!(meta.is_fresh(revalidated_at));
2935 assert_eq!(meta.fresh_until(), revalidated_at + Duration::from_secs(60));
2936 }
2937
2938 #[test]
2939 fn test_update_variance_preserves_provenance_when_primary_variance_unchanged() {
2940 let created = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
2941 let family_start = SystemTime::UNIX_EPOCH + Duration::from_secs(80);
2942 let refresh = SystemTime::UNIX_EPOCH + Duration::from_secs(200);
2943 let variance = [1; 16];
2944
2945 let mut old_meta = test_meta(created);
2946 old_meta.set_provenance(family_start);
2947 old_meta.set_variance_key(variance);
2948 let mut cache = cache_with_stale_meta(old_meta, CacheKey::new("same-vary", ""));
2949
2950 cache.set_cache_meta(test_meta(refresh));
2951 cache.update_variance(Some(variance));
2952
2953 assert_eq!(cache.cache_meta().provenance(), family_start);
2954 assert_eq!(cache.cache_meta().variance(), Some(variance));
2955 assert!(cache.cache_key().get_variance_key().is_none());
2956 }
2957
2958 #[test]
2959 fn test_update_variance_resets_provenance_when_primary_variance_changes() {
2960 let created = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
2961 let family_start = SystemTime::UNIX_EPOCH + Duration::from_secs(80);
2962 let refresh = SystemTime::UNIX_EPOCH + Duration::from_secs(200);
2963 let old_variance = [1; 16];
2964 let new_variance = [2; 16];
2965
2966 let mut old_meta = test_meta(created);
2967 old_meta.set_provenance(family_start);
2968 old_meta.set_variance_key(old_variance);
2969 let mut cache = cache_with_stale_meta(old_meta, CacheKey::new("changed-vary", ""));
2970
2971 cache.set_cache_meta(test_meta(refresh));
2972 cache.update_variance(Some(new_variance));
2973
2974 assert_eq!(cache.cache_meta().provenance(), refresh);
2975 assert_eq!(cache.cache_meta().variance(), Some(new_variance));
2976 assert!(cache.cache_key().get_variance_key().is_none());
2977 }
2978
2979 #[test]
2980 fn test_update_variance_resets_provenance_when_primary_variance_appears() {
2981 let created = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
2982 let family_start = SystemTime::UNIX_EPOCH + Duration::from_secs(80);
2983 let refresh = SystemTime::UNIX_EPOCH + Duration::from_secs(200);
2984 let variance = [1; 16];
2985
2986 let mut old_meta = test_meta(created);
2987 old_meta.set_provenance(family_start);
2988 let mut cache = cache_with_stale_meta(old_meta, CacheKey::new("vary-appears", ""));
2989
2990 cache.set_cache_meta(test_meta(refresh));
2991 cache.update_variance(Some(variance));
2992
2993 assert_eq!(cache.cache_meta().provenance(), refresh);
2994 assert_eq!(cache.cache_meta().variance(), Some(variance));
2995 assert!(cache.cache_key().get_variance_key().is_none());
2996 }
2997
2998 #[test]
2999 fn test_update_variance_resets_provenance_when_secondary_takes_primary_slot() {
3000 let created = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
3001 let family_start = SystemTime::UNIX_EPOCH + Duration::from_secs(80);
3002 let refresh = SystemTime::UNIX_EPOCH + Duration::from_secs(200);
3003 let old_variance = [1; 16];
3004 let mut key = CacheKey::new("secondary-takeover", "");
3005 key.set_variance_key(old_variance);
3006
3007 let mut old_meta = test_meta(created);
3008 old_meta.set_provenance(family_start);
3009 old_meta.set_variance_key(old_variance);
3010 let mut cache = cache_with_stale_meta(old_meta, key);
3011
3012 cache.set_cache_meta(test_meta(refresh));
3013 cache.update_variance(None);
3014
3015 assert_eq!(cache.cache_meta().provenance(), refresh);
3016 assert!(cache.cache_meta().variance().is_none());
3017 assert!(cache.cache_key().get_variance_key().is_none());
3018 }
3019
3020 #[test]
3021 fn test_update_variance_preserves_provenance_when_secondary_variance_unchanged() {
3022 let created = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
3023 let family_start = SystemTime::UNIX_EPOCH + Duration::from_secs(80);
3024 let refresh = SystemTime::UNIX_EPOCH + Duration::from_secs(200);
3025 let variance = [1; 16];
3026 let mut key = CacheKey::new("secondary-same-vary", "");
3027 key.set_variance_key(variance);
3028
3029 let mut old_meta = test_meta(created);
3030 old_meta.set_provenance(family_start);
3031 old_meta.set_variance_key(variance);
3032 let mut cache = cache_with_stale_meta(old_meta, key);
3033
3034 cache.set_cache_meta(test_meta(refresh));
3035 cache.update_variance(Some(variance));
3036
3037 assert_eq!(cache.cache_meta().provenance(), family_start);
3038 assert_eq!(cache.cache_meta().variance(), Some(variance));
3039 assert_eq!(cache.cache_key().get_variance_key(), Some(&variance));
3040 }
3041
3042 #[tokio::test]
3043 async fn test_cache_vary_lookup_uses_provenance_for_valid_after() {
3044 let family_start = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
3045 let secondary_created = SystemTime::UNIX_EPOCH + Duration::from_secs(150);
3046 let primary_refreshed = SystemTime::UNIX_EPOCH + Duration::from_secs(200);
3047 let primary_variance = [1; 16];
3048 let secondary_variance = [2; 16];
3049
3050 let mut primary_meta = test_meta(primary_refreshed);
3051 primary_meta.set_provenance(family_start);
3052 primary_meta.set_variance_key(primary_variance);
3053
3054 let mut secondary_meta = test_meta(secondary_created);
3055 secondary_meta.set_provenance(family_start);
3056 secondary_meta.set_variance_key(secondary_variance);
3057
3058 let mut cache = cache_with_lookup_storage(CacheKey::new("valid-after-provenance", ""));
3059 assert!(!cache.cache_vary_lookup(secondary_variance, &primary_meta));
3060 assert_eq!(
3061 cache.inner_enabled().valid_after,
3062 Some(primary_meta.provenance())
3063 );
3064
3065 let secondary_key = cache.cache_key().to_compact();
3066 ONE_SHOT_LOOKUP_STORAGE
3067 .entries
3068 .lock()
3069 .unwrap()
3070 .push((secondary_key, secondary_meta));
3071
3072 assert!(cache.cache_lookup().await.unwrap().is_some());
3073 }
3074}