Skip to content

Commit 852d8e4

Browse files
committed
Use quick_cache on ValueCache and introduce ParkingCache
1 parent b9dee2a commit 852d8e4

7 files changed

Lines changed: 189 additions & 139 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

linera-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ linera-storage.workspace = true
6767
linera-version.workspace = true
6868
linera-views.workspace = true
6969
lru.workspace = true
70+
quick_cache.workspace = true
7071
papaya = { workspace = true, features = ["serde"] }
7172
prometheus = { workspace = true, optional = true }
7273
proptest = { workspace = true, optional = true }

linera-core/src/chain_worker/actor.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use super::{config::ChainWorkerConfig, state::ChainWorkerState, DeliveryNotifier
3636
use crate::{
3737
chain_worker::BlockOutcome,
3838
data_types::{ChainInfoQuery, ChainInfoResponse},
39-
value_cache::ValueCache,
39+
value_cache::{ParkingCache, ValueCache},
4040
worker::{NetworkActions, WorkerError},
4141
};
4242

@@ -275,7 +275,7 @@ where
275275
config: ChainWorkerConfig,
276276
storage: StorageClient,
277277
block_values: Arc<ValueCache<CryptoHash, Hashed<Block>>>,
278-
execution_state_cache: Arc<ValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>,
278+
execution_state_cache: Arc<ParkingCache<CryptoHash, ExecutionStateView<InactiveContext>>>,
279279
tracked_chains: Option<Arc<sync::RwLock<HashSet<ChainId>>>>,
280280
delivery_notifier: DeliveryNotifier,
281281
is_tracked: bool,
@@ -328,7 +328,7 @@ where
328328
config: ChainWorkerConfig,
329329
storage: StorageClient,
330330
block_values: Arc<ValueCache<CryptoHash, Hashed<Block>>>,
331-
execution_state_cache: Arc<ValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>,
331+
execution_state_cache: Arc<ParkingCache<CryptoHash, ExecutionStateView<InactiveContext>>>,
332332
tracked_chains: Option<Arc<RwLock<HashSet<ChainId>>>>,
333333
delivery_notifier: DeliveryNotifier,
334334
chain_id: ChainId,

linera-core/src/chain_worker/state.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use tracing::{debug, instrument, trace, warn};
4646
use super::{ChainWorkerConfig, ChainWorkerRequest, DeliveryNotifier, EventSubscriptionsResult};
4747
use crate::{
4848
data_types::{ChainInfo, ChainInfoQuery, ChainInfoResponse, CrossChainRequest},
49-
value_cache::ValueCache,
49+
value_cache::{ParkingCache, ValueCache},
5050
worker::{NetworkActions, Notification, Reason, WorkerError},
5151
};
5252

@@ -77,7 +77,7 @@ where
7777
shared_chain_view: Option<Arc<RwLock<ChainStateView<StorageClient::Context>>>>,
7878
service_runtime_endpoint: Option<ServiceRuntimeEndpoint>,
7979
block_values: Arc<ValueCache<CryptoHash, Hashed<Block>>>,
80-
execution_state_cache: Arc<ValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>,
80+
execution_state_cache: Arc<ParkingCache<CryptoHash, ExecutionStateView<InactiveContext>>>,
8181
tracked_chains: Option<Arc<sync::RwLock<HashSet<ChainId>>>>,
8282
delivery_notifier: DeliveryNotifier,
8383
knows_chain_is_active: bool,
@@ -103,7 +103,7 @@ where
103103
config: ChainWorkerConfig,
104104
storage: StorageClient,
105105
block_values: Arc<ValueCache<CryptoHash, Hashed<Block>>>,
106-
execution_state_cache: Arc<ValueCache<CryptoHash, ExecutionStateView<InactiveContext>>>,
106+
execution_state_cache: Arc<ParkingCache<CryptoHash, ExecutionStateView<InactiveContext>>>,
107107
tracked_chains: Option<Arc<sync::RwLock<HashSet<ChainId>>>>,
108108
delivery_notifier: DeliveryNotifier,
109109
chain_id: ChainId,
@@ -1458,7 +1458,7 @@ where
14581458
.await
14591459
.with_execution_context(ChainExecutionContext::Query)?;
14601460
self.execution_state_cache
1461-
.insert_owned(&requested_block, state);
1461+
.insert(&requested_block, state);
14621462
Ok(outcome)
14631463
} else {
14641464
tracing::debug!(requested_block = %requested_block, "requested block hash not found in cache, querying committed state");
@@ -1774,7 +1774,7 @@ where
17741774
.await?;
17751775
let block = Block::new(block.clone(), outcome);
17761776
let block_hash = CryptoHash::new(&block);
1777-
self.execution_state_cache.insert_owned(
1777+
self.execution_state_cache.insert(
17781778
&block_hash,
17791779
Box::pin(
17801780
self.chain

linera-core/src/unit_tests/value_cache_tests.rs

Lines changed: 91 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Zefchain Labs, Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::{borrow::Cow, collections::BTreeSet};
4+
use std::borrow::Cow;
55

66
use linera_base::{
77
crypto::CryptoHash,
@@ -23,7 +23,7 @@ fn test_retrieve_missing_value() {
2323
let hash = CryptoHash::test_hash("Missing value");
2424

2525
assert!(cache.get(&hash).is_none());
26-
assert!(cache.keys::<Vec<_>>().is_empty());
26+
assert_eq!(cache.len(), 0);
2727
}
2828

2929
/// Tests inserting a certificate value in the cache.
@@ -36,7 +36,7 @@ fn test_insert_single_certificate_value() {
3636
assert!(cache.insert(Cow::Borrowed(&value)));
3737
assert!(cache.contains(&hash));
3838
assert_eq!(cache.get(&hash), Some(value));
39-
assert_eq!(cache.keys::<BTreeSet<_>>(), BTreeSet::from([hash]));
39+
assert_eq!(cache.len(), 1);
4040
}
4141

4242
/// Tests inserting many certificate values in the cache, one-by-one.
@@ -54,10 +54,7 @@ fn test_insert_many_certificate_values_individually() {
5454
assert_eq!(cache.get(&value.hash()).as_ref(), Some(value));
5555
}
5656

57-
assert_eq!(
58-
cache.keys::<BTreeSet<_>>(),
59-
BTreeSet::from_iter(values.iter().map(Hashed::hash))
60-
);
57+
assert_eq!(cache.len(), TEST_CACHE_SIZE);
6158
}
6259

6360
/// Tests inserting many values in the cache, all-at-once.
@@ -73,10 +70,7 @@ fn test_insert_many_values_together() {
7370
assert_eq!(cache.get(&value.hash()).as_ref(), Some(value));
7471
}
7572

76-
assert_eq!(
77-
cache.keys::<BTreeSet<_>>(),
78-
BTreeSet::from_iter(values.iter().map(|el| el.hash()))
79-
);
73+
assert_eq!(cache.len(), TEST_CACHE_SIZE);
8074
}
8175

8276
/// Tests re-inserting many values in the cache, all-at-once.
@@ -96,97 +90,126 @@ fn test_reinsertion_of_values() {
9690
assert_eq!(cache.get(&value.hash()).as_ref(), Some(value));
9791
}
9892

99-
assert_eq!(
100-
cache.keys::<BTreeSet<_>>(),
101-
BTreeSet::from_iter(values.iter().map(Hashed::hash))
93+
// Re-insertion should not increase the count
94+
assert_eq!(cache.len(), TEST_CACHE_SIZE);
95+
}
96+
97+
/// Tests eviction when cache is full.
98+
/// Note: quick_cache uses S3-FIFO eviction, so we verify that the cache
99+
/// doesn't grow unboundedly and eviction occurs.
100+
#[test]
101+
fn test_eviction_occurs() {
102+
let cache = ValueCache::<CryptoHash, Hashed<Timeout>>::new(TEST_CACHE_SIZE);
103+
// Insert more than capacity
104+
let values =
105+
create_dummy_certificate_values(0..((TEST_CACHE_SIZE as u64) * 2)).collect::<Vec<_>>();
106+
107+
for value in &values {
108+
cache.insert(Cow::Borrowed(value));
109+
}
110+
111+
// Cache size should be bounded by capacity
112+
assert!(
113+
cache.len() <= TEST_CACHE_SIZE,
114+
"Cache size {} exceeds capacity {}",
115+
cache.len(),
116+
TEST_CACHE_SIZE
117+
);
118+
119+
// Count how many values are still in cache
120+
let present_count = values.iter().filter(|v| cache.contains(&v.hash())).count();
121+
122+
// At least some values should have been evicted
123+
assert!(
124+
present_count < values.len(),
125+
"Cache should have evicted at least some entries"
102126
);
103127
}
104128

105-
/// Tests eviction of one entry.
129+
/// Tests eviction when inserting one more than capacity.
130+
/// With S3-FIFO, the first inserted item may or may not be evicted (depends on access patterns).
106131
#[test]
107-
fn test_one_eviction() {
132+
fn test_one_over_capacity() {
108133
let cache = ValueCache::<CryptoHash, Hashed<Timeout>>::new(TEST_CACHE_SIZE);
109134
let values = create_dummy_certificate_values(0..=(TEST_CACHE_SIZE as u64)).collect::<Vec<_>>();
110135

111136
cache.insert_all(values.iter().map(Cow::Borrowed));
112137

113-
assert!(!cache.contains(&values[0].hash()));
114-
assert!(cache.get(&values[0].hash()).is_none());
115-
116-
for value in values.iter().skip(1) {
117-
assert!(cache.contains(&value.hash()));
118-
assert_eq!(cache.get(&value.hash()).as_ref(), Some(value));
119-
}
138+
// Cache should not exceed capacity
139+
assert!(
140+
cache.len() <= TEST_CACHE_SIZE,
141+
"Cache size {} exceeds capacity {}",
142+
cache.len(),
143+
TEST_CACHE_SIZE
144+
);
120145

146+
// Exactly one value should have been evicted
147+
let present_count = values.iter().filter(|v| cache.contains(&v.hash())).count();
121148
assert_eq!(
122-
cache.keys::<BTreeSet<_>>(),
123-
BTreeSet::from_iter(values.iter().skip(1).map(Hashed::hash))
149+
present_count, TEST_CACHE_SIZE,
150+
"Expected {} items in cache after inserting {} items with capacity {}",
151+
TEST_CACHE_SIZE,
152+
values.len(),
153+
TEST_CACHE_SIZE
124154
);
125155
}
126156

127-
/// Tests eviction of the second entry.
157+
/// Tests that accessing a value affects eviction (values accessed recently are more likely to stay).
158+
/// S3-FIFO uses frequency-based eviction, so frequently accessed items should survive.
128159
#[test]
129-
fn test_eviction_of_second_entry() {
160+
fn test_access_affects_eviction() {
130161
let cache = ValueCache::<CryptoHash, Hashed<Timeout>>::new(TEST_CACHE_SIZE);
131-
let values = create_dummy_certificate_values(0..=(TEST_CACHE_SIZE as u64)).collect::<Vec<_>>();
162+
let values = create_dummy_certificate_values(0..(TEST_CACHE_SIZE as u64)).collect::<Vec<_>>();
132163

133-
cache.insert_all(values.iter().take(TEST_CACHE_SIZE).map(Cow::Borrowed));
134-
cache.get(&values[0].hash());
135-
assert!(cache.insert(Cow::Borrowed(&values[TEST_CACHE_SIZE])));
164+
// Fill the cache
165+
cache.insert_all(values.iter().map(Cow::Borrowed));
136166

137-
assert!(cache.contains(&values[0].hash()));
138-
assert_eq!(cache.get(&values[0].hash()).as_ref(), Some(&values[0]));
167+
// Access the first value multiple times to make it "hot"
168+
for _ in 0..5 {
169+
cache.get(&values[0].hash());
170+
}
139171

140-
assert!(!cache.contains(&values[1].hash()));
141-
assert!(cache.get(&values[1].hash()).is_none());
172+
// Insert additional values to trigger eviction
173+
let extra_values =
174+
create_dummy_certificate_values((TEST_CACHE_SIZE as u64)..((TEST_CACHE_SIZE as u64) + 5))
175+
.collect::<Vec<_>>();
142176

143-
for value in values.iter().skip(2) {
144-
assert!(cache.contains(&value.hash()));
145-
assert_eq!(cache.get(&value.hash()).as_ref(), Some(value));
177+
for value in &extra_values {
178+
cache.insert(Cow::Borrowed(value));
146179
}
147180

148-
assert_eq!(
149-
cache.keys::<BTreeSet<_>>(),
150-
BTreeSet::from_iter(
151-
values
152-
.iter()
153-
.skip(2)
154-
.map(Hashed::hash)
155-
.chain(Some(values[0].hash()))
156-
)
181+
// The frequently accessed first value should still be present
182+
assert!(
183+
cache.contains(&values[0].hash()),
184+
"Frequently accessed value should survive eviction"
157185
);
158186
}
159187

160-
/// Tests if reinsertion of the first entry promotes it so that it's not evicted so soon.
188+
/// Tests that re-inserting a value promotes it in the eviction order.
161189
#[test]
162190
fn test_promotion_of_reinsertion() {
163191
let cache = ValueCache::<CryptoHash, Hashed<Timeout>>::new(TEST_CACHE_SIZE);
164-
let values = create_dummy_certificate_values(0..=(TEST_CACHE_SIZE as u64)).collect::<Vec<_>>();
192+
let values = create_dummy_certificate_values(0..(TEST_CACHE_SIZE as u64)).collect::<Vec<_>>();
165193

166-
cache.insert_all(values.iter().take(TEST_CACHE_SIZE).map(Cow::Borrowed));
167-
assert!(!cache.insert(Cow::Borrowed(&values[0])));
168-
assert!(cache.insert(Cow::Borrowed(&values[TEST_CACHE_SIZE])));
194+
// Fill the cache
195+
cache.insert_all(values.iter().map(Cow::Borrowed));
169196

170-
assert!(cache.contains(&values[0].hash()));
171-
assert_eq!(cache.get(&values[0].hash()).as_ref(), Some(&values[0]));
197+
// Re-insert the first value (this should "promote" it)
198+
assert!(!cache.insert(Cow::Borrowed(&values[0])));
172199

173-
assert!(!cache.contains(&values[1].hash()));
174-
assert!(cache.get(&values[1].hash()).is_none());
200+
// Insert additional values to trigger eviction
201+
let extra_values =
202+
create_dummy_certificate_values((TEST_CACHE_SIZE as u64)..((TEST_CACHE_SIZE as u64) + 3))
203+
.collect::<Vec<_>>();
175204

176-
for value in values.iter().skip(2) {
177-
assert!(cache.contains(&value.hash()));
178-
assert_eq!(cache.get(&value.hash()).as_ref(), Some(value));
205+
for value in &extra_values {
206+
cache.insert(Cow::Borrowed(value));
179207
}
180208

181-
assert_eq!(
182-
cache.keys::<BTreeSet<_>>(),
183-
BTreeSet::from_iter(
184-
values
185-
.iter()
186-
.skip(2)
187-
.map(Hashed::hash)
188-
.chain(Some(values[0].hash()))
189-
)
209+
// The re-inserted first value should still be present
210+
assert!(
211+
cache.contains(&values[0].hash()),
212+
"Re-inserted value should survive eviction"
190213
);
191214
}
192215

0 commit comments

Comments
 (0)