From a72b3daf0a12c2bc09fb8e7623f49d2f74a2cfd6 Mon Sep 17 00:00:00 2001 From: junyao <1071307515@qq.com> Date: Fri, 30 Oct 2026 16:43:27 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E5=88=86=E9=85=8D=E4=B8=AD=E9=80=94?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E4=BB=8E=E6=A0=B9=E5=9B=9E=E6=94=B6=E7=A9=BA?= =?UTF-8?q?=20box=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=AF=B9=E8=B1=A1=E9=87=8D?= =?UTF-8?q?=E5=8F=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sbo_find_alloc 在 meta 块不足时调用 sbo_reclaim_empty(root)。若当前所在 box(或其祖先)整棵子树为空,会被这次回收 blocks_free 掉;随后 blocks_alloc 往往把同一块再分出来,新对象落在已脱离树的 box 里,而父层 认为该 slot 空闲,之后的分配与之重叠,且该块永久泄漏。 - sbo_find_alloc:只回收当前 box 自己的空子 box;块不足直接失败。 - sbo_alloc:find 失败且确有块被回收时,在顶层重试一次(不再误报 OOM)。 - sbo_mask_contmax:m32 非 0 时 best 应为 32 而不是 33(只会高估,hint 精度问题)。 - sbo_find_alloc 新建子 box 后补 sbo_propagate,父链缓存不再过期高估。 - sbo_box_at:id * stride 改用 size_t,避免单 shard 池 >= 4GiB 时溢出。 18_model_check 新增 G(3 块池定向复现:重叠 + 泄漏)与 H(5 块池随机 churn)两个阶段;旧代码下二者均 FAIL。 --- include/slotsboxmalloc/slotsboxobj.h | 21 ++++-- tutorial/18_model_check.c | 103 +++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/include/slotsboxmalloc/slotsboxobj.h b/include/slotsboxmalloc/slotsboxobj.h index 814506e..2c654ed 100644 --- a/include/slotsboxmalloc/slotsboxobj.h +++ b/include/slotsboxmalloc/slotsboxobj.h @@ -175,7 +175,7 @@ static inline sbo_ctx_t sbo_ctx_for(sbo_meta_t *meta, uint8_t si, sbo_lock_t *lk return c; } static inline sbo_box_t *sbo_box_at(const sbo_ctx_t *c, int32_t id) { - return (sbo_box_t *)(c->pool + (uint32_t)id * c->stride + c->head); + return (sbo_box_t *)(c->pool + (size_t)(uint32_t)id * c->stride + c->head); } static inline uint8_t sbo_mask_contmax(uint64_t m) { @@ -192,7 +192,7 @@ static inline uint8_t sbo_mask_contmax(uint64_t m) { uint64_t m32 = m16 & (m16 >> 16); uint8_t best = 1; uint64_t x = m, y; - if (m32) { x = m32; best += 32; } + if (m32) { x = m32; best = 32; } y = (x >> 16) & m16; if (y) { x = y; best += 16; } y = (x >> 8) & m8; if (y) { x = y; best += 8; } y = (x >> 4) & m4; if (y) { x = y; best += 4; } @@ -377,11 +377,11 @@ static uint64_t sbo_find_alloc(sbo_ctx_t *c, sbo_box_t *box, } uint32_t need_blk = (uint32_t)(box->objlevel - usage.level); - if (!box->free_mask || c->max_blocks - (uint32_t)c->pool_meta->used_blocks < need_blk) { - sbo_reclaim_empty(c, sbo_box_at(c, 0)); - if (!box->free_mask || c->max_blocks - (uint32_t)c->pool_meta->used_blocks < need_blk) - return SBO_ALLOC_FAIL; - } + /* Never reclaim from the root here: it can free `box` (or an ancestor) while + * we are still inside it. sbo_alloc reclaims at top level and retries. */ + if (!box->free_mask && box->child_mask) sbo_reclaim_empty(c, box); + if (!box->free_mask || c->max_blocks - (uint32_t)c->pool_meta->used_blocks < need_blk) + return SBO_ALLOC_FAIL; int64_t cid = blocks_alloc(c->pool_meta, c->pool); if (cid < 0) return SBO_ALLOC_FAIL; @@ -402,6 +402,7 @@ static uint64_t sbo_find_alloc(sbo_ctx_t *c, sbo_box_t *box, box->child_max_count++; } box->max_obj_cap = sbo_mask_contmax(box->free_mask); + sbo_propagate(c, box); uint64_t sub = sbo_find_alloc(c, ch, usage, need_enc); if (sub == SBO_ALLOC_FAIL) { @@ -631,6 +632,12 @@ uint64_t sbo_alloc(void *metaptr, size_t size) { if (sbo_combined(root) >= need_enc) { if (!sbo_try_hint(&c, root, usage, need_enc, &off)) { off = sbo_find_alloc(&c, root, usage, need_enc); + if (off == SBO_ALLOC_FAIL && root->child_mask) { + uint64_t used = c.pool_meta->used_blocks; + sbo_reclaim_empty(&c, root); + if (c.pool_meta->used_blocks < used) + off = sbo_find_alloc(&c, root, usage, need_enc); + } if (off != SBO_ALLOC_FAIL && usage.level >= 1 && usage.level <= 3 && c.last_id >= 0) { uint32_t bsh = (uint32_t)usage.level * SBO_N_BITS + 3; diff --git a/tutorial/18_model_check.c b/tutorial/18_model_check.c index 92b60c4..1470c4f 100644 --- a/tutorial/18_model_check.c +++ b/tutorial/18_model_check.c @@ -241,6 +241,107 @@ static void phase_single_shard_mt(int nthr, int iters) { printf("F single-shard MT done (errors so far=%ld)\n", (long)g_err); } +/* Single-shard arena whose box pool holds exactly `blocks` boxes (root included). */ +static uint8_t *tiny_pool_arena(size_t data, uint32_t blocks) { + size_t probe = sbo_meta_size(data, 64 * 1024); + uint8_t *p = calloc(1, probe); + if (!p || sbo_init(p, probe, data) != 0) { free(p); return NULL; } + size_t stride = ((sbo_meta_t *)p)->block_stride; + free(p); + size_t ms = sbo_meta_size(data, blocks * stride); + uint8_t *m = calloc(1, ms); + if (!m || sbo_init(m, ms, data) != 0 || ((sbo_meta_t *)m)->max_blocks != blocks) { + free(m); + return NULL; + } + return m; +} + +static bool overlaps(uint64_t a, uint64_t as, uint64_t b, uint64_t bs) { + return a < b + bs && b < a + as; +} + +/* With every object freed, root + (blocks-1) boxes must be rebuildable: each + * 63*512B object needs its own L2 box. Fails if a box block leaked. */ +static bool tiny_pool_recoverable(uint8_t *m, uint32_t blocks) { + uint64_t o[64]; + uint32_t n = 0; + bool ok = true; + for (; n < blocks - 1 && n < 64; n++) { + o[n] = sbo_alloc(m, 63 * 512); + if (o[n] == FAIL64) { ok = false; break; } + } + for (uint32_t i = 0; i < n; i++) sbo_free(m, o[i]); + return ok; +} + +/* Pool exhausted while the search sits in an empty child box: that box must + * not be reclaimed under the in-flight allocation (it used to be, and the new + * object landed in a detached box overlapping later allocations). */ +static void phase_tiny_pool(void) { + const size_t data = 2097152; + uint8_t *m = tiny_pool_arena(data, 3); + if (!m) { printf("tiny pool: init fail\n"); g_err++; return; } + uint64_t a = sbo_alloc(m, 63 * 512), b = sbo_alloc(m, 63 * 512); + if (a == FAIL64 || b == FAIL64) { printf("tiny pool: setup alloc fail\n"); g_err++; free(m); return; } + sbo_free(m, b); + uint64_t o[3] = {a, sbo_alloc(m, 8), sbo_alloc(m, 63 * 32768)}; + const size_t req[3] = {63 * 512, 8, 63 * 32768}; + for (int i = 0; i < 3; i++) { + if (o[i] == FAIL64) { printf("tiny pool: alloc %zu failed\n", req[i]); g_err++; continue; } + if (sbo_allocated_size(m, o[i]) != expect_cap(req[i])) g_err++; + for (int j = 0; j < i; j++) + if (o[j] != FAIL64 && overlaps(o[i], expect_cap(req[i]), o[j], expect_cap(req[j]))) { + printf("tiny pool: OVERLAP off=%lu and off=%lu\n", (unsigned long)o[i], (unsigned long)o[j]); + g_err++; + } + } + for (int i = 0; i < 3; i++) if (o[i] != FAIL64) sbo_free(m, o[i]); + if (!tiny_pool_recoverable(m, 3)) { printf("tiny pool: box leaked\n"); g_err++; } + free(m); + printf("G tiny pool reclaim done (errors so far=%ld)\n", (long)g_err); +} + +/* Randomized churn on a box pool small enough to be exhausted constantly. */ +static void phase_tiny_pool_churn(int iters) { + static const size_t szs[] = {8, 100, 512, 4096, 32256, 65536, 300000}; + const size_t data = 2097152; + const uint32_t blocks = 5; + uint8_t *m = tiny_pool_arena(data, blocks); + if (!m) { printf("tiny churn: init fail\n"); g_err++; return; } + uint64_t rs = 0xC0FFEEULL, off[64], cap[64]; + int nl = 0; + long ok = 0; + for (int i = 0; i < iters; i++) { + if (nl > 0 && (nl == 64 || (rng(&rs) & 1))) { + int k = (int)(rng(&rs) % (uint64_t)nl); + if (sbo_allocated_size(m, off[k]) != cap[k]) g_err++; + sbo_free(m, off[k]); + off[k] = off[--nl]; cap[k] = cap[nl]; + continue; + } + size_t req = szs[rng(&rs) % (sizeof(szs) / sizeof(szs[0]))]; + uint64_t o = sbo_alloc(m, req); + if (o == FAIL64) continue; + ok++; + uint64_t c = expect_cap(req); + if (sbo_allocated_size(m, o) != c || o + c > data) { g_err++; sbo_free(m, o); continue; } + for (int j = 0; j < nl; j++) + if (overlaps(o, c, off[j], cap[j])) { + printf("tiny churn: OVERLAP iter=%d off=%lu cap=%lu vs off=%lu cap=%lu\n", i, + (unsigned long)o, (unsigned long)c, (unsigned long)off[j], (unsigned long)cap[j]); + g_err++; + break; + } + off[nl] = o; cap[nl++] = c; + } + while (nl > 0) { nl--; sbo_free(m, off[nl]); } + if (ok == 0) { printf("tiny churn: no successful allocations\n"); g_err++; } + if (!tiny_pool_recoverable(m, blocks)) { printf("tiny churn: box leaked\n"); g_err++; } + free(m); + printf("H tiny pool churn done (errors so far=%ld)\n", (long)g_err); +} + int main(int argc, char **argv) { if (argc > 3) { printf("usage: %s [threads 1..254] [iters>=1]\n", argv[0]); return 2; } int nthr = argc > 1 ? atoi(argv[1]) : 4; @@ -259,6 +360,8 @@ int main(int argc, char **argv) { phase_edges(); phase_small_arenas(); phase_single_shard_mt(nthr, iters); + phase_tiny_pool(); + phase_tiny_pool_churn(iters); printf("18_model_check: %s\n", g_err == 0 ? "PASS" : "FAIL"); free(g_meta); free(g_data); free(g_claim); return g_err == 0 ? 0 : 1; From 09fe719a84f22b4fd17cce321c01247df939d6e0 Mon Sep 17 00:00:00 2001 From: junyao <1071307515@qq.com> Date: Fri, 30 Oct 2026 17:28:38 +0000 Subject: [PATCH 2/2] =?UTF-8?q?revert:=20=E5=8E=BB=E6=8E=89=20contmax=20?= =?UTF-8?q?=E4=B8=8E=20propagate=20=E4=B8=A4=E5=A4=84=E5=B0=8F=E6=94=B9?= =?UTF-8?q?=E5=8A=A8=EF=BC=8C=E6=9C=AC=20PR=20=E5=8F=AA=E4=BF=9D=E7=95=99?= =?UTF-8?q?=E9=87=8D=E5=8F=A0=E4=B8=8E=E6=BA=A2=E5=87=BA=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两者只影响 hint 精度(高估容量,不会分错也不会误报 OOM),且 contmax 修正在 churn 基准上有约 2.5% 的未解释开销。 --- include/slotsboxmalloc/slotsboxobj.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/slotsboxmalloc/slotsboxobj.h b/include/slotsboxmalloc/slotsboxobj.h index 2c654ed..9846d5f 100644 --- a/include/slotsboxmalloc/slotsboxobj.h +++ b/include/slotsboxmalloc/slotsboxobj.h @@ -192,7 +192,7 @@ static inline uint8_t sbo_mask_contmax(uint64_t m) { uint64_t m32 = m16 & (m16 >> 16); uint8_t best = 1; uint64_t x = m, y; - if (m32) { x = m32; best = 32; } + if (m32) { x = m32; best += 32; } y = (x >> 16) & m16; if (y) { x = y; best += 16; } y = (x >> 8) & m8; if (y) { x = y; best += 8; } y = (x >> 4) & m4; if (y) { x = y; best += 4; } @@ -402,7 +402,6 @@ static uint64_t sbo_find_alloc(sbo_ctx_t *c, sbo_box_t *box, box->child_max_count++; } box->max_obj_cap = sbo_mask_contmax(box->free_mask); - sbo_propagate(c, box); uint64_t sub = sbo_find_alloc(c, ch, usage, need_enc); if (sub == SBO_ALLOC_FAIL) {