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(),
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}");
}