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