From 8efa9ec6c3d123ea9af4f8deb54191d03ab3d01d Mon Sep 17 00:00:00 2001 From: monotophic Date: Sat, 18 Jul 2026 15:57:12 -0400 Subject: [PATCH] E5: hazard-audit round-2 fixes (re-register set hygiene, SUMMARY mutex ref) 1. DEFENSIVE (backend_metal.mm, coli_metal_register): re-registering a live base overwrote s.buf and resset_add'ed the new wrapper without removing the OLD one from the residency set -- ARC drops our reference but the set retains the object and keeps its pages resident: a leak and a set/g_slabs divergence. The found branch now stashes the replaced wrapper under g_slab_mtx and, after dropping the lock (round-1 invariant: no Metal call under the slab lock), resset_remove(old)s it before resset_add(b); identical old==b early-outs both set operations. Invariant defended, stated in the comment: residency-set membership mirrors g_slabs exactly. No in-tree caller re-registers a live base today (audited) -- closed defensively. 2. DOC (SUMMARY.md): the moe_submit lifecycle bullet still said resset_flush commits "under g_slab_mtx" -- stale text from before the round-1 mutex split; the code takes g_resset_mtx and never touches the slab lock there. Parenthetical corrected; register bullet updated to describe the re-register hygiene from item 1. --- SUMMARY.md | 8 ++++++-- c/backend_metal.mm | 11 +++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/SUMMARY.md b/SUMMARY.md index 599b8ca..d196eb1 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -50,7 +50,10 @@ the `COLI_METAL_RESSET` `getenv` branch in `coli_metal_init`, so `resset_add`/`r dirty flag), calls `[rs addAllocation:b]` and sets `g_resset_dirty` — **it does not commit**. No Metal call ever runs under `g_slab_mtx` (validator round-1 fix; E4's audit round 2 identified mutex-over-live-Metal-call as the leading suspect for its +12s - expert-disk regression). + expert-disk regression). Re-registering a live base (no in-tree caller does today) drops + the replaced wrapper from the set via `resset_remove(old)` before adding the new one + (hazard-audit defensive fix — the set would otherwise retain the old buffer, and its + pages' residency, forever), keeping set membership an exact mirror of `g_slabs`. - **`coli_metal_unregister`**: erases the `g_slabs` entry under `g_slab_mtx` (stashing the buffer), then calls `resset_remove(b)` **outside `g_slab_mtx`**, before returning. `resset_remove` (under `g_resset_mtx`) calls `[rs removeAllocation:b]` **and commits @@ -58,7 +61,8 @@ the `COLI_METAL_RESSET` `getenv` branch in `coli_metal_init`, so `resset_add`/`r function returns. See UNCERTAINTIES for why this asymmetry is deliberate. - **`moe_submit`** (the one function whose `use` list — resolved expert weight/scale slabs — scales with LRU cache size): calls `resset_flush()` at the top (commits any pending adds - from `resset_add`, under `g_slab_mtx`), then, if `g_resset_enabled`, **skips** the + from `resset_add`, under `g_resset_mtx` — it never touches the slab lock), then, if + `g_resset_enabled`, **skips** the `for(auto&b:use) [e useResource:b usage:MTLResourceUsageRead];` loop entirely — residency is already guaranteed by the queue-attached set. Every other `useResource:` call site in the file (`bind_gemv`'s weight/scale buffers, `coli_metal_attn_decode`/`coli_metal_layer_decode`'s diff --git a/c/backend_metal.mm b/c/backend_metal.mm index be7dae8..6a8e3a0 100644 --- a/c/backend_metal.mm +++ b/c/backend_metal.mm @@ -399,13 +399,20 @@ extern "C" void coli_metal_register(void *base, size_t len) { id b = [g_dev newBufferWithBytesNoCopy:base length:len options:g_res_opts deallocator:nil]; if (!b) return; + id old = nil; // E5: replaced wrapper on re-register of a live base (defensive) { std::lock_guard lk(g_slab_mtx); // called from parallel expert_load threads bool found = false; - for (auto &s : g_slabs) if (s.base == base) { s.len = len; s.buf = b; found = true; break; } + for (auto &s : g_slabs) if (s.base == base) { old = s.buf; s.len = len; s.buf = b; found = true; break; } if (!found) g_slabs.push_back({base, len, b}); } - resset_add(b); // E5: outside g_slab_mtx (no Metal call under the slab lock); still before return + // E5, outside g_slab_mtx (no Metal call under the slab lock), before returning. Invariant + // defended: set membership mirrors g_slabs exactly -- a re-register of a live base must + // drop the replaced wrapper from the set (ARC releases our reference, but the set retains + // it and keeps its pages resident forever) before adding the new one. No in-tree caller + // re-registers a live base today; defensive. + if (old && old != b) resset_remove(old); + if (old != b) resset_add(b); } extern "C" void coli_metal_unregister(void *base) { id b = nil;