19 区块互换现象
1 区块互换现象(MC-161823)
区块互换是一个罕见但真实存在的 bug(Mojang bug tracker: MC-161823):区块 A 的内容(方块、实体、方块实体)出现在区块 B 的位置,区块 B 的内容出现在区块 A 的位置,或者区块 A 的内容覆盖了区块 B。
核心机制:Region 文件头部指针错误。
1.1 Region 文件的结构
一个 .mca region 文件包含 32×32 个区块,结构如下:
- Cluster 0(4096 字节):头部,存储 1024 个指针(每个区块 4 字节),指向该区块数据在文件中的扇区偏移和长度
- Cluster 1(4096 字节):时间戳头部,存储每个区块的最后修改时间
- Cluster 2+:实际的区块数据(NBT 压缩后的数据)
关键冗余:
- 头部指针:
sectorData.get(index)→ 偏移和长度 - 区块数据内部:NBT 中存储了
xPos和zPos(正确的全局坐标)
理论上,头部指针和区块数据的坐标应该一致。但如果头部指针错误,就会出现 "头部说你是 [16, -15],但你的 NBT 数据说你是 [16, -10]" 的矛盾。
1.2 错误的写入顺序
RegionFile.writeChunk() 的写入流程(简化):
java
11 LINES||
// SYNTAX_HIGHLIGHT
正常情况下,这个顺序是安全的:
- 先写数据到新扇区(line 287)
- 再更新内存中的头部(line 290)
- 再刷新头部到磁盘(line 292)
- 最后释放旧扇区(line 295)
问题在于:如果在步骤 2-3 之间发生崩溃/断电/OOM/halt (1):
- 数据已写入新扇区(磁盘上)
- 但头部的内存副本已更新,而磁盘上的头部还是旧的
- 如果崩溃,内存丢失,磁盘上的旧头部仍然指向旧扇区
更隐蔽的情况是:头部部分刷新成功。如果 writeHeader() 在写入过程中被中断(极罕见),可能导致:
- 区块 A 的头部指针被更新
- 区块 B 的头部指针未更新
- 但区块 A 和区块 B 的数据都被写入,只是头部指向混乱
1.3 Minecraft 的错误处理方式
当 Minecraft 加载区块时,检测到坐标不匹配:
[Server thread/ERROR]: Chunk file at [16, -15] is in the wrong location; relocating.
(Expected [16, -15], got [16, -10])
这表示:
- 头部指针的索引对应 [16, -15]
- 但读出的 NBT 数据的
xPos/zPos是 [16, -10]
Minecraft 的处理(错误):
java
6 LINES||
// SYNTAX_HIGHLIGHT
问题:Minecraft 信任头部指针,修改区块数据的坐标,而不是相反。这是错误的,因为:
- 头部指针可能在崩溃时损坏
- 区块数据的坐标更可靠(已经通过 NBT 解析验证)
结果:
- 区块 [16, -15] 的头部指针指向了区块 [16, -10] 的数据
- Minecraft "修复" 后,区块 [16, -10] 的数据被强制标记为 [16, -15]
- 如果区块 [16, -10] 的头部指针也指向错误位置(如指向 [16, -15] 的数据),就形成了互换
1.4 为什么没有稳定复现方法
触发条件极为苛刻:
- 时间窗口极窄:必须在
this.sectorData.put()和this.writeHeader()之间崩溃,或者在writeHeader()的文件 I/O 过程中中断 - 依赖外部事件:需要崩溃/断电/OOM/Watchdog halt (1)
- DSYNC 部分缓解:从 20w21a 开始(MC-149159),region 文件以
StandardOpenOption.DSYNC模式打开,强制每次写入立即同步到磁盘,减少了问题发生概率 - 仍可能发生:
- DSYNC 只能保证单次
channel.write()的原子性 - 无法保证 "写数据 → 写头部" 的顺序在崩溃时的一致性
- 如果头部写入过程中被中断(操作系统级别的中断,如断电),仍可能导致部分头部损坏
- DSYNC 只能保证单次
1.5 日志特征
当区块互换发生时,日志中会出现:
[Server thread/ERROR]: Chunk file at [X1, Z1] is in the wrong location; relocating. (Expected [X1, Z1], got [X2, Z2])
[Server thread/ERROR]: Chunk file at [X2, Z2] is in the wrong location; relocating. (Expected [X2, Z2], got [X1, Z1])
如果看到成对的错位日志,且两个区块的坐标互换,就是典型的区块互换现象。
