perf(litebe): take a read lock for wallet serialization in do_save

Saving the wallet serializes it to a temporary file under the sync
lock, but it took a write guard on the wallet to do so. Serialization
only reads wallet state, so the write guard needlessly excluded other
readers for the whole duration of the write, buffering, and flush.

- The save path now takes a read guard to serialize. A read guard
  still excludes writers, so nothing can mutate the wallet mid-write;
  it only lets other readers proceed in parallel. The surrounding
  sync lock still serializes saves against syncs, and the atomic
  temp-file-then-rename sequence is unchanged.

The serialized bytes are identical, verified against the differential
harness. The wallet file format is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 08:50:12 -05:00
co-authored by Claude Opus 4.8
parent 0bdea68337
commit b49f85bcbe
@@ -908,7 +908,7 @@ impl LightClient {
// Write to a temporary file first (atomic save). C254/s1-D9: a fresh owner-only file (0600 on unix); a stale // Write to a temporary file first (atomic save). C254/s1-D9: a fresh owner-only file (0600 on unix); a stale
// .dat.tmp is unlinked first, never reused. The rename carries the mode to the wallet. // .dat.tmp is unlinked first, never reused. The rename carries the mode to the wallet.
let wallet = self.wallet.write().unwrap(); let wallet = self.wallet.read().unwrap();
let mut file_buffer = BufWriter::with_capacity( let mut file_buffer = BufWriter::with_capacity(
1_000_000, // 1 MB write buffer 1_000_000, // 1 MB write buffer
create_private_file_new(&tmp_path).map_err(|e| format!("Failed to create temp file: {}", e))?); create_private_file_new(&tmp_path).map_err(|e| format!("Failed to create temp file: {}", e))?);