麻将服务器崩溃补偿与异常处理语义(CRASH_SEMANTICS)
第0条(基线)本文件语义以产品源码为权威源;引用只允许以文件与符号(函数、常量、字段、协议名)给出。常量:
MAHJONG_PROTO_CRASH = 1020、MAHJONG_EVENT_CRASH = 7(mahjong_define.lua)。
第1章 崩溃类型
第1条(类型表)类型常量定义于 mahjong_define.lua:
| # | 常量 | 触发 | 处理链 | 冻结资金 | 房间状态 | 客户端 |
|---|---|---|---|---|---|---|
| 1 | PLAYER_OFFLINE |
无生产触发点;GS 处理列表不含 | 人工注入时 GS 静默忽略,无回滚无通知 | 不退款 | 不清除 | 无通知 SUSPECT-1 |
| 2 | ALLOC_PLAY_NODE |
场景服建服失败映射;MC 通知 | MC 到场景服,逐玩家到 GC 到 GS 回滚并确认 | 全额退还,额度为冻结与入场冻结的较小值 | 场景服删房;MC 取消房 | S2C_MAHJONG_Crash |
| 3 | CENTER_DISCONNECT |
场景服检测 GC 断开整房补偿;建服失败映射 | 场景服只通知 MC;MC 无类型分支,走兜底取消 | 重登后退 | 场景服删房与解绑 | 重登按无房处理 SUSPECT-4 |
| 4 | ROOM_NOT_EXISTS |
场景服空房销毁逐玩家通知,含一次重试;无房兜底 | 场景服到 GC 到 GS 回滚;并行通知 MC 取消 | 全额退还 | 双端清理 | S2C_MAHJONG_Crash |
| 5 | MATCH_CENTER_DISCONNECT |
无生产触发点;MC 断开实际由场景服处理,只解绑删房不通知玩家 GC | 玩家退款完全依赖重登无房路径 | 重登后退 | 场景服删房 | 重登按无房处理 SUSPECT-2 |
| 6 | PLAY_SERVER_DISCONNECT |
MC 健康检查;玩家操作发现断线;验证失败;场景服侧验证超时 | MC 到场景服校验玩法服 ID,不一致走验证自愈与冷却;逐玩家到 GC 到 GS 经三通道判定后回滚、确认、通知 | 全额退还 | 场景服删房并清反向索引;MC 取消房 | S2C_MAHJONG_Crash |
| 7 | SERVER_MAINTENANCE |
维护事件经 MC 下发,本局作废 | MC 到场景服,逐玩家到 GC 到 GS 回滚;MC 取消房 | 全额退还 | 场景服删房 | S2C_MAHJONG_Crash |
第2条(统一处理链)
(一)崩溃补偿转发:场景服以 RPC 入参场景服 ID 为准,为 0 时必须补全;转发失败必须回 ForwardFailed;场景服对 ForwardFailed 只认结算协议,CRASH 不得重投。
(二)确认回执:GS 必须回 AckSettlement;场景服在确认 token 不小于结算 key 时必须移除 pending。
(三)上线确认:玩家上线在场景服不可达时必须回失败,等待下次重试。
(四)结算重投兜底:pending 登记后按节流重试(单条上限 4 次、跨会话总上限 12 次、每帧限量),耗尽暂停,重登恢复。
第2章 退款语义
第3条(退款校验链)RefundFrozenOnAbnormal(mahjong_game_server.lua)写前日志为统一线性流水线 token,与胡牌、EndGame 结算共享。步骤:
| 步骤 | 校验或动作 | 失败行为 |
|---|---|---|
| 1 状态守卫 | 无房间且冻结不大于 0 时清冻结 token 并成功返回 | 幂等返回 |
| 2 房间 ID 校验 | 仅当上下文房号大于 0:与存档房号不一致必须拒绝 | 拒绝且不退款 |
| 3 FreezeToken 契约 | 仅当双方 token 大于 0:不一致必须拒绝 | 拒绝且不退款 |
| 4 退款额度 | 冻结与入场冻结的较小值;差额必须记警告 | 亏损部分不返还 |
| 5 写前日志 | token 先推进并原子持久化 | 持久化失败拒绝,可重试 |
| 6 执行退款 | 额度大于 0 发放;成功清理并更新可用余额快照 | 发放失败必须回退 token 并持久化 |
| 6b 消耗冻结属性 | 消费冻结属性,失败重试 3 次 | 全败记 CRITICAL;token 不回退,防重复发奖 |
| 7 无冻结 | 直接清理并持久化 | 成功 |
第4条(防重复退还幂等点)
- 写前日志先推进再执行;成功路径不回退。
- 发放失败回退 token;发放成功绝不回退。
- 消耗冻结失败不回退 token。
- 状态守卫幂等。
- 房间 ID 校验防跨房误退。
- FreezeToken 契约防误退新房间。
- 崩溃三通道共用 key 公式(结算序号加回合号);小于期望跳过并确认,大于期望只确认等待,等于才回滚。
- 旧协议 key 兜底为已处理 token 加一。
- 回滚失败不确认,等待重试。
- 回滚入口幂等:无待确认事务即成功。
- 入场冻结 token 去重:清理时单调并入已处理 token,防重复扣款。
- 充值序列号去重:先持久化再扣款,失败清除标记。
- 场景服确认回执:确认 token 不小于结算 key 时移除 pending,否则照删。
- EndGame 直发 key 为结算序号加当前回合;二次结算同轮 key 加标记;按实际最大 key 精确推进。
- 自动结算失败不做内存重试,只入 5 秒重试队列。
Game_End幂等毒丸提供重入保护。- 结算原子持久化:冻结镜像与 token 同批写入;可用余额快照失败不回退 token。
- 结算包先于成功匹配到达时以结算包重建最小房间上下文,保证结算不落空。
- 全局分数平衡校验:总和大于 0 时强制清零。
第3章 崩溃后必须清理的状态
第5条(玩家存档数据)
- 房间上下文置空、pending 冻结 token 清零并并入已处理、pending 入场类型清零、冻结属性消费、可用余额快照更新。
- 其他回滚入口必须同样清理三件套:异常退房、重登无房、入场与建房与分享码加入失败。
第6条(房间索引)
- MatchCenter 取消房:跨中心匹配索引、房间表、对局计数、房号索引。
- 场景服:玩法服反向索引(桶空即删)、分享码、玩家解绑、房间移除。
- MC 玩家路由表。
第7条(定时器)
- 玩法服按房清理:开局失败与对局结束。
- 场景服:验证、建房重试、加载定时器。
- 客户端:全局、本地、请求、按类型、倒计时、回合、自动动作、结算清理全部计时器。
第8条(易漏清理点)
- pending 冻结 token 清零必须同步单调推进已处理 token,否则重放将重复冻结。
- 结束毒丸必须在异常路径清除,否则房间无法结束。
- 结束后房间保留且冻结 token 跨局保留;崩溃发生在结束与下局之间时,退款必须依赖 key 而非无房间。
- 场景服玩家索引必须在 GC 断开时清理。
- 客户端被踢玩家集合必须防止快照恢复被踢玩家。
- 客户端房间代际必须在完全重置时递增,以丢弃陈旧回包。
第4章 客户端侧
第9条(崩溃协议处理)
- 发布崩溃事件,含类型与参数,兼容旧包体字段。
- UI:有消息时弹窗;已结束状态自动退房;不清理牌桌,推迟到下次开局。
- 清空项:无;必须等待 EndGame 协议清理数据。
- 保留项:全部对局数据,含手牌、流水、房号、结算结果。
- 保留理由:服务端玩法服房间在结束后保留用于展示,结算经场景服 pending 重投送达;提前清空将导致 EndGame 无牌桌可渲染、流水恢复永久丢失;数据清理职责在 EndGame 与退出踢出路径。
第5章 模拟器可注入断点
第10条(注入点)
| 节点 | 函数 | 注入参数 | 模拟场景 |
|---|---|---|---|
| MatchCenter | ServerDisconnect_HandlePlayServer |
服务器索引 | 类型 6 玩法服断开 |
| MatchCenter | OnTickHealthCheck 与 VerifyRoom |
返回断连 | 类型 6 健康检查与验证 |
| Scene | MatchCenter2SceneServer_Crash |
类型 2、6、7;房号;错误断开服务器 ID | 类型 2、6、7 与串服回包 |
| Scene | Room_NotifyPlayersRoomClosed |
房间含不可达玩家 | 类型 4 空房解散 |
| Scene | 建房失败回调 | 永久错误与瞬态错误 | 类型 2、3、4 建服失败 |
| Scene | 中心断开处理系 | 断开 ID | 类型 3、5、6 |
| Scene | 上线验证超时 | 验证超时值 | 类型 6 重连验证超时 |
| GameCenter | SceneServer2GameCenter_Crash |
任意类型、场景服 ID 为 0、无效玩家 | 转发层容错 |
| GameCenter | 转发失败分支 | 玩家离线或跨中心断连 | ForwardFailed 回执 |
| GameServer | GameCenter2GameServer_Crash |
回合号前移与后移、未覆盖类型 | 三通道幂等 |
| GameServer | RefundFrozenOnAbnormal |
房号错值、冻结 token 错值、缺省上下文 | 校验链 |
| GameServer | Mahjong_AwardPlayerAttr 与存档写入 |
返回失败 | token 回退与不回退 |
| GameServer | 充值同步 | 相同序列号重放 | 序列号去重 |
| Scene | 结算重试与确认回执 | 确认 token 与结算 key 关系 | 确认与重投 |
| PlayServer | Game_Start 异常体 |
注入运行时错误 | 回滚清定时器与毒丸 |
| PlayServer | Game_End 自动结算 |
自动结算返回失败 | 重试队列 |
| 客户端 | 崩溃协议处理 | 类型、参数、旧包体 | 事件与保留语义 |
第6章 可疑缺陷
第11条(SUSPECT 清单)
- SUSPECT-1 类型 1 为死常量:无生产点且 GS 不处理,注入即资金悬挂。
- SUSPECT-2 类型 5 为死常量:MC 断开实际只删房不通知玩家,退款依赖重登无房,不重登则悬挂。
- SUSPECT-3 验证超时路径回合号硬编码为 1:真实回合大于 1 时 key 小于期望被跳过,不退款且仅收通知,资金悬挂。
- SUSPECT-4 类型 3 整房补偿不通知玩家 GC:同房来自存活中心或 GC 的玩家收不到补偿,只能等重登。
- SUSPECT-5 客户端注释与服务器类型表不符:注释提及的类型 8 与破产类型不存在,测试禁止按注释构造。
- SUSPECT-6 场景服建服永久失败分支不通知玩家 GC 与 MC:入场冻结退款仅靠重登无房。
- SUSPECT-7 GS 未来 key 分支不回滚不通知客户端:若重试永不达且 pending 已清,将静默丢单。
- SUSPECT-8 CRASH 转发失败无重投:通知丢失后仅靠重登链路兜底。
- SUSPECT-9 空房关闭通知仅重试一次,二次失败即丢弃。
- SUSPECT-10 退款校验在上下文为空时跳过房号与冻结 token 校验,直接按当前房间退款;测试必须覆盖新房间冻结受新 token 保护。
- SUSPECT-11 维护类型在玩法服侧无显式处理:内存房间清理依赖后续玩家操作或结束路径。
第7章 测试点
第12条(测试点清单)
- 注入类型 1 到 GS 崩溃处理:断言无回滚、无客户端通知、冻结不变。
- 类型 6 全链路:断言退款执行且额度为冻结与入场冻结的较小值。
- 类型 6 注入错误服务器 ID:断言走验证与冷却而非直接清理。
- 类型 6 验证超时:断言回合号硬编码为 1 且 GS 收到兜底 key。
- 类型 2 注入:断言跳过校验直接清理、逐玩家通知。
- 类型 7 注入:断言 MC 取消房被调用。
- 类型 4:断言每个玩家收到一次通知,失败时重试一次。
- 场景服 ID 为 0 注入:断言被补全为 RPC 入参。
- 转发失败:断言回 ForwardFailed 且 CRASH 不得重投。
- GS 三通道 key 小于期望:断言跳过并确认与通知,无退款。
- key 大于期望:断言只确认、无退款、无通知。
- key 等于期望:断言回滚、token 推进、确认与通知。
- 上下文房号错值:断言拒绝且冻结不变。
- 冻结 token 错值且双方大于 0:断言拒绝退款。
- 冻结大于入场冻结:断言退款等于入场冻结且记差额日志。
- 写前日志持久化失败:断言拒绝且 token 未推进。
- 发放失败:断言 token 回退并持久化。
- 消耗冻结连续失败 3 次:断言 CRITICAL 日志且 token 不回退。
- 无冻结无房间重入退款:断言幂等成功并清冻结 token。
- 退款成功后重发崩溃:断言跳过且不重复发放。
- 确认链路:断言确认 token 不小于结算 key 时移除 pending。
- 重试达单条上限:断言暂停;重登恢复。
- 总重试超上限:断言永久放弃并清理。
- 崩溃后重登:断言无房路径退款。
- 开局异常:断言定时器全清、毒丸复位、状态回等待、通知断开错误。
- 充值同序列号重放:断言第二次直接返回。
- 客户端收到崩溃:断言事件发布、数据未清、结算结果保留。
- 客户端崩溃后收到 EndGame:断言正常展示结算。
- 已结束状态收到崩溃:断言 UI 自动退房。
- 序列号组件崩溃重启同秒:断言持久化推进且 ID 不碰撞。