mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
TSan failed on all three platforms in PR CI. Both races are in code this branch
added, and both were invisible to the local ladder because it has no TSan leg —
that lane is CI-only, so "macOS and Linux green" said nothing about either.
1. system_info.c: the cached system info
cbm_system_info() published its cache with a plain flag:
if (!info_cached) { cached_info = detect_...(); info_cached = 1; }
Fine while only the main thread asked. The memory-relief work started calling
cbm_mem_system_under_pressure() from the per-file extract loop, so several
workers now race on the flag AND on the struct — and a struct assignment is
not atomic, so a reader could see half of one. Reported at
system_info.c:284 against :274, from extract_worker, on macOS, ubuntu-arm and
ubuntu-latest alike.
Now a three-state atomic: whichever thread wins the compare-exchange is the
only writer of cached_info, a loser returns the copy it detected itself (the
same answer — detection only reads the OS), and later readers see the
published struct through the acquire/release pair. No mutex, so nothing has
to be initialised before first use.
2. pass_parallel.c: the spill pointer
pp_spill_active() tested ec->pctx->spill BEFORE its atomic spill_mode gate,
so it read an 8-byte pointer with no synchronisation while pp_spill_enter was
publishing that pointer under spill_mu. pp_spill_enter assigns the pointer
and only then release-stores spill_mode, so gating on the atomic first makes
the pointer read ordered by that release. Short-circuit order is load bearing
in that expression now, and the comment says so.
Found by running the TSan lane locally with halt_on_error=0 rather than CI's
halt_on_error=1: the first setting stops at the first race, so each fix would
have revealed the next one a CI round trip later. Enumerating instead showed
the second race was the last one in that suite.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>