From e45d705105a63f2fd9b35ab9e37ffbc640be8231 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Wed, 9 Sep 2026 07:33:51 +0200 Subject: [PATCH] fix(mcp): free the extraction bitset cache on the in-process auto-index thread (LSan) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cbm_kind_in_set lazily builds a THREAD-LOCAL node-type bitset cache during extraction. That cache must be freed before a worker thread exits, or the calloc'd bitsets are orphaned when the thread's TLS is torn down and LeakSanitizer reports them at process exit. Parallel workers already free it (pass_parallel.c:993) and the main thread frees it at process teardown; the IN-PROCESS sequential auto-index runs extraction on autoindex_thread — a short-lived pthread joined by cbm_mcp_server_free — and was the one gap: it built the cache but never freed it (360 bytes in 9 allocations leaked, all from ks_build). This is a PRE-EXISTING latent leak: the extraction path and autoindex_thread are byte-identical to main. It is newly EXPOSED by #713's new autoindex_limit_admits_non_git_root_under_limit test, the first LSan-observed path that runs an in-process auto-index and joins the worker thread in-process — so the fix ships with the test that exposes it. Free the calling thread's bitset cache after cbm_pipeline_free(p), mirroring the established per-worker-thread teardown. Zero behavior change to the CLI/main path. macOS LSan lane, mcp suite: 360 B / 9 allocations -> 0. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Martin Vogel --- src/mcp/mcp.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/mcp/mcp.c b/src/mcp/mcp.c index 87df93e60..07ea0b698 100644 --- a/src/mcp/mcp.c +++ b/src/mcp/mcp.c @@ -17383,6 +17383,16 @@ static void register_watcher_if_enabled(cbm_mcp_server_t *srv) { } /* Background auto-index thread function */ +/* Extraction builds a THREAD-LOCAL node-type bitset cache (cbm_kind_in_set). + * Every worker thread that runs extraction must free that cache before it exits, + * or the calloc'd bitsets are orphaned when the thread's TLS is torn down and + * LeakSanitizer reports them at process exit. Parallel workers do this in + * pass_parallel.c; the in-process (sequential) auto-index runs extraction on + * THIS short-lived thread, so it must free its own cache too. Declared extern + * (not via internal/cbm/helpers.h) to avoid pulling the extraction layer's + * header into the MCP TU — the same pattern test_main.c uses for teardown. */ +extern void cbm_kind_in_set_free_cache(void); + static void *autoindex_thread(void *arg) { cbm_mcp_server_t *srv = (cbm_mcp_server_t *)arg; @@ -17422,6 +17432,7 @@ static void *autoindex_thread(void *arg) { cbm_pipeline_unlock(); cbm_pipeline_free(p); + cbm_kind_in_set_free_cache(); /* free THIS thread's extraction bitset cache (see above) */ cbm_mem_collect(); /* return mimalloc pages to OS after indexing (in-process only) */ if (rc == 0) {