crates_docs/tools/docs/cache/
ttl.rs1use std::time::Duration;
4
5const DEFAULT_JITTER_RATIO: f64 = 0.1;
17
18const DEFAULT_CRATE_DOCS_TTL_SECS: u64 = 3600;
30
31const DEFAULT_SEARCH_RESULTS_TTL_SECS: u64 = 300;
43
44const DEFAULT_ITEM_DOCS_TTL_SECS: u64 = 1800;
56
57#[derive(Debug, Clone, Copy)]
68pub struct DocCacheTtl {
69 pub crate_docs_secs: u64,
71 pub search_results_secs: u64,
73 pub item_docs_secs: u64,
75 pub jitter_ratio: f64,
80}
81
82impl Default for DocCacheTtl {
83 fn default() -> Self {
84 Self {
85 crate_docs_secs: DEFAULT_CRATE_DOCS_TTL_SECS,
86 search_results_secs: DEFAULT_SEARCH_RESULTS_TTL_SECS,
87 item_docs_secs: DEFAULT_ITEM_DOCS_TTL_SECS,
88 jitter_ratio: DEFAULT_JITTER_RATIO,
89 }
90 }
91}
92
93impl DocCacheTtl {
94 #[must_use]
104 pub fn from_cache_config(config: &crate::cache::CacheConfig) -> Self {
105 Self {
106 crate_docs_secs: config
107 .crate_docs_ttl_secs
108 .unwrap_or(DEFAULT_CRATE_DOCS_TTL_SECS),
109 search_results_secs: config
110 .search_results_ttl_secs
111 .unwrap_or(DEFAULT_SEARCH_RESULTS_TTL_SECS),
112 item_docs_secs: config
113 .item_docs_ttl_secs
114 .unwrap_or(DEFAULT_ITEM_DOCS_TTL_SECS),
115 jitter_ratio: DEFAULT_JITTER_RATIO,
116 }
117 }
118
119 #[must_use]
129 #[allow(clippy::cast_possible_truncation)]
130 #[allow(clippy::cast_sign_loss)]
131 #[allow(clippy::cast_precision_loss)]
132 pub fn apply_jitter(&self, base_ttl: u64) -> u64 {
133 if self.jitter_ratio <= 0.0 {
134 return base_ttl;
135 }
136
137 let ratio = self.jitter_ratio.clamp(0.0, 1.0);
138 let rng = fastrand::f64();
139 let offset = (rng * 2.0 - 1.0) * ratio;
140
141 (base_ttl as f64 * (1.0 + offset)).max(1.0) as u64
142 }
143
144 #[must_use]
146 pub fn crate_docs_duration(&self) -> Duration {
147 Duration::from_secs(self.apply_jitter(self.crate_docs_secs))
148 }
149
150 #[must_use]
152 pub fn search_results_duration(&self) -> Duration {
153 Duration::from_secs(self.apply_jitter(self.search_results_secs))
154 }
155
156 #[must_use]
158 pub fn item_docs_duration(&self) -> Duration {
159 Duration::from_secs(self.apply_jitter(self.item_docs_secs))
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_doc_cache_ttl_default() {
169 let ttl = DocCacheTtl::default();
170 assert_eq!(ttl.crate_docs_secs, DEFAULT_CRATE_DOCS_TTL_SECS);
171 assert_eq!(ttl.search_results_secs, DEFAULT_SEARCH_RESULTS_TTL_SECS);
172 assert_eq!(ttl.item_docs_secs, DEFAULT_ITEM_DOCS_TTL_SECS);
173 assert!((ttl.jitter_ratio - DEFAULT_JITTER_RATIO).abs() < f64::EPSILON);
174 }
175
176 #[test]
177 fn test_doc_cache_ttl_from_config() {
178 let config = crate::cache::CacheConfig {
179 cache_type: "memory".to_string(),
180 memory_size: Some(1000),
181 redis_url: None,
182 key_prefix: String::new(),
183 default_ttl: Some(DEFAULT_CRATE_DOCS_TTL_SECS),
184 crate_docs_ttl_secs: Some(7200),
185 item_docs_ttl_secs: Some(DEFAULT_CRATE_DOCS_TTL_SECS),
186 search_results_ttl_secs: Some(600),
187 };
188 let ttl = DocCacheTtl::from_cache_config(&config);
189 assert_eq!(ttl.crate_docs_secs, 7200);
190 assert_eq!(ttl.item_docs_secs, DEFAULT_CRATE_DOCS_TTL_SECS);
191 assert_eq!(ttl.search_results_secs, 600);
192 }
193
194 #[test]
195 fn test_apply_jitter_no_jitter() {
196 let ttl = DocCacheTtl {
197 jitter_ratio: 0.0,
198 ..Default::default()
199 };
200 assert_eq!(ttl.apply_jitter(1000), 1000);
201 }
202
203 #[test]
204 fn test_apply_jitter_with_jitter() {
205 let ttl = DocCacheTtl {
206 jitter_ratio: 0.5,
207 ..Default::default()
208 };
209
210 for _ in 0..100 {
211 let jittered = ttl.apply_jitter(1000);
212 assert!((500..=1500).contains(&jittered));
213 }
214 }
215
216 #[test]
217 fn test_durations() {
218 let ttl = DocCacheTtl {
219 jitter_ratio: 0.0,
220 crate_docs_secs: DEFAULT_CRATE_DOCS_TTL_SECS,
221 search_results_secs: DEFAULT_SEARCH_RESULTS_TTL_SECS,
222 item_docs_secs: DEFAULT_ITEM_DOCS_TTL_SECS,
223 };
224
225 assert_eq!(
226 ttl.crate_docs_duration(),
227 Duration::from_secs(DEFAULT_CRATE_DOCS_TTL_SECS)
228 );
229 assert_eq!(
230 ttl.search_results_duration(),
231 Duration::from_secs(DEFAULT_SEARCH_RESULTS_TTL_SECS)
232 );
233 assert_eq!(
234 ttl.item_docs_duration(),
235 Duration::from_secs(DEFAULT_ITEM_DOCS_TTL_SECS)
236 );
237 }
238}