Skip to main content

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}