From d76d3b9061e1d8fe5e5097c463f377bfa86dd374 Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Tue, 10 Mar 2026 13:12:38 +0200 Subject: [PATCH] Use write_all for ledger entries to improve concurrent-write safety writeln!(f, ...) makes two syscalls (data + newline) which can interleave between concurrent processes even with O_APPEND. Serialise entry to bytes and append the newline before write_all() so the entire entry lands in a single write() syscall, which O_APPEND makes atomic on Linux local filesystems for typical entry sizes. Co-Authored-By: Claude Sonnet 4.6 --- src/agent.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 44d409a..7d37ca7 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -617,18 +617,22 @@ fn append_ledger_entry(ledger: &Path, result: &BacktestResult, strategy: &Value) .to_string(), strategy: strategy.clone(), }; - let line = match serde_json::to_string(&entry) { - Ok(s) => s, + // Append newline inside the serialised bytes so the entire write is a single + // write_all() syscall — O_APPEND + single write() is atomic on Linux local + // filesystems, making concurrent instances safe for typical entry sizes. + let mut bytes = match serde_json::to_vec(&entry) { + Ok(b) => b, Err(e) => { warn!("could not serialize ledger entry: {e}"); return; } }; + bytes.push(b'\n'); if let Err(e) = std::fs::OpenOptions::new() .append(true) .create(true) .open(ledger) - .and_then(|mut f| writeln!(f, "{}", line)) + .and_then(|mut f| f.write_all(&bytes)) { warn!("could not write ledger entry: {e}"); }