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 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 13:12:38 +02:00
parent 0945c94cc8
commit d76d3b9061

View File

@@ -617,18 +617,22 @@ fn append_ledger_entry(ledger: &Path, result: &BacktestResult, strategy: &Value)
.to_string(), .to_string(),
strategy: strategy.clone(), strategy: strategy.clone(),
}; };
let line = match serde_json::to_string(&entry) { // Append newline inside the serialised bytes so the entire write is a single
Ok(s) => s, // 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) => { Err(e) => {
warn!("could not serialize ledger entry: {e}"); warn!("could not serialize ledger entry: {e}");
return; return;
} }
}; };
bytes.push(b'\n');
if let Err(e) = std::fs::OpenOptions::new() if let Err(e) = std::fs::OpenOptions::new()
.append(true) .append(true)
.create(true) .create(true)
.open(ledger) .open(ledger)
.and_then(|mut f| writeln!(f, "{}", line)) .and_then(|mut f| f.write_all(&bytes))
{ {
warn!("could not write ledger entry: {e}"); warn!("could not write ledger entry: {e}");
} }