Skip to content

Commit c12bf49

Browse files
authored
Respect ModuleCache max size (#5441)
## Motivation `ModuleCache::insert()` never incremented `total_size` after inserting a new entry. It only decremented during eviction in `reduce_size_to()`. This meant: - `total_size` was permanently 0 - The eviction check `if self.total_size + bytecode_size > self.max_size` never triggered - The cache grew unboundedly despite the 512 MiB limit - `reduce_size_to()` would underflow on the subtraction `self.total_size -= bytecode_size` ## Proposal - Add `self.total_size += bytecode_size` after insertion, only when the key wasn't already present (to avoid double-counting on re-insertion of the same bytecode) - Add an early return when a single bytecode exceeds `max_size`, which prevents an underflow in the `self.max_size - bytecode_size` computation passed to `reduce_size_to()` - Add 4 unit tests covering: size tracking, eviction triggering, oversized rejection, and duplicate key handling ## Test Plan - CI + new tests pass ## Release Plan - These changes should be backported to the latest `testnet` branch, then - be released in a validator hotfix.
1 parent d461a29 commit c12bf49

File tree

5 files changed

+105
-33
lines changed

5 files changed

+105
-33
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ kube = "0.88.1"
152152
linera-kywasmtime = "0.1.0"
153153
linked-hash-map = "0.5.6"
154154
log = "0.4.21"
155-
lru = "0.12.3"
155+
lru = "0.15.0"
156156
mini-moka = "0.10.3"
157157
nonzero_lit = "0.1.2"
158158
num-bigint = "0.4.3"

examples/Cargo.lock

Lines changed: 8 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

linera-execution/src/wasm/module_cache.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ impl<Module> Default for ModuleCache<Module> {
3232
}
3333
}
3434

35+
impl<Module> ModuleCache<Module> {
36+
#[cfg(test)]
37+
fn with_max_size(max_size: u64) -> Self {
38+
ModuleCache {
39+
modules: LruCache::unbounded(),
40+
total_size: 0,
41+
max_size,
42+
}
43+
}
44+
}
45+
3546
impl<Module: Clone> ModuleCache<Module> {
3647
/// Returns a `Module` for the requested `bytecode`, creating it with `module_builder` and
3748
/// adding it to the cache if it doesn't already exist in the cache.
@@ -58,11 +69,20 @@ impl<Module: Clone> ModuleCache<Module> {
5869
pub fn insert(&mut self, bytecode: Bytecode, module: Module) {
5970
let bytecode_size = bytecode.as_ref().len() as u64;
6071

72+
if bytecode_size > self.max_size {
73+
return;
74+
}
75+
76+
if self.modules.promote(&bytecode) {
77+
return;
78+
}
79+
6180
if self.total_size + bytecode_size > self.max_size {
6281
self.reduce_size_to(self.max_size - bytecode_size);
6382
}
6483

6584
self.modules.put(bytecode, module);
85+
self.total_size += bytecode_size;
6686
}
6787

6888
/// Evicts entries from the cache so that the total size of cached bytecode files is less than
@@ -79,3 +99,71 @@ impl<Module: Clone> ModuleCache<Module> {
7999
}
80100
}
81101
}
102+
103+
#[cfg(test)]
104+
mod tests {
105+
use super::*;
106+
107+
fn bytecode(size: usize) -> Bytecode {
108+
Bytecode::new(vec![0u8; size])
109+
}
110+
111+
fn distinct_bytecode(size: usize, discriminant: u8) -> Bytecode {
112+
let mut bytes = vec![0u8; size];
113+
bytes[0] = discriminant;
114+
Bytecode::new(bytes)
115+
}
116+
117+
#[test]
118+
fn total_size_tracks_insertions() {
119+
let mut cache = ModuleCache::<u32>::with_max_size(1000);
120+
cache.insert(bytecode(100), 1);
121+
assert_eq!(cache.total_size, 100);
122+
cache.insert(distinct_bytecode(200, 1), 2);
123+
assert_eq!(cache.total_size, 300);
124+
}
125+
126+
#[test]
127+
fn eviction_triggers_when_full() {
128+
let mut cache = ModuleCache::<u32>::with_max_size(250);
129+
cache.insert(bytecode(100), 1);
130+
cache.insert(distinct_bytecode(100, 1), 2);
131+
assert_eq!(cache.total_size, 200);
132+
assert_eq!(cache.modules.len(), 2);
133+
134+
cache.insert(distinct_bytecode(100, 2), 3);
135+
assert_eq!(cache.modules.len(), 2);
136+
assert!(cache.total_size <= 250);
137+
}
138+
139+
#[test]
140+
fn oversized_bytecode_is_rejected() {
141+
let mut cache = ModuleCache::<u32>::with_max_size(50);
142+
cache.insert(bytecode(100), 1);
143+
assert_eq!(cache.total_size, 0);
144+
assert_eq!(cache.modules.len(), 0);
145+
}
146+
147+
#[test]
148+
fn reinserting_same_key_does_not_double_count() {
149+
let mut cache = ModuleCache::<u32>::with_max_size(1000);
150+
let bc = bytecode(100);
151+
cache.insert(bc.clone(), 1);
152+
assert_eq!(cache.total_size, 100);
153+
cache.insert(bc, 2);
154+
assert_eq!(cache.total_size, 100);
155+
assert_eq!(cache.modules.len(), 1);
156+
}
157+
158+
#[test]
159+
fn reinserting_existing_key_does_not_evict() {
160+
let mut cache = ModuleCache::<u32>::with_max_size(200);
161+
cache.insert(bytecode(100), 1);
162+
cache.insert(distinct_bytecode(100, 1), 2);
163+
assert_eq!(cache.modules.len(), 2);
164+
165+
cache.insert(bytecode(100), 3);
166+
assert_eq!(cache.modules.len(), 2);
167+
assert_eq!(cache.total_size, 200);
168+
}
169+
}

linera-sdk/tests/fixtures/Cargo.lock

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

0 commit comments

Comments
 (0)