diff --git a/docs/superpowers/specs/2026-10-06-payload-ownership-design.md b/docs/superpowers/specs/2026-10-06-payload-ownership-design.md new file mode 100644 index 0000000..dc86935 --- /dev/null +++ b/docs/superpowers/specs/2026-10-06-payload-ownership-design.md @@ -0,0 +1,121 @@ +# 模组网络包的线程归属与安全交接设计 + +日期:2026-10-06(Asia/Shanghai) + +状态:用户已同意“共用入口、正确性优先”的方向;本文待审阅,尚未进入实施。 + +## 1. 用户目标与边界 + +修复 Simulated 普通抓握把手无法松手,并覆盖走相同网络基础设施的更多模组交互,而非继续逐个包补丁。维度更新、实体等负载继续并行,要求主线程的处理器仍在真正的服务器主线程执行。允许为了正确性增加可观测的同步等待,但不把并行优化整体关闭。 + +首轮目标是 NeoForge 1.21.1 的服务端游戏阶段自定义包及其受支持的同步后续任务。登录、握手、配置阶段、客户端接收和纯网络线程处理不被无条件改派。不能据公共入口适配便宣称全部模组兼容。 + +坐服童子 ServerWarashi 的采样崩溃仍是待修复项,但属于独立的采样状态隔离工作,不混入本设计的包调度实现。不能声称网络修复会顺带解决它。 + +## 2. 已有证据 + +- 1.1.16 在隔离测试中修复了 CC 主线程读取被维度线程接管的世界而得到假空值的路径,已有暂停、收口、恢复和主线程回调处理机制。 +- 当前仅针对 Create `BlockEntityConfigurationPacket` 和 Simulated `AssemblePacket` 增加了 `SableAssemblyDispatch`,没有覆盖抓握把手更新包。 +- 本机 Simulated 1.3.0 的 `UpdatePlayerUsingHandlePacket.handle` 先读取方块实体。松手时会先停止玩家握持状态,但只有读到 `HandleBlockEntity` 才调用 `stopGrabbingServer`、释放约束。因此假空值存在“玩家状态清了,物理约束没清”的路径,现场仍须复现验证。 +- NeoForge `MainThreadPayloadHandler` 通过 `IPayloadContext.enqueueWork` 调用原处理器;`ServerPayloadContext` 同时存在 Runnable 和 Supplier 两种返回 future 的重载。 +- 本机 Veil 4.2.0 通过 NeoForge 的注册接口注册服务端处理器,公共入口有机会覆盖 Simulated 等使用 Veil 的模组。 +- 23:43 崩溃报告的新增诊断记录了 Jade 主线程查询与 Create 后台光照查询的归属不匹配。后者不属于网络包入口,必须保留为范围外问题。 +- ServerWarashi 2.1.1.2 的直接异常是 `PerfWorkTracker.claimSlot` 下标 -1。其共享采样栈不隔离主线程、维度线程,这是另一条需要复现的兼容路径。 + +## 3. 选定方案与取舍 + +采用公共入口适配、保守的全维度安全阶段,以及可审计的单维度例外机制。 + +对比:逐包白名单同步开销较小,但容易漏掉新的模组;直接允许任意线程读取世界仅消除检查,不能消除数据竞争。两者都不作为默认方案。 + +第一版新公共入口默认全维度隔离,不凭“玩家当前维度”或包内坐标猜测整个处理器的访问范围。已有单维度补丁必须审计其调用链后决定保留或归入公共阶段,不能因已存在就视为线程安全。以后缩小隔离范围须增加对应测试,不自动学习白名单。 + +代价:有交互负载时,主线程可能等待最慢的在途维度更新;批处理只能摊薄交接开销,不能保证忙维度绝不拖累交互。无待处理包时不建立新的安全屏障。 + +## 4. 组件职责 + +### 4.1 入口适配层 + +识别服务端、游戏阶段、有效玩家连接和原始 payload ID。拦截 NeoForge 主线程处理器的调度边界,保留原处理器及其上下文,不重写权限、距离、游戏规则和业务逻辑。审计 `ServerPayloadContext.enqueueWork` 两个重载,明确区分首次投递、阶段内嵌套任务和网络处理器主动提交的主线程工作。 + +每项任务携带服务器实例、连接身份、玩家身份、接收时世界身份、payload ID、入队时间和完成结果。禁止保存或输出包正文、聊天文本及认证数据作为诊断。 + +普通网络线程处理器的计算仍在原线程;它显式提交的世界主线程工作才进入交接。注册/注销通道、登录任务完成等路径照旧,不能在配置阶段调用只能用于游戏玩家的接口。 + +### 4.2 待处理队列 + +每个服务器独立托管,连接内按公共入口观察到的投递顺序执行,单项任务至多执行一次。不得按 payload 类型重排或合并抓取、松手和配置操作。不同来源的 vanilla 与 mod 包不承诺原本不存在的全局顺序,必须测试握持流程实际依赖的顺序。 + +初始批次上限为 64 项或 2 ms 的处理时间,达到任一条件后不启动下一项;不强行中断已开始的处理器。服务器队列上限 4096 项、单连接上限 256 项。满载时拒绝新任务、结束其 future 并限频报告,不能静默丢弃,不能无限堆积。连接过载应明确断开并走原有清理逻辑;把手清理须纳入验收,不能留下永久约束。 + +### 4.3 安全阶段执行器 + +由真正的服务器线程在不位于同步 worker→main 回调内的外层调度点取批次。请求暂停维度续排,等待已开始的维度工作及其子任务完全收口;等待期间继续服务使这些工作完成所需的主线程回调。禁止持有包队列锁、模组锁或世界锁等待。 + +取得全部所需世界的独占执行权后执行本批次。退出时只恢复本次暂停的循环,保留菜单暂停、停服和外层阶段的状态。异常也必须释放阶段,完成 future,不重复执行已经产生副作用的处理器。 + +阶段状态至少区分空闲、等待收口、独占执行、恢复。等待期间收到的普通包只排队,不能递归建立另一个全局阶段。独占执行期间的同服务器嵌套同步任务复用现有阶段。 + +服务器启动的未接管世界沿用原版执行权;动态维度创建/移除及停服必须与阶段生命周期协调,不能在独占阶段中偷偷启动新的维度写入者。超时用于诊断,不是允许并发执行的理由。 + +### 4.4 Future、异常与重入契约 + +保留 Runnable/Supplier 的结果、异常、取消语义;审计并保留 NeoForge 原有 `NetworkRegistry.guard` 对外语义,同时内部保留真实失败原因用于诊断。不能把未执行任务报告为成功,也不能统一把失败值伪造为 null。 + +阶段内同步调用应立即完成,避免原处理器 join 自己的排队任务。普通主线程外层调用可进入安全阶段后同步执行;同步 Sable 回调内尚未持有全局执行权的任务不得阻塞等待自己的维度归还所有权。此类不支持的同步重入必须明确失败并记录调用路径,不能以泛化修复之名引入死锁;对应模组须使用经验证的专门适配。 + +阶段外 worker/网络线程投递立即返回 future,不等待主线程。若上游工作线程本身阻塞等待这个 future,会与全局收口形成环路:不得宣称该路径自动兼容,测试必须检出并作为未支持调用契约报告。不可通过放开读取守卫掩盖。 + +## 5. 模组覆盖与旧补丁迁移 + +优先验收以下路径,但每条分别记录“已审计入口、已模拟验证、已实际模组验证”,不混为一项结论: + +| 路径 | 验收目标 | +| --- | --- | +| NeoForge MAIN 自定义包 | 真主线程、世界独占、原处理器至多一次 | +| Veil / Simulated | 抓取、滚轮距离、松手、组装与拆解 | +| Create 配置 | 转速设置与权限/距离检查保留 | +| Jade 查询 | 返回正确方块实体数据,追踪再次提交任务的路径 | +| 非 MAIN 处理器的 enqueueWork | 原网络计算不迁移,显式同步工作安全交接 | + +公共阶段已持有执行权时,旧 Create/组装适配必须直接复用它,不能再次排到已暂停的维度队列。旧补丁的移除需有相应通过测试;不存在的可选模组不得导致启动注入失败。 + +模组通过裸 `server.execute`、自建线程或其他框架再次提交的工作,不因首次包进入公共阶段就自动受保护。首轮对以上已知目标审计这些逃逸点,必要时增加范围明确的适配。禁止全局替换所有服务器任务的执行语义。 + +## 6. 玩家与世界生命周期 + +执行前重新核对连接仍有效、玩家未被替换、世界与接收时一致。过期的普通交互不对新世界重放,其 future 明确结束,原因计入诊断。 + +抓握释放不能只靠取消过期包:需要验证原模组在断线、换维度、死亡、把手拆除及子世界卸载时的约束清理。若原清理不完整,增加针对握持资源的可信生命周期适配,在旧世界安全执行;不能将过期客户端包改写坐标后重新执行。业务清理与通用排队失效策略分别测试。 + +服务器停止时停止接收新任务,尚未执行的 future 以取消/失败结束,正在执行者完成安全收尾;清空服务器持有的连接与世界引用。不能在保存开始后重启循环或继续应用包。 + +## 7. 诊断与性能验收 + +新增按 payload ID 的有界聚合:接收数、完成数、失败/过期/拒绝数、待处理数、最老等待时间、阶段等待/执行耗时、批次数。记录上限 128 个 ID,超出归入 other,避免恶意 ID 撑大统计。异常日志全局限频,保留最近有限条事件,绝不按包持续打印堆栈。 + +状态输出与崩溃报告要能区分:网络队列积压、等待维度收口、处理器慢、Sable 回调等待。沿用 1.1.16 的归属快照,不将快照中的 queued 单独称为死锁。 + +比较空载、普通交互、密集抓取/松手、多人查询和一个慢维度时的主线程 TPS、各维度实际完成 TPS、排队延迟及阶段耗时。报告回退代价,不以低 Carpet MSPT 判断成功。 + +## 8. 必须通过的验证 + +1. 先复现普通把手的失败路径:证明松手包到达、读到的对象和约束释放结果;测试不能只证明假包调用成功。 +2. 公共入口在维度工作进行中执行 handler 时仍取得正确对象、真实主线程和独占权;修复前应失败。 +3. 真实的抓取→距离修改→松手严格按投递顺序且仅一次执行。包含高频交互、不同玩家、掉线/换维度和子世界卸载。 +4. 权限拒绝、超距、错误坐标仍按原处理器拒绝,不造成强加载或越权操作。 +5. Runnable/Supplier 结果、异常、取消、嵌套 enqueue、等待中回调和非法同步重入都有测试;任何失败不能让 future 永远悬挂。 +6. 多维度、预先暂停、异常恢复、动态维度生命周期及停服时不发生并发写入、错误恢复或残留任务。 +7. 队列容量、批次预算和限频实际生效;松手不会被合并或被静默丢弃。 +8. 未安装 Create/Veil/Jade 等可选模组可启动;现有 CC、区块读取、水流、交互和纯 Java 调度回归全部运行。 +9. 验证发布 jar 不包含夹具和测试模组。完整整合包只使用存档副本,不修改正式服务器、启动器和模组目录。 + +真实航空学依赖无法加载或无法可靠自动化时,明确保留该验收项未通过,不将公共入口夹具等同于真实物理验收。 + +## 9. 本轮交付与后续独立工作 + +本文批准后编写实现计划;计划审阅后实施入口、执行器、适配及测试。交付候选 jar、覆盖清单、验证证据与未解决项。不自动上传远端或部署正式服务器。 + +ServerWarashi 随后的独立修复必须从其真实采样调用链建立失败测试,决定线程过滤或按线程分离状态时说明报告覆盖范围;不能只夹紧数组下标,也不能把仅主线程采样伪称全维度采样。 + +C2ME 随机源注入冲突、Create 后台光照读取、通用异步世界访问及既有停服超时行为不因本设计而宣称解决。