mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-12 09:47:20 +08:00
Six reports describe the same dead end: the indexing worker dies, the supervisor points at `logs/.worker-<pid>.log`, and the file is 0 bytes. Nothing was ever flushed, so not one of #1070, #1130, #1132, #1133, #1145 or #1450 is reproducible or attributable -- the hint says "crashed on a file" and the file is never named. Root cause is buffering, not the crash. The worker's stderr is redirected to that log file by the supervisor, and every diagnostic goes through `fprintf(stderr, ...)` with no flush. The C standard only promises stderr is "not fully buffered"; the Windows CRT gives a redirected stderr FULL buffering, which is why five of the six reports are Windows. A worker that aborts, is SIGKILLed by the OOM killer, or is terminated after a hang takes its whole buffer with it. - cbm_log_set_crash_durable(): setvbuf(_IONBF) plus a per-line flush in emit_line. Both, deliberately -- setvbuf covers every writer to the stream including the plain fprintf startup errors that explain a worker which never got as far as logging, and the flush covers the case where setvbuf is refused because the stream was already written to. Enabled for the worker role only, claimed in main() before the process writes anything. - cbm_index_worker_log_begin(): a startup header written and flushed as the worker's first act -- version, build fingerprint, pid, repo path, and the worker's own arguments. A control record rather than an info line, so CBM_LOG_LEVEL cannot restore the 0-byte log. - Under a crash-durable log the parallel extract pass logs every file it starts, not just the first two rounds of workers, so the log ends with the files that were in flight when the worker died. One line per file, never per node; unchanged for every non-worker caller. This fixes no crash. It converts six unreproducible reports into reports we can act on, and every future one arrives with the run identified. Test: a worker started with a fully buffered stderr -- the state the Windows CRT hands it, forced on POSIX so the contract binds on all three legs -- writes diagnostics and is then SIGKILLed. The retained log must be non-empty, carry the header with the version, pid, repo path and args, and still hold the line written after it. Reverting the production change leaves that log at 0 bytes. The probe kills rather than aborts because Darwin's abort() runs the stdio cleanup handler and flushes the very buffer the repro depends on stranding: under abort the reverted build still produced a populated log and the test was quietly toothless. SIGKILL runs no cleanup, and is literally #1070's death (signal=9). Refs #1070 Refs #1130 Refs #1132 Refs #1133 Refs #1145 Refs #1450 Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>