Guard file generation increments against u64 overflow

Fixes #1128

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-22 20:39:39 +02:00
co-authored by Claude Haiku 5.5
parent 9ec80bf2c2
commit 82779ec0f2
2 changed files with 36 additions and 2 deletions
+9 -2
View File
@@ -1167,7 +1167,10 @@ impl Store {
Log { generation, file: f, pending: Vec::new(), entries: kept, bytes, dirty: kept > 0 }
}
// A new snapshot and log of the next generation, so appends start clean.
_ => self.rewrite(file, generation + 1, &server).map_err(|e| e.why)?,
_ => {
let next = generation.checked_add(1).ok_or("the file's generation can't go any higher")?;
self.rewrite(file, next, &server).map_err(|e| e.why)?
}
};
self.entry(file)?.log = Some(log);
Ok(server)
@@ -1243,7 +1246,11 @@ impl Store {
fn compact(&mut self, file: &str, server: &Server) {
self.compactions += 1;
let Some(generation) = self.files.get(file).and_then(|e| e.log.as_ref()).map(|l| l.generation) else { return };
match self.rewrite(file, generation + 1, server) {
let Some(next) = generation.checked_add(1) else {
log::error!("file {file}: compaction failed: the file's generation can't go any higher");
return;
};
match self.rewrite(file, next, server) {
Ok(log) => {
if let Ok(e) = self.entry(file) {
e.log = Some(log);
+27
View File
@@ -313,3 +313,30 @@ fn edits_confirmed_after_a_compaction_cut_short_survive_a_restart() {
let name = page_name(&restored);
assert!(at(&name) >= at(&confirmed), "restored {name:?}, confirmed {confirmed:?}");
}
#[test]
fn a_maxed_out_generation_does_not_wrap() {
let dir = TempDir::new("maxgen");
let c = StoreConfig { compact_after: 3, ..cfg(dir.path()) };
let mut store = Store::open(dir.path(), c.clone()).unwrap();
let (id, _, _) = publish(&mut store, "Max");
let path = dir.path().join(FILES).join(&id);
let (_, state) = disk::decode_snapshot(&fs::read(path.join(SNAPSHOT)).unwrap()).unwrap();
// Loading: no log goes with the snapshot, so the next generation would overflow.
fs::write(path.join(SNAPSHOT), disk::encode_snapshot(u64::MAX, &state).unwrap()).unwrap();
fs::remove_file(path.join(LOG)).unwrap();
assert!(store.open(&id).is_err());
// Compacting: the open log is at the largest generation.
fs::write(path.join(SNAPSHOT), disk::encode_snapshot(u64::MAX - 1, &state).unwrap()).unwrap();
let _ = fs::remove_file(path.join(LOG));
let mut server = store.open(&id).unwrap();
let mut a = join(&mut server, Access::Edit);
store.changed(&id, &mut server);
for i in 0..4 {
rename(&mut server, &mut a, &format!("P{i}"));
store.changed(&id, &mut server);
store.flush();
}
let snap = disk::check_snapshot(&fs::read(path.join(SNAPSHOT)).unwrap()).unwrap();
assert_eq!(snap, u64::MAX);
}