1use crate::{
2 CacheKey, CacheLayerPlan, CacheModelError, CacheTopology, FreshnessPolicy, InvalidationSet,
3 RequestCoalescingMode,
4};
5use serde::{Deserialize, Serialize};
6use std::sync::Arc;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct CacheEntry {
10 pub key: CacheKey,
11 pub value: String,
12 pub stored_at: crate::CacheInstant,
13 pub freshness: FreshnessPolicy,
14 pub tags: InvalidationSet,
15 pub scope: crate::CacheScope,
16 pub layers: CacheLayerPlan,
17}
18
19impl CacheEntry {
20 pub fn age_seconds(&self, now: crate::CacheInstant) -> u64 {
21 now.as_unix_seconds()
22 .saturating_sub(self.stored_at.as_unix_seconds())
23 }
24
25 pub fn is_fresh(&self, now: crate::CacheInstant) -> bool {
26 self.age_seconds(now) <= self.freshness.ttl_seconds()
27 }
28
29 pub fn is_stale_but_servable(&self, now: crate::CacheInstant) -> bool {
30 if self.is_fresh(now) {
31 return false;
32 }
33
34 self.freshness
35 .stale_while_revalidate_seconds()
36 .is_some_and(|swr| {
37 self.age_seconds(now) <= self.freshness.ttl_seconds().saturating_add(swr)
38 })
39 }
40
41 pub fn is_expired(&self, now: crate::CacheInstant) -> bool {
42 !self.is_fresh(now) && !self.is_stale_but_servable(now)
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
47pub enum CacheLookupState {
48 Miss,
49 Fresh,
50 Stale,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54pub struct CacheLookup {
55 pub state: CacheLookupState,
56 pub entry: Option<CacheEntry>,
57 pub needs_revalidation: bool,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
61pub struct CacheMetrics {
62 pub hits: u64,
63 pub stale_hits: u64,
64 pub misses: u64,
65 pub invalidations: u64,
66 pub coalesced_waits: u64,
67 pub fills_started: u64,
68 pub fills_completed: u64,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72pub enum FillDecision {
73 Start(FillLease),
74 Coalesced { key: CacheKey, holder: String },
75 Uncoalesced,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79pub struct FillLease {
80 pub key: CacheKey,
81 pub holder: String,
82}
83
84#[derive(Debug, Clone)]
85pub struct CacheRuntime {
86 topology: CacheTopology,
87 backend: crate::CacheBackendAdapter,
88}
89
90impl CacheRuntime {
91 pub fn new(topology: CacheTopology) -> Self {
92 Self::with_backend(topology, crate::CacheBackendAdapter::new(topology))
93 }
94
95 #[cfg(test)]
96 #[allow(dead_code)]
97 #[doc(hidden)]
98 pub fn local_for_testing(topology: CacheTopology) -> Self {
99 Self::with_backend(
100 topology,
101 crate::CacheBackendAdapter::local_for_testing(topology),
102 )
103 }
104
105 pub fn with_shared_runtime(
106 topology: CacheTopology,
107 runtime: Arc<dyn crate::DistributedCacheRuntime>,
108 ) -> Self {
109 Self::with_backend(
110 topology,
111 crate::CacheBackendAdapter::with_shared_runtime(topology, runtime),
112 )
113 }
114
115 pub fn with_backend(topology: CacheTopology, backend: crate::CacheBackendAdapter) -> Self {
116 Self { topology, backend }
117 }
118
119 pub fn topology(&self) -> CacheTopology {
120 self.topology
121 }
122
123 pub fn backend_kind(&self) -> crate::CacheBackendKind {
124 self.backend.kind()
125 }
126
127 pub fn backend_is_shared(&self) -> bool {
128 self.backend.is_shared()
129 }
130
131 pub fn metrics(&self) -> CacheMetrics {
132 self.backend.metrics()
133 }
134
135 pub fn insert(
136 &mut self,
137 plan: &crate::ApplicationCachePlan,
138 value: impl Into<String>,
139 now: crate::CacheInstant,
140 ) {
141 let entry = CacheEntry {
142 key: plan.key().clone(),
143 value: value.into(),
144 stored_at: now,
145 freshness: plan.freshness(),
146 tags: plan.tags().clone(),
147 scope: plan.scope().clone(),
148 layers: plan.layers().clone(),
149 };
150 self.backend.insert(entry);
151 }
152
153 pub fn lookup(&mut self, key: &CacheKey, now: crate::CacheInstant) -> CacheLookup {
154 self.backend.lookup(key, now)
155 }
156
157 pub fn invalidate(&mut self, tags: &InvalidationSet) -> Vec<CacheKey> {
158 self.backend.invalidate(tags)
159 }
160
161 pub fn begin_fill(
162 &mut self,
163 key: &CacheKey,
164 mode: RequestCoalescingMode,
165 holder: impl Into<String>,
166 ) -> FillDecision {
167 self.backend.begin_fill(key, mode, holder)
168 }
169
170 pub fn complete_fill(&mut self, lease: &FillLease) -> Result<(), CacheModelError> {
171 self.backend.complete_fill(lease)
172 }
173}