Skip to content

Commit fdb5348

Browse files
committed
feat: handle fallible LruCache in NonceCache
1 parent 54d476f commit fdb5348

File tree

3 files changed

+89
-19
lines changed

3 files changed

+89
-19
lines changed

stacks-common/src/util/lru_cache.rs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
use std::fmt::Display;
1717

18-
use hashbrown::HashMap;
18+
use hashbrown::{HashMap, HashSet};
1919

2020
/// Node in the doubly linked list
2121
struct Node<K, V> {
@@ -75,7 +75,12 @@ impl<K: Display, V: Display> Display for LruCache<K, V> {
7575

7676
impl<K: Eq + std::hash::Hash + Clone, V: Copy> LruCache<K, V> {
7777
/// Create a new LRU cache with the given capacity
78-
pub fn new(capacity: usize) -> Self {
78+
pub fn new(mut capacity: usize) -> Self {
79+
if capacity == 0 {
80+
error!("Capacity must be greater than 0. Defaulting to 1024.");
81+
capacity = 1024;
82+
}
83+
7984
LruCache {
8085
capacity,
8186
cache: HashMap::new(),
@@ -218,18 +223,37 @@ impl<K: Eq + std::hash::Hash + Clone, V: Copy> LruCache<K, V> {
218223

219224
/// Flush all dirty values in the cache, calling the given function, `f`,
220225
/// for each dirty value.
221-
pub fn flush<E>(&mut self, mut f: impl FnMut(&K, V) -> Result<(), E>) -> Result<(), E> {
222-
let mut index = self.head;
223-
while index != self.capacity {
224-
let next = self.order[index].next;
225-
if self.order[index].dirty {
226-
let value = self.order[index].value;
227-
f(&self.order[index].key, value)?;
228-
self.order[index].dirty = false;
226+
/// Outer result is an error iff the cache is corrupted and should be discarded.
227+
/// Inner result is an error iff the function, `f`, returns an error.
228+
pub fn flush<E>(
229+
&mut self,
230+
mut f: impl FnMut(&K, V) -> Result<(), E>,
231+
) -> Result<Result<(), E>, ()> {
232+
let mut current = self.head;
233+
234+
// Keep track of visited nodes to detect cycles
235+
let mut visited = HashSet::new();
236+
237+
while current != self.capacity {
238+
// Detect cycles
239+
if !visited.insert(current) {
240+
return Err(());
229241
}
230-
index = next;
242+
243+
let node = self.order.get_mut(current).ok_or(())?;
244+
let next = node.next;
245+
if node.dirty {
246+
let value = node.value;
247+
248+
// Call the flush function
249+
match f(&node.key, value) {
250+
Ok(()) => node.dirty = false,
251+
Err(e) => return Ok(Err(e)),
252+
}
253+
}
254+
current = next;
231255
}
232-
Ok(())
256+
Ok(Ok(()))
233257
}
234258
}
235259

stackslib/src/config/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,6 +2664,14 @@ impl MinerConfigFile {
26642664
} else {
26652665
miner_default_config.tenure_cost_limit_per_block_percentage
26662666
};
2667+
2668+
let nonce_cache_size = self
2669+
.nonce_cache_size
2670+
.unwrap_or(miner_default_config.nonce_cache_size);
2671+
if nonce_cache_size == 0 {
2672+
return Err("miner.nonce_cache_size must be greater than 0".to_string());
2673+
}
2674+
26672675
Ok(MinerConfig {
26682676
first_attempt_time_ms: self
26692677
.first_attempt_time_ms

stackslib/src/core/nonce_cache.rs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,23 @@ use crate::util_lib::db::{query_row, u64_to_sql, DBConn, Error as db_error};
3939
pub struct NonceCache {
4040
/// In-memory LRU cache of nonces.
4141
cache: LruCache<StacksAddress, u64>,
42+
max_size: usize,
4243
}
4344

4445
impl NonceCache {
4546
pub fn new(max_size: usize) -> Self {
4647
Self {
4748
cache: LruCache::new(max_size),
49+
max_size,
50+
}
51+
}
52+
53+
/// Reset the cache to an empty state and clear the nonce DB.
54+
/// This should only be called when the cache is corrupted.
55+
fn reset_cache(&mut self, conn: &mut DBConn) {
56+
self.cache = LruCache::new(self.max_size);
57+
if let Err(e) = conn.execute("DELETE FROM nonces", []) {
58+
warn!("error clearing nonces table: {e}");
4859
}
4960
}
5061

@@ -67,9 +78,14 @@ impl NonceCache {
6778
C: ClarityConnection,
6879
{
6980
// Check in-memory cache
70-
if let Some(cached_nonce) = self.cache.get(address) {
71-
return cached_nonce;
72-
};
81+
match self.cache.get(address) {
82+
Ok(Some(nonce)) => return nonce,
83+
Ok(None) => {}
84+
Err(_) => {
85+
// The cache is corrupt, reset it
86+
self.reset_cache(mempool_db);
87+
}
88+
}
7389

7490
// Check sqlite cache
7591
let db_nonce_opt = db_get_nonce(mempool_db, address).unwrap_or_else(|e| {
@@ -79,7 +95,14 @@ impl NonceCache {
7995
if let Some(db_nonce) = db_nonce_opt {
8096
// Insert into in-memory cache, but it is not dirty,
8197
// since we just got it from the database.
82-
let evicted = self.cache.insert_clean(address.clone(), db_nonce);
98+
let evicted = match self.cache.insert_clean(address.clone(), db_nonce) {
99+
Ok(evicted) => evicted,
100+
Err(_) => {
101+
// The cache is corrupt, reset it
102+
self.reset_cache(mempool_db);
103+
None
104+
}
105+
};
83106
if evicted.is_some() {
84107
// If we evicted something, we need to flush the cache.
85108
self.flush_with_evicted(mempool_db, evicted);
@@ -97,7 +120,14 @@ impl NonceCache {
97120
/// Set the nonce for `address` to `value` in the in-memory cache.
98121
/// If this causes an eviction, flush the in-memory cache to the DB.
99122
pub fn set(&mut self, address: StacksAddress, value: u64, conn: &mut DBConn) {
100-
let evicted = self.cache.insert(address.clone(), value);
123+
let evicted = match self.cache.insert(address.clone(), value) {
124+
Ok(evicted) => evicted,
125+
Err(_) => {
126+
// The cache is corrupt, reset it
127+
self.reset_cache(conn);
128+
Some((address, value))
129+
}
130+
};
101131
if evicted.is_some() {
102132
// If we evicted something, we need to flush the cache.
103133
self.flush_with_evicted(conn, evicted);
@@ -147,10 +177,18 @@ impl NonceCache {
147177
tx.execute(sql, params![addr, nonce])?;
148178
}
149179

150-
self.cache.flush(|addr, nonce| {
180+
match self.cache.flush(|addr, nonce| {
151181
tx.execute(sql, params![addr, nonce])?;
152182
Ok::<(), db_error>(())
153-
})?;
183+
}) {
184+
Ok(inner) => inner?,
185+
Err(_) => {
186+
drop(tx);
187+
// The cache is corrupt, reset it and return
188+
self.reset_cache(conn);
189+
return Ok(());
190+
}
191+
};
154192

155193
tx.commit()?;
156194

0 commit comments

Comments
 (0)