Skip to content

fix: 分配中途从根回收导致对象重叠;box 池 >4GiB 时指针溢出 - #14

Open
carsontung666 wants to merge 2 commits into
array2d:mainfrom
carsontung666:fix/reclaim-inflight-overlap
Open

carsontung666 wants to merge 2 commits into
array2d:mainfrom
carsontung666:fix/reclaim-inflight-overlap

Conversation

@carsontung666

@carsontung666 carsontung666 commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

只修两个会破坏数据的 bug,不含优化。

1. 分配中途从根回收,导致对象重叠

sbo_find_alloc 在 meta 块不足(max_blocks - used_blocks < need_blk)时调用 sbo_reclaim_empty(root)。如果当前所在的 box(或它的祖先)整棵子树为空,这次回收会把它 blocks_free 掉。紧接着 blocks_alloc 往往把同一块再分出来,新对象落在一个已经脱离树的 box 里;父层认为该 slot 空闲,后续分配就和它重叠,那个块也永久泄漏。

最小复现(2MB 数据、单 shard、池里正好 3 个 box):

  1. 分配两个 63×512B,得到 L2 box A、B,池满;
  2. free B 里的对象,B 变空但惰性保留;
  3. 分配 8B:搜索进入空 box B,块不足,从 root 回收,把 B 自己回收后又分给了自己。返回 y=33272;
  4. 分配 63×32KB:返回 z=[32768, 2097152),y 落在 z 里面。

kvspace-c 只在池"完全满"时才扩容(sbo_pool_full 只看还能否再放 1 块),剩 1–2 块、请求又需要建多层 box 时同样会走到这条路径。

修复: sbo_find_alloc 不再从 root 回收,只回收当前 box 自己的空子 box(不会释放当前 box),块不足直接失败;sbo_alloc 在 find 失败且 root 回收确实释放了块时,在顶层重试一次,不会因此误报 OOM。

2. box 池超过 4 GiB 时 sbo_box_at 32 位溢出

(uint32_t)id * c->stride 是 32 位乘法。单 shard 的 box 池超过 4 GiB(约 880 万个 box)后,box 指针回绕,指向池开头的旧 box。kvspace-c 用单 shard,head 池可以原地扩到 256 GiB(REGION_RESERVE),所以大库会走到这里。

复现:8 GiB 数据、4.5 GiB box 池、单 shard,连续分配 504B(每次新建一个 L1 box),直到超过回绕点 20 万个 box。之后检查最早的 1000 个对象:main 上有 669 个 sbo_allocated_size 出错,本分支 0 个。这个测试要 4.5 GiB 内存,没有放进 ctest。

修复: 改成 size_t 乘法。

测试

18_model_check 新增两个阶段:

  • G:上面的 3 块池定向复现,检查是否重叠,以及全部 free 后池是否还能重建(检测块泄漏)。
  • H:5 块池上的随机 churn(8B–300KB),逐次检查重叠和容量,结束后检查块泄漏。
main(v0.1.7) 本分支
18_model_check 4 10000(ASan+UBSan) FAIL:G 报 OVERLAP 和块泄漏,H 报块泄漏 PASS
18_model_check 8 × {1, 100, 1000, 50000, 200000}(ASan+UBSan) — 全部 PASS
ctest(Debug ASan+UBSan,不含 03_stress) 10/10 10/10
> 4 GiB box 池 669/1000 损坏 0/1000

性能

Xeon w7-3455,gcc 13.2 -O3 -DNDEBUG,单线程绑一个物理核。main、本分支、main 副本(A/A)交替运行 30 次取中位数。工作负载是随机尺寸 churn(8B–200KB,4096 个存活对象)。

case main Mops/s 本分支 Mops/s 本分支/main A/A 失败次数 main → 本分支
128MB / 64 shard 17.45 17.58 1.008 1.007 0 → 0
2MB / 1 shard 17.79 17.97 1.010 1.002 16971 → 16971
2MB / 8 块池 29.16 18.24 0.626 0.988 563830 → 31125

正常路径没有可测的开销。8 块池那一行 main 更"快",是因为 200 万次操作里有 56 万次分配直接失败:僵尸 box 不断吃掉块,失败路径返回得很早。

review 中发现的另外两处(sbo_mask_contmax 在 m32 非 0 时高估 1、新建子 box 后父链缓存不更新)只影响 hint 精度,不会分错也不会误报 OOM,按"只修大问题"的原则从本 PR 里去掉了。

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。
两者只影响 hint 精度(高估容量,不会分错也不会误报 OOM),且 contmax
修正在 churn 基准上有约 2.5% 的未解释开销。
@carsontung666 carsontung666 changed the title fix: 分配中途不再从根回收空 box,修复对象重叠 fix: 分配中途从根回收导致对象重叠;box 池 >4GiB 时指针溢出 Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant