Skip to main content

signet_libmdbx/tx/
cache.rs

1//! Caches for [`Database`] info, used by the [`TxSync`] and [`TxUnsync`] types.
2//!
3//! This module defines cache types for storing database handles within
4//! transactions. Caches improve performance by avoiding repeated lookups of
5//! database information.
6//!
7//! The primary caches are:
8//! - [`DbCache`]: A simple inline cache using `SmallVec` for efficient storage
9//!   of a small number of database handles. Used in unsynchronized
10//!   transactions via [`RefCell`].
11//! - [`SharedCache`]: A thread-safe cache using `Arc<RwLock<...>>` for
12//!   synchronized transactions.
13//!
14//! [`TxSync`]: crate::tx::aliases::TxSync
15//! [`TxUnsync`]: crate::tx::aliases::TxUnsync
16
17use crate::Database;
18use parking_lot::RwLock;
19use smallvec::SmallVec;
20use std::{
21    cell::RefCell,
22    hash::{Hash, Hasher},
23    sync::Arc,
24};
25
26/// Cache trait for transaction-local database handles.
27///
28/// This is used by the [`SyncKind`] trait to define the cache type for each
29/// transaction kind.
30///
31/// [`SyncKind`]: crate::tx::kind::SyncKind
32pub trait Cache: Clone + Default + std::fmt::Debug {
33    /// Read a database entry from the cache.
34    fn read_db(&self, name_hash: u64) -> Option<Database>;
35
36    /// Write a database entry to the cache.
37    fn write_db(&self, db: CachedDb);
38
39    /// Remove a database entry from the cache by dbi.
40    fn remove_dbi(&self, dbi: ffi::MDBX_dbi);
41}
42
43/// Cached database entry.
44///
45/// Uses hash-only comparison since 64-bit hash collisions are negligible
46/// for practical database counts.
47#[derive(Debug, Clone, Copy)]
48pub struct CachedDb {
49    /// Hash of database name (None hashes distinctly from any string).
50    name_hash: u64,
51    /// The cached database (dbi + flags).
52    db: Database,
53}
54
55impl CachedDb {
56    /// Creates a new cached database entry.
57    pub(crate) fn new(name: Option<&str>, db: Database) -> Self {
58        let name_hash = Self::hash_name(name);
59        Self { name_hash, db }
60    }
61
62    #[inline]
63    pub(crate) fn hash_name(name: Option<&str>) -> u64 {
64        let mut hasher = std::hash::DefaultHasher::new();
65        name.hash(&mut hasher);
66        hasher.finish()
67    }
68}
69
70impl From<CachedDb> for Database {
71    fn from(value: CachedDb) -> Self {
72        value.db
73    }
74}
75
76/// Simple cache container for database handles.
77///
78/// Uses inline storage for the common case (most apps use < 16 databases).
79#[derive(Debug, Default, Clone)]
80#[repr(transparent)]
81pub struct DbCache(SmallVec<[CachedDb; 16]>);
82
83impl DbCache {
84    /// Read a database entry from the cache.
85    fn read_db(&self, name_hash: u64) -> Option<Database> {
86        for entry in self.0.iter() {
87            if entry.name_hash == name_hash {
88                return Some(entry.db);
89            }
90        }
91        None
92    }
93
94    /// Write a database entry to the cache.
95    fn write_db(&mut self, db: CachedDb) {
96        for entry in self.0.iter() {
97            if entry.name_hash == db.name_hash {
98                return; // Another thread beat us
99            }
100        }
101        self.0.push(db);
102    }
103
104    /// Remove a database entry from the cache by dbi.
105    fn remove_dbi(&mut self, dbi: ffi::MDBX_dbi) {
106        self.0.retain(|entry| entry.db.dbi() != dbi);
107    }
108}
109
110/// Simple cache container for database handles.
111///
112/// Uses inline storage for the common case (most apps use < 16 databases).
113#[derive(Debug, Clone)]
114pub struct SharedCache {
115    cache: Arc<RwLock<DbCache>>,
116}
117
118impl SharedCache {
119    /// Creates a new empty cache.
120    fn new() -> Self {
121        Self { cache: Arc::new(RwLock::new(DbCache::default())) }
122    }
123
124    /// Returns a read guard to the cache.
125    fn read(&self) -> parking_lot::RwLockReadGuard<'_, DbCache> {
126        self.cache.read()
127    }
128
129    /// Returns a write guard to the cache.
130    fn write(&self) -> parking_lot::RwLockWriteGuard<'_, DbCache> {
131        self.cache.write()
132    }
133}
134
135impl Cache for SharedCache {
136    /// Read a database entry from the cache.
137    fn read_db(&self, name_hash: u64) -> Option<Database> {
138        let cache = self.read();
139        cache.read_db(name_hash)
140    }
141
142    /// Write a database entry to the cache.
143    fn write_db(&self, db: CachedDb) {
144        let mut cache = self.write();
145        cache.write_db(db);
146    }
147
148    /// Remove a database entry from the cache by dbi.
149    fn remove_dbi(&self, dbi: ffi::MDBX_dbi) {
150        let mut cache = self.write();
151        cache.remove_dbi(dbi);
152    }
153}
154
155impl Default for SharedCache {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161impl Cache for RefCell<DbCache> {
162    /// Read a database entry from the cache.
163    fn read_db(&self, name_hash: u64) -> Option<Database> {
164        let cache = self.borrow();
165        cache.read_db(name_hash)
166    }
167
168    /// Write a database entry to the cache.
169    fn write_db(&self, db: CachedDb) {
170        let mut cache = self.borrow_mut();
171        cache.write_db(db);
172    }
173
174    /// Remove a database entry from the cache by dbi.
175    fn remove_dbi(&self, dbi: ffi::MDBX_dbi) {
176        let mut cache = self.borrow_mut();
177        cache.remove_dbi(dbi);
178    }
179}