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;