Skip to content

4-rwirext/json/json·from 覆盖写不清目标子树(孤儿键泄漏 + obj→scalar 脏读) #102

Description

@miaobyte

背景

json·from 写入前不清空目标子树,只写新成员并覆盖 marker。旧成员的 key 仍物理存在。

实测(8 组覆盖写,第一次写 A、第二次同 root 写 B):

场景 A → B 读回
obj-to-scalar {"a":{"x":1}}{"a":1} {"a":{"x":1}} ❌ 读到旧结构
shrink-obj {"a":1,"b":2,"c":3}{"a":9} {"a":9} ✅ 逻辑正确
shrink-array {"a":[1..5]}{"a":[1]} {"a":[1]} ✅ 逻辑正确

两类后果:

(1) 脏读(正确性)。 obj-to-scalar 时新值写在 root·a(标量),但旧的 root·a· marker 与 root·a·x 仍在;readValue 先探 marker,命中旧 objindex 就走 readObj,标量新值被完全遮蔽。

(2) 孤儿键(存储泄漏)。 逻辑正确的场景下物理垃圾照样留存:

$ redis-cli --scan --pattern '*ow-shrink-obj*'
/cx/ow-shrink-obj·      ← marker,成员表只剩 a
/cx/ow-shrink-obj·a
/cx/ow-shrink-obj·b     ← 孤儿
/cx/ow-shrink-obj·c     ← 孤儿

$ redis-cli --scan --pattern '*ow-shrink-array*'
/cx/ow-shrink-array·a·[0]
/cx/ow-shrink-array·a·[1..4]   ← 孤儿(marker 已是 map[1]{1})

list 走 marker body,所以逻辑视图干净,垃圾只在物理层累积——反复导入同一 root 会单调增长。

需求描述

json·from(src) -> root 的语义应是「root 子树等于 src」,而非「把 src 合并进 root」。

预期结果

  • 写入后 root 子树与源 JSON 精确对应,无残留 key。
  • obj-to-scalar 读回 {"a":1}
  • 重复导入同一 root 的物理 key 数不随次数增长。

方案简述

写入前对 root 执行子树删除(kvspace 侧已有 deltree 语义,需确认 dispatch ABI 是否暴露);若不暴露,则在 writeValue 里对每个被覆盖的节点按旧 marker 成员表递归删除。

需要在方案文档里明确:json·from 是覆盖语义还是合并语义。二者都可实现,但必须择一并写死,不能靠调用方猜。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions