1use crate::{CacheKey, CachePolicy, CacheStorage, PutHandle, StoredEntry};
4use futures_lite::{AsyncRead, AsyncWrite};
5use moka::{future::Cache, ops::compute::Op};
6use std::{
7 fmt::{self, Debug, Formatter},
8 io,
9 pin::Pin,
10 sync::Arc,
11 task::{Context, Poll},
12 time::Duration,
13};
14use trillium_http::{Body, BodySource, Headers};
15
16const DEFAULT_MAX_CAPACITY_BYTES: u64 = 256 * 1024 * 1024;
17
18type Bucket = Arc<[Variant]>;
21
22#[derive(Clone)]
23struct Variant {
24 policy: Arc<CachePolicy>,
25 body: Arc<[u8]>,
26 trailers: Option<Headers>,
27}
28
29impl Debug for Variant {
30 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
31 f.debug_struct("Variant")
32 .field("body_len", &self.body.len())
33 .field("has_trailers", &self.trailers.is_some())
34 .finish_non_exhaustive()
35 }
36}
37
38#[derive(Clone)]
72pub struct InMemoryStorage {
73 cache: Cache<CacheKey, Bucket>,
74 max_capacity_bytes: Option<u64>,
75 time_to_idle: Option<Duration>,
76 time_to_live: Option<Duration>,
77}
78
79impl Debug for InMemoryStorage {
80 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
81 f.debug_struct("InMemoryStorage")
82 .field("entry_count", &self.cache.entry_count())
83 .field("weighted_size", &self.cache.weighted_size())
84 .field("max_capacity_bytes", &self.max_capacity_bytes)
85 .field("time_to_idle", &self.time_to_idle)
86 .field("time_to_live", &self.time_to_live)
87 .finish_non_exhaustive()
88 }
89}
90
91impl Default for InMemoryStorage {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97impl InMemoryStorage {
98 pub fn new() -> Self {
101 Self {
102 cache: build_cache(Some(DEFAULT_MAX_CAPACITY_BYTES), None, None),
103 max_capacity_bytes: Some(DEFAULT_MAX_CAPACITY_BYTES),
104 time_to_idle: None,
105 time_to_live: None,
106 }
107 }
108
109 pub fn with_max_capacity_bytes(mut self, bytes: u64) -> Self {
113 self.max_capacity_bytes = Some(bytes);
114 self.rebuild();
115 self
116 }
117
118 pub fn unbounded(mut self) -> Self {
122 self.max_capacity_bytes = None;
123 self.rebuild();
124 self
125 }
126
127 pub fn with_time_to_idle(mut self, duration: Duration) -> Self {
130 self.time_to_idle = Some(duration);
131 self.rebuild();
132 self
133 }
134
135 pub fn with_time_to_live(mut self, duration: Duration) -> Self {
143 self.time_to_live = Some(duration);
144 self.rebuild();
145 self
146 }
147
148 pub fn entry_count(&self) -> u64 {
153 self.cache.entry_count()
154 }
155
156 pub fn weighted_size(&self) -> u64 {
161 self.cache.weighted_size()
162 }
163
164 pub async fn run_pending_tasks(&self) {
169 self.cache.run_pending_tasks().await;
170 }
171
172 fn rebuild(&mut self) {
175 self.cache = build_cache(
176 self.max_capacity_bytes,
177 self.time_to_idle,
178 self.time_to_live,
179 );
180 }
181}
182
183fn build_cache(
184 max_capacity_bytes: Option<u64>,
185 time_to_idle: Option<Duration>,
186 time_to_live: Option<Duration>,
187) -> Cache<CacheKey, Bucket> {
188 let mut builder = Cache::<CacheKey, Bucket>::builder().weigher(weigh_bucket);
189 if let Some(cap) = max_capacity_bytes {
190 builder = builder.max_capacity(cap);
191 }
192 if let Some(tti) = time_to_idle {
193 builder = builder.time_to_idle(tti);
194 }
195 if let Some(ttl) = time_to_live {
196 builder = builder.time_to_live(ttl);
197 }
198 builder.build()
199}
200
201fn weigh_bucket(_key: &CacheKey, bucket: &Bucket) -> u32 {
202 let total: u64 = bucket.iter().map(|v| v.body.len() as u64).sum();
203 u32::try_from(total).unwrap_or(u32::MAX)
204}
205
206#[derive(Clone)]
209pub struct InMemoryEntry {
210 variant: Variant,
211 cache: Cache<CacheKey, Bucket>,
212 key: CacheKey,
213}
214
215impl Debug for InMemoryEntry {
216 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
217 f.debug_struct("InMemoryEntry")
218 .field("key", &self.key)
219 .field("variant", &self.variant)
220 .finish_non_exhaustive()
221 }
222}
223
224impl StoredEntry for InMemoryEntry {
225 fn policy(&self) -> &CachePolicy {
226 &self.variant.policy
227 }
228
229 async fn refresh_policy(&mut self, new_policy: CachePolicy) -> io::Result<()> {
230 let new_arc = Arc::new(new_policy);
231 self.variant.policy = Arc::clone(&new_arc);
235
236 self.cache
237 .entry(self.key.clone())
238 .and_compute_with(|maybe_entry| async move {
239 let Some(entry) = maybe_entry else {
240 return Op::Nop;
241 };
242 let bucket = entry.into_value();
243 let mut updated = false;
244 let new_variants: Vec<Variant> = bucket
245 .iter()
246 .map(|v| {
247 if !updated && v.policy.same_variant_as(&new_arc) {
248 updated = true;
249 Variant {
250 policy: Arc::clone(&new_arc),
251 body: Arc::clone(&v.body),
252 trailers: v.trailers.clone(),
253 }
254 } else {
255 v.clone()
256 }
257 })
258 .collect();
259 if updated {
260 Op::Put(Arc::from(new_variants.into_boxed_slice()))
261 } else {
262 Op::Nop
263 }
264 })
265 .await;
266 Ok(())
267 }
268
269 async fn open(self) -> io::Result<Body> {
270 let Variant { body, trailers, .. } = self.variant;
271 let len = u64::try_from(body.len()).ok();
272 let source = ReplayBodySource {
273 body,
274 position: 0,
275 trailers,
276 };
277 Ok(Body::new_with_trailers(source, len))
278 }
279}
280
281struct ReplayBodySource {
283 body: Arc<[u8]>,
284 position: usize,
285 trailers: Option<Headers>,
286}
287
288impl AsyncRead for ReplayBodySource {
289 fn poll_read(
290 mut self: Pin<&mut Self>,
291 _cx: &mut Context<'_>,
292 buf: &mut [u8],
293 ) -> Poll<io::Result<usize>> {
294 let remaining = self.body.len() - self.position;
295 let n = remaining.min(buf.len());
296 if n > 0 {
297 buf[..n].copy_from_slice(&self.body[self.position..self.position + n]);
298 self.position += n;
299 }
300 Poll::Ready(Ok(n))
301 }
302}
303
304impl BodySource for ReplayBodySource {
305 fn trailers(self: Pin<&mut Self>) -> Option<Headers> {
306 self.get_mut().trailers.take()
307 }
308}
309
310impl CacheStorage for InMemoryStorage {
311 type PutHandle = InMemoryPutHandle;
312 type StoredEntry = InMemoryEntry;
313
314 async fn get(&self, key: &CacheKey) -> Vec<Self::StoredEntry> {
315 let Some(bucket) = self.cache.get(key).await else {
316 return Vec::new();
317 };
318 bucket
319 .iter()
320 .map(|variant| InMemoryEntry {
321 variant: variant.clone(),
322 cache: self.cache.clone(),
323 key: key.clone(),
324 })
325 .collect()
326 }
327
328 async fn put(&self, key: CacheKey, policy: CachePolicy) -> io::Result<Self::PutHandle> {
329 Ok(InMemoryPutHandle {
330 cache: self.cache.clone(),
331 key,
332 policy,
333 buffer: Vec::new(),
334 })
335 }
336
337 async fn invalidate(&self, key: &CacheKey) {
338 self.cache.invalidate(key).await;
339 }
340}
341
342#[derive(Debug)]
348pub struct InMemoryPutHandle {
349 cache: Cache<CacheKey, Bucket>,
350 key: CacheKey,
351 policy: CachePolicy,
352 buffer: Vec<u8>,
353}
354
355impl AsyncWrite for InMemoryPutHandle {
356 fn poll_write(
357 mut self: Pin<&mut Self>,
358 _cx: &mut Context<'_>,
359 buf: &[u8],
360 ) -> Poll<io::Result<usize>> {
361 self.buffer.extend_from_slice(buf);
362 Poll::Ready(Ok(buf.len()))
363 }
364
365 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
366 Poll::Ready(Ok(()))
367 }
368
369 fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
370 Poll::Ready(Ok(()))
371 }
372}
373
374impl PutHandle for InMemoryPutHandle {
375 async fn finalize(self, trailers: Option<Headers>) -> io::Result<()> {
376 let Self {
377 cache,
378 key,
379 policy,
380 buffer,
381 } = self;
382 let new_variant = Variant {
383 policy: Arc::new(policy),
384 body: Arc::from(buffer.into_boxed_slice()),
385 trailers,
386 };
387
388 cache
389 .entry(key)
390 .and_upsert_with(|maybe_entry| async move {
391 let mut variants: Vec<Variant> = match maybe_entry {
392 Some(entry) => entry.into_value().to_vec(),
393 None => Vec::new(),
394 };
395 variants.retain(|v| !v.policy.same_variant_as(&new_variant.policy));
396 variants.push(new_variant);
397 Arc::from(variants.into_boxed_slice())
398 })
399 .await;
400 Ok(())
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407 use crate::test_helpers::*;
408 use futures_lite::{AsyncReadExt, AsyncWriteExt};
409 use std::time::SystemTime;
410 use trillium_client::Conn;
411 use trillium_http::{KnownHeaderName::*, Method, Status};
412 use trillium_testing::{TestResult, harness, test};
413
414 fn key() -> CacheKey {
415 CacheKey::new(Method::Get, "http://example.com/".parse().unwrap())
416 }
417
418 async fn store(storage: &InMemoryStorage, conn: &Conn, body: &[u8]) {
419 let policy = policy_from(conn, SystemTime::now(), private_cache());
420 let mut handle = storage.put(key(), policy).await.unwrap();
421 handle.write_all(body).await.unwrap();
422 handle.finalize(None).await.unwrap();
423 }
424
425 async fn read_body(entry: InMemoryEntry) -> Vec<u8> {
426 let mut body = entry.open().await.unwrap();
427 let mut buf = Vec::new();
428 body.read_to_end(&mut buf).await.unwrap();
429 buf
430 }
431
432 #[test(harness)]
433 async fn get_missing_key_returns_empty() -> TestResult {
434 let storage = InMemoryStorage::new();
435 assert!(storage.get(&key()).await.is_empty());
436 Ok(())
437 }
438
439 #[test(harness)]
440 async fn put_then_get_returns_entry() -> TestResult {
441 let storage = InMemoryStorage::new();
442 let conn = exchange(
443 Method::Get,
444 &[],
445 Status::Ok,
446 &[(CacheControl, "max-age=600")],
447 );
448 store(&storage, &conn, b"hello").await;
449 let result = storage.get(&key()).await;
450 assert_eq!(result.len(), 1);
451 assert_eq!(read_body(result[0].clone()).await, b"hello");
452 Ok(())
453 }
454
455 #[test(harness)]
456 async fn put_with_same_vary_replaces() -> TestResult {
457 let storage = InMemoryStorage::new();
458 let conn = exchange(
459 Method::Get,
460 &[(AcceptEncoding, "gzip")],
461 Status::Ok,
462 &[(CacheControl, "max-age=600"), (Vary, "Accept-Encoding")],
463 );
464 store(&storage, &conn, b"v1").await;
465 store(&storage, &conn, b"v2").await;
466 let result = storage.get(&key()).await;
467 assert_eq!(result.len(), 1);
468 assert_eq!(read_body(result[0].clone()).await, b"v2");
469 Ok(())
470 }
471
472 #[test(harness)]
473 async fn put_with_different_vary_appends() -> TestResult {
474 let storage = InMemoryStorage::new();
475 let gzip = exchange(
476 Method::Get,
477 &[(AcceptEncoding, "gzip")],
478 Status::Ok,
479 &[(CacheControl, "max-age=600"), (Vary, "Accept-Encoding")],
480 );
481 let br = exchange(
482 Method::Get,
483 &[(AcceptEncoding, "br")],
484 Status::Ok,
485 &[(CacheControl, "max-age=600"), (Vary, "Accept-Encoding")],
486 );
487 store(&storage, &gzip, b"gz").await;
488 store(&storage, &br, b"br").await;
489 let result = storage.get(&key()).await;
490 assert_eq!(result.len(), 2);
491 Ok(())
492 }
493
494 #[test(harness)]
495 async fn invalidate_removes_all_entries_for_key() -> TestResult {
496 let storage = InMemoryStorage::new();
497 let conn = exchange(
498 Method::Get,
499 &[],
500 Status::Ok,
501 &[(CacheControl, "max-age=600")],
502 );
503 store(&storage, &conn, b"x").await;
504 storage.run_pending_tasks().await;
505 assert_eq!(storage.entry_count(), 1);
506 storage.invalidate(&key()).await;
507 assert!(storage.get(&key()).await.is_empty());
508 storage.run_pending_tasks().await;
509 assert_eq!(storage.entry_count(), 0);
510 Ok(())
511 }
512
513 #[test(harness)]
514 async fn invalidate_does_not_touch_other_keys() -> TestResult {
515 let storage = InMemoryStorage::new();
516 let conn = exchange(
517 Method::Get,
518 &[],
519 Status::Ok,
520 &[(CacheControl, "max-age=600")],
521 );
522 let key_a = CacheKey::new(Method::Get, "http://a.example/".parse().unwrap());
523 let key_b = CacheKey::new(Method::Get, "http://b.example/".parse().unwrap());
524 {
525 let policy_a = policy_from(&conn, SystemTime::now(), private_cache());
526 let mut h = storage.put(key_a.clone(), policy_a).await.unwrap();
527 h.write_all(b"a").await.unwrap();
528 h.finalize(None).await.unwrap();
529 }
530 {
531 let policy_b = policy_from(&conn, SystemTime::now(), private_cache());
532 let mut h = storage.put(key_b.clone(), policy_b).await.unwrap();
533 h.write_all(b"b").await.unwrap();
534 h.finalize(None).await.unwrap();
535 }
536 storage.invalidate(&key_a).await;
537 assert!(storage.get(&key_a).await.is_empty());
538 assert_eq!(storage.get(&key_b).await.len(), 1);
539 Ok(())
540 }
541
542 #[test(harness)]
543 async fn drop_put_handle_without_finalize_discards() -> TestResult {
544 let storage = InMemoryStorage::new();
545 let conn = exchange(
546 Method::Get,
547 &[],
548 Status::Ok,
549 &[(CacheControl, "max-age=600")],
550 );
551 let policy = policy_from(&conn, SystemTime::now(), private_cache());
552 let mut handle = storage.put(key(), policy).await.unwrap();
553 handle.write_all(b"partial").await.unwrap();
554 drop(handle);
555 assert!(storage.get(&key()).await.is_empty());
556 Ok(())
557 }
558
559 #[test(harness)]
560 async fn refresh_policy_updates_storage() -> TestResult {
561 let storage = InMemoryStorage::new();
562 let conn = exchange(
563 Method::Get,
564 &[],
565 Status::Ok,
566 &[(CacheControl, "max-age=600")],
567 );
568 store(&storage, &conn, b"body").await;
569
570 let mut entries = storage.get(&key()).await;
571 let original_time = entries[0].policy().response_time;
572 let refreshed = exchange(
573 Method::Get,
574 &[],
575 Status::Ok,
576 &[(CacheControl, "max-age=1200")],
577 );
578 let new_policy = policy_from(
579 &refreshed,
580 original_time + Duration::from_secs(100),
581 private_cache(),
582 );
583 entries[0].refresh_policy(new_policy).await.unwrap();
584
585 let fresh = storage.get(&key()).await;
586 assert_eq!(fresh.len(), 1);
587 assert_ne!(fresh[0].policy().response_time, original_time);
588 Ok(())
589 }
590
591 #[test(harness)]
593 async fn size_cap_evicts_old_entries() -> TestResult {
594 let storage = InMemoryStorage::new().with_max_capacity_bytes(1024);
596 let conn = exchange(
597 Method::Get,
598 &[],
599 Status::Ok,
600 &[(CacheControl, "max-age=600")],
601 );
602 let body = vec![b'x'; 600];
603 for i in 0..10 {
604 let key = CacheKey::new(
605 Method::Get,
606 format!("http://example.com/{i}").parse().unwrap(),
607 );
608 let policy = policy_from(&conn, SystemTime::now(), private_cache());
609 let mut h = storage.put(key, policy).await.unwrap();
610 h.write_all(&body).await.unwrap();
611 h.finalize(None).await.unwrap();
612 }
613 storage.run_pending_tasks().await;
614 assert!(
615 storage.weighted_size() <= 1024,
616 "weighted size {} should be within cap of 1024",
617 storage.weighted_size()
618 );
619 Ok(())
620 }
621}