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;