brokerd: recover a torn line in place, real dates only, bounded ttl, EMFILE
From the independent review of task 23. A torn last line followed by an empty later file had its recovery written into the later file, which broke the chain for good; the line is now ended in its own file. The log-name rule takes months 01 to 12 and days 01 to 31 only. [approvals] ttl_ms is limited to a day, the longest loopd waits after a pending frame. Running out of file descriptors or memory pauses the listener instead of stopping brokerd (the errors the previous fix skipped do not occur on Linux). args.rs's doc fixed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -104,3 +104,31 @@ fn the_lock_file_outlives_the_writer() {
|
||||
assert!(dir.path.join(".lock").exists());
|
||||
Writer::open(&dir.path, false).expect("the lock was released with the writer");
|
||||
}
|
||||
|
||||
/// A torn last line in one file, then an empty later file (created, never written). The torn line
|
||||
/// is ended in its own file and the recovery goes on the chain after it, so the log verifies and
|
||||
/// the next start is an ordinary one. Found by the independent review of task 23.
|
||||
#[test]
|
||||
fn a_torn_line_before_an_empty_later_file_is_recovered_in_place() {
|
||||
let dir = TempDir::unmade("torn-then-empty");
|
||||
let mut writer = Writer::open(&dir.path, false).unwrap().writer;
|
||||
writer
|
||||
.append(ts("2026-09-17T08:00:00.000Z"), denied(1))
|
||||
.unwrap();
|
||||
drop(writer);
|
||||
let mut d1 = std::fs::read(dir.path.join(D1)).unwrap();
|
||||
d1.extend_from_slice(br#"{"seq":1,"time":"2026-09-17T09"#);
|
||||
std::fs::write(dir.path.join(D1), &d1).unwrap();
|
||||
std::fs::write(dir.path.join(D2), b"").unwrap();
|
||||
|
||||
let opened = Writer::open(&dir.path, false).unwrap();
|
||||
assert!(opened.recovered);
|
||||
drop(opened);
|
||||
let report = verify_dir(&dir.path).unwrap();
|
||||
assert_eq!(report.failure, None, "{:?}", report.failure);
|
||||
assert_eq!(report.recoveries.len(), 1);
|
||||
assert!(std::fs::read(dir.path.join(D1)).unwrap().ends_with(b"\n"));
|
||||
|
||||
let again = Writer::open(&dir.path, false).expect("the next start is an ordinary one");
|
||||
assert!(!again.recovered);
|
||||
}
|
||||
|
||||
@@ -177,3 +177,27 @@ fn the_entries_exist() {
|
||||
assert!(runbook.lines().any(|l| l == entry), "{entry}");
|
||||
}
|
||||
}
|
||||
|
||||
/// `[approvals] ttl_ms` is between 1 ms and a day: `loopd` waits at most a day after a pending
|
||||
/// frame, so a longer approval would be abandoned while `bxctl` still listed it.
|
||||
#[test]
|
||||
fn an_approval_ttl_outside_a_day_is_a_config_error() {
|
||||
for ttl in ["0", "86400001", "18446744073709551615"] {
|
||||
let dir = TempDir::new("ptr-ttl");
|
||||
std::fs::create_dir_all(dir.path().join("grants")).unwrap();
|
||||
let path = dir.write(
|
||||
"brokerd.toml",
|
||||
&format!(
|
||||
"[paths]\nhome = \"{0}\"\ngrants = \"{0}/grants\"\n[approvals]\nttl_ms = {ttl}\n",
|
||||
dir.path().display()
|
||||
),
|
||||
);
|
||||
let out = brokerd([
|
||||
OsStr::new("serve"),
|
||||
OsStr::new("--config"),
|
||||
path.as_os_str(),
|
||||
]);
|
||||
fails_with_pointer(&out);
|
||||
assert!(stderr(&out).contains("ttl_ms"), "{ttl}: {}", stderr(&out));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user