mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(mem): free the thread locale at exit; a settling gate keeps its latch
Two CI-only lanes went red on 9724d903, both this branch's own doing.
The macOS leak lane: every test passed and LeakSanitizer reported 16 to
101 objects of 1,472 bytes per test process, allocated from
cbm_arena_sprintf. The formatter creates one C locale per thread on macOS
(the vsnprintf locale lock fix) and kept it in a thread-local pointer
only, so every worker thread that ever formatted a name leaked its locale
at exit. A pthread key destructor now frees it when the thread exits; the
main thread keeps its locale until process exit.
The TSan lane on Ubuntu arm: pipeline_backpressure_futile_nap_disengages
counted 8 nap cycles against a bound of 7, the "gate re-paid per pull"
shape the test exists for. The spill shortcut reports "not over" while a
sweep is still bringing memory down, and that reading re-armed the
futility latch, so the next over-budget pull paid a full nap cycle again.
The latch now re-arms only on a genuine under-budget reading; a reading
the shortcut produced is not one.
Verification: the local macOS leak lane (make -f Makefile.cbm test-lsan)
reports zero leaks, 8050 passed / 10 skipped (the CI lane had 16-101
leaked objects per process); the local TSan lane (scripts/test.sh --tsan)
passes 1157 / 9 skipped with the nap-cycle test green (the CI lane had it
at 8 cycles); pipeline, parallel and arena suites twice and lint-ci on the
fixed tree.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
9724d90304
commit
9e69cb7d9b
2 files changed
+30
-1
No files matched your search
@@ -18,6 +18,7 @@ enum { ARENA_ALIGN = 7, ARENA_GROW_OK = 1 };
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#if defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
#include <xlocale.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
@@ -144,10 +145,30 @@ char *cbm_arena_strndup(CBMArena *a, const char *s, size_t len) {
|
||||
* object of its own, so no thread ever waits for another, and the common
|
||||
* short string is formatted once into a stack buffer. */
|
||||
#if defined(__APPLE__)
|
||||
/* One locale object per thread, freed when the thread exits: the object is
|
||||
* heap memory that the thread-local pointer alone kept, so every worker
|
||||
* thread that ever formatted a name leaked 1,472 bytes at exit (the macOS
|
||||
* LSan lane on PR #2202, 16-101 objects per test process). A pthread key
|
||||
* destructor is the one hook that runs at thread exit for a TLS-held
|
||||
* resource; the main thread keeps its locale until process exit. */
|
||||
static _Thread_local locale_t tl_c_locale;
|
||||
static pthread_key_t tl_c_locale_key;
|
||||
static pthread_once_t tl_c_locale_once = PTHREAD_ONCE_INIT;
|
||||
static void arena_c_locale_free(void *loc) {
|
||||
if (loc) {
|
||||
freelocale((locale_t)loc);
|
||||
}
|
||||
}
|
||||
static void arena_c_locale_key_init(void) {
|
||||
(void)pthread_key_create(&tl_c_locale_key, arena_c_locale_free);
|
||||
}
|
||||
static locale_t arena_c_locale(void) {
|
||||
if (!tl_c_locale) {
|
||||
tl_c_locale = newlocale(LC_ALL_MASK, "C", NULL);
|
||||
if (tl_c_locale) {
|
||||
pthread_once(&tl_c_locale_once, arena_c_locale_key_init);
|
||||
(void)pthread_setspecific(tl_c_locale_key, tl_c_locale);
|
||||
}
|
||||
}
|
||||
return tl_c_locale; /* NULL = the global locale, the pre-fix behaviour */
|
||||
}
|
||||
|
||||
@@ -1020,6 +1020,7 @@ static void extract_worker(int worker_id, void *ctx_ptr) {
|
||||
pp_spill_enter(ec, "near_budget");
|
||||
}
|
||||
}
|
||||
bool settling = false;
|
||||
if (over) {
|
||||
/* Admission control, first response: park what can be parked.
|
||||
* Only what is still over budget after that -- the floor --
|
||||
@@ -1030,6 +1031,7 @@ static void extract_worker(int worker_id, void *ctx_ptr) {
|
||||
over = cbm_mem_over_budget();
|
||||
if (over && pp_spill_active(ec) && (parked > 0 || pp_spill_work_remains(ec))) {
|
||||
over = false; /* memory is still on its way down */
|
||||
settling = true;
|
||||
}
|
||||
}
|
||||
bool futile = atomic_load_explicit(&ec->bp_futile, memory_order_relaxed) != 0;
|
||||
@@ -1046,7 +1048,13 @@ static void extract_worker(int worker_id, void *ctx_ptr) {
|
||||
atomic_store_explicit(&ec->bp_futile, 0, memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
} else if (!over && futile) {
|
||||
} else if (!over && !settling && futile) {
|
||||
/* Re-arm only on a genuine under-budget reading. A reading the
|
||||
* spill shortcut produced ("still on its way down") is not
|
||||
* one: re-arming on it made the next over-budget pull pay a
|
||||
* full nap cycle again -- the gate re-paid per pull that
|
||||
* pipeline_backpressure_futile_nap_disengages guards against
|
||||
* (TSan lane on PR #2202: 8 cycles against a bound of 7). */
|
||||
atomic_store_explicit(&ec->bp_futile, 0, memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user