foyer_storage/engine/
mod.rs

1// Copyright 2026 foyer Project Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{any::Any, fmt::Debug, sync::Arc};
16
17use foyer_common::{
18    code::{StorageKey, StorageValue},
19    error::Result,
20    metrics::Metrics,
21    properties::{Age, Properties},
22    spawn::Spawner,
23};
24use foyer_memory::Piece;
25use futures_core::future::BoxFuture;
26
27use crate::{filter::StorageFilterResult, io::engine::IoEngine, keeper::PieceRef, Device};
28
29/// Source context for populated entry.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Populated {
33    /// The age of the entry.
34    pub age: Age,
35}
36
37/// Load result.
38pub enum Load<K, V, P> {
39    /// Load entry success.
40    Entry {
41        /// The key of the entry.
42        key: K,
43        /// The value of the entry.
44        value: V,
45        /// The populated context of the entry.
46        populated: Populated,
47    },
48    /// Load entry success from disk cache write queue.
49    Piece {
50        /// The piece of the entry.
51        piece: Piece<K, V, P>,
52        /// The populated context of the entry.
53        populated: Populated,
54    },
55    /// The entry may be in the disk cache, the read io is throttled.
56    Throttled,
57    /// Disk cache miss.
58    Miss,
59}
60
61impl<K, V, P> Debug for Load<K, V, P> {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        match self {
64            Load::Entry { populated, .. } => f.debug_struct("Load::Entry").field("populated", populated).finish(),
65            Load::Piece { piece, populated } => f
66                .debug_struct("Load::Piece")
67                .field("piece", piece)
68                .field("populated", populated)
69                .finish(),
70            Load::Throttled => f.debug_struct("Load::Throttled").finish(),
71            Load::Miss => f.debug_struct("Load::Miss").finish(),
72        }
73    }
74}
75
76impl<K, V, P> Load<K, V, P> {
77    /// Return `Some` with the entry if load success, otherwise return `None`.
78    pub fn entry(self) -> Option<(K, V, Populated)> {
79        match self {
80            Load::Entry { key, value, populated } => Some((key, value, populated)),
81            _ => None,
82        }
83    }
84
85    /// Return `Some` with the entry if load success, otherwise return `None`.
86    ///
87    /// Only key and value will be returned.
88    pub fn kv(self) -> Option<(K, V)> {
89        match self {
90            Load::Entry { key, value, .. } => Some((key, value)),
91            _ => None,
92        }
93    }
94
95    /// Check if the load result is a cache miss.
96    pub fn is_miss(&self) -> bool {
97        matches!(self, Load::Miss)
98    }
99
100    /// Check if the load result is miss caused by io throttled.
101    pub fn is_throttled(&self) -> bool {
102        matches!(self, Load::Throttled)
103    }
104}
105
106/// The recover mode of the disk cache.
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
109#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
110pub enum RecoverMode {
111    /// Do not recover disk cache.
112    ///
113    /// For updatable cache, either [`RecoverMode::None`] or the tombstone log must be used to prevent from phantom
114    /// entry when reopen.
115    None,
116    /// Recover disk cache and skip errors.
117    #[default]
118    Quiet,
119    /// Recover disk cache and panic on errors.
120    Strict,
121}
122
123/// Context for building the disk cache engine.
124pub struct EngineBuildContext {
125    /// IO engine for the disk cache engine.
126    pub io_engine: Arc<dyn IoEngine>,
127    /// Shared metrics for all components.
128    pub metrics: Arc<Metrics>,
129    /// The runtime for the disk cache engine.
130    pub spawner: Spawner,
131    /// The recover mode of the disk cache engine.
132    pub recover_mode: RecoverMode,
133}
134
135/// Disk cache engine config trait.
136#[expect(clippy::type_complexity)]
137pub trait EngineConfig<K, V, P>: Send + Sync + 'static + Debug
138where
139    K: StorageKey,
140    V: StorageValue,
141    P: Properties,
142{
143    /// Build the engine with the given configurations.
144    fn build(self: Box<Self>, ctx: EngineBuildContext) -> BoxFuture<'static, Result<Arc<dyn Engine<K, V, P>>>>;
145
146    /// Box the config.
147    fn boxed(self) -> Box<Self>
148    where
149        Self: Sized,
150    {
151        Box::new(self)
152    }
153}
154
155/// Disk cache engine trait.
156pub trait Engine<K, V, P>: Send + Sync + 'static + Debug + Any
157where
158    K: StorageKey,
159    V: StorageValue,
160    P: Properties,
161{
162    /// Get the device used by this disk cache engine.
163    fn device(&self) -> &Arc<dyn Device>;
164
165    /// Return if the given key can be picked by the disk cache engine.
166    fn filter(&self, hash: u64, estimated_size: usize) -> StorageFilterResult;
167
168    /// Push a in-memory cache piece to the disk cache write queue.
169    fn enqueue(&self, piece: PieceRef<K, V, P>, estimated_size: usize);
170
171    /// Load a cache entry from the disk cache.
172    ///
173    /// `load` may return a false-positive result on entry key hash collision. It's the caller's responsibility to
174    /// check if the returned key matches the given key.
175    fn load(&self, hash: u64) -> BoxFuture<'static, Result<Load<K, V, P>>>;
176
177    /// Delete the cache entry with the given key from the disk cache.
178    fn delete(&self, hash: u64);
179
180    /// Check if the disk cache contains a cached entry with the given key.
181    ///
182    /// `contains` may return a false-positive result if there is a hash collision with the given key.
183    fn may_contains(&self, hash: u64) -> bool;
184
185    /// Delete all cached entries of the disk cache.
186    fn destroy(&self) -> BoxFuture<'static, Result<()>>;
187
188    /// Wait for the ongoing flush and reclaim tasks to finish.
189    fn wait(&self) -> BoxFuture<'static, ()>;
190
191    /// Close the disk cache gracefully.
192    ///
193    /// `close` will wait for all ongoing flush and reclaim tasks to finish.
194    fn close(&self) -> BoxFuture<'static, Result<()>>;
195}
196
197pub mod block;
198pub mod noop;