hitbox_core/offload.rs
1//! Offload trait for background task execution.
2//!
3//! This module provides the [`Offload`] trait which abstracts over
4//! different implementations for spawning background tasks.
5//!
6//! # Lifetime Parameter
7//!
8//! The `Offload<'a>` trait is parameterized by a lifetime to support both:
9//! - `'static` futures (for real background execution with `OffloadManager`)
10//! - Non-`'static` futures (for middleware integration with `DisabledOffload`)
11//!
12//! This design allows `CacheFuture` to work with borrowed upstreams (like reqwest
13//! middleware's `Next<'_>`) when background revalidation is not needed.
14
15use std::future::Future;
16
17use smol_str::SmolStr;
18
19/// Trait for spawning background tasks.
20///
21/// This trait allows components like `CacheFuture` and `CompositionBackend`
22/// to offload work to be executed in the background without blocking the main
23/// request path.
24///
25/// # Lifetime Parameter
26///
27/// The lifetime parameter `'a` determines what futures can be spawned:
28/// - `Offload<'static>`: Can spawn futures that live forever (real background tasks)
29/// - `Offload<'a>`: Can only spawn futures that live at least as long as `'a`
30///
31/// This enables [`DisabledOffload`] to accept any lifetime (since it doesn't
32/// actually spawn anything), while `OffloadManager` requires `'static`.
33///
34/// # Implementations
35///
36/// - [`DisabledOffload`]: Does nothing, accepts any lifetime. Use when background
37/// execution is not needed (e.g., reqwest middleware integration).
38/// - `OffloadManager` (in `hitbox` crate): Real background execution, requires `'static`.
39///
40/// # Clone bound
41///
42/// Implementors should use `Arc` internally to ensure all cloned instances
43/// share the same configuration and state.
44///
45/// # Example
46///
47/// ```ignore
48/// use hitbox_core::Offload;
49///
50/// fn offload_cache_write<'a, O: Offload<'a>>(offload: &O, key: String) {
51/// offload.spawn("cache_write", async move {
52/// // Perform background cache write
53/// println!("Writing to cache: {}", key);
54/// });
55/// }
56/// ```
57pub trait Offload<'a>: Send + Sync + Clone {
58 /// Spawn a future to be executed in the background.
59 ///
60 /// The future will be executed asynchronously and its result will be
61 /// handled according to the implementation's policy.
62 ///
63 /// # Arguments
64 ///
65 /// * `kind` - A label categorizing the task type (e.g., "revalidate", "cache_write").
66 /// Used for metrics and tracing.
67 /// * `future` - The future to execute in the background. Must be `Send + 'a`.
68 /// For real background execution, `'a` must be `'static`.
69 fn spawn<F>(&self, kind: impl Into<SmolStr>, future: F)
70 where
71 F: Future<Output = ()> + Send + 'a;
72}
73
74/// A disabled offload implementation that discards all spawned tasks.
75///
76/// This implementation accepts futures with any lifetime since it doesn't
77/// actually execute them. Use this when:
78/// - Background revalidation is not needed
79/// - Integrating with middleware systems that have non-`'static` types
80/// (e.g., reqwest middleware's `Next<'_>`)
81///
82/// # Example
83///
84/// ```
85/// use hitbox_core::{Offload, DisabledOffload};
86///
87/// let offload = DisabledOffload;
88///
89/// // This works even with non-'static futures
90/// let borrowed_data = String::from("hello");
91/// let borrowed_ref = &borrowed_data;
92/// offload.spawn("test", async move {
93/// // Would use borrowed_ref here
94/// let _ = borrowed_ref;
95/// });
96/// ```
97#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
98pub struct DisabledOffload;
99
100impl<'a> Offload<'a> for DisabledOffload {
101 #[inline]
102 fn spawn<F>(&self, _kind: impl Into<SmolStr>, _future: F)
103 where
104 F: Future<Output = ()> + Send + 'a,
105 {
106 // Intentionally does nothing.
107 // The future is dropped without execution.
108 }
109}