18 KiB
阶段3缓存同步独立方案审查
日期:2026-09-22。独立审查任务:login_rounds_independent_review。审查对象为方案,不是代码验收;本轮没有阶段3生产实现、测试执行、正式服或 Unity 操作。
最新状态:主会话已修订,R1–R4 技术合同问题复审关闭,限定独立方案评审通过;两项收紧策略及完整方案仍待用户书面确认。 以下保留初审记录,末尾为复审闭环。技术通过不代表已取得用户确认或已执行阶段3测试。
裁决摘要
方向认可,当前为“补齐合同后通过;两项收紧策略仍待用户书面确认”,尚不放行生产编码。
独立状态/磁盘层 → 固定会话串行同步 → 限定联合验收的分批方式合理。没有要求修改服务器 CAS、游戏入口或 PlayFab;旧备份与未知写均采取保守冻结,可以在现有服务器契约下避免自动继续覆盖,但降低异常后的可用性。这两点改变旧总体方案,不能拿历史 G1 通过替代本次用户确认。
本审查发现的是必须在实现前统一的并发/返回值合同,不是声称尚未编写的代码已经有 bug。需补三项关键约束(R1–R3)和一项 Delete 语义(R4),避免不同实现者作出相反解释。修订由主会话完成,本审查只拥有本文件。
审查依据
- 主方案《阶段3缓存同步方案与评审-2026-09-22.md》,包括本轮已追加的 active/pending sequence 合法引用、Unknown/auth 独立标志、连续同类型组批与水位 Superseded 计数。
- 《阶段1验收.md》(G1)、《客户端迁移详细修改方案.md》第6/7节、《阶段2独立KV代码审查-2026-09-22.md》最终限定裁决。
- 当前 PlayerKVClient/PlayerKVModels、S101AccountSession、UserService API 登录/Logout、CustomServerMgr 会话更换路径。
源码约束核对:PlayerKVClient 对所有进入传输的失败写保守返回 MayHaveCommitted,不能由阶段3自行把 HTTP400/401 改成确定未写;S101AccountSession 每次 Login 建新 CookieContainer,但其公共 Cookies/IsAuthenticated 可随调用方生命周期改变;UserService 当前 Logout 先 Dispose apiSession,后清 Cookie/取消 LoginLifetime,尚没有阶段3 owner 关闭合同;CustomServerMgr 的 transport 可被替换。因此主方案“不直接复用全局 transport、阶段4才接入”是必要边界。
R1:补齐操作准入、登记与 Close 的原子性/锁顺序
优先级:P1 级设计约束;主要阻塞3B,状态锁/所有权定义应在3A模型阶段明确。 位置:主方案§4.1、§5、§8.1。
方案已要求 Close 同步置 Closed、取消、等待登记操作和磁盘收尾,再释放独占锁;但没有写清“检查可用状态”和“登记操作”的同一原子步骤,也没有区分实际工作任务与等待票据的任务。
必须排除的交错
- Update/Flush 检查到非 Closed,尚未登记。
- Close 设置 Closed,看到登记集合为空,释放文件锁并返回。
- 新 owner 获得同命名空间文件锁。
- 旧调用才登记并落盘/派发,形成两个写入者。
另一种死锁:Close 等待“所有登记任务”,其中包含等待立即票据的 Task;关闭后的调度器不再运行,又没有终结尚未发送票据,双方永久等待。这与方案有意等待“不响应取消的真实传输”不同,不应混为允许的关闭等待。
最小修订建议
- 一个操作准入门保护 Closed 检查、操作计数/登记与 Close 的封门;调用要么原子取得许可并登记,要么立刻 Closed,不存在已允许但未计数的窗口。
- 独占文件锁属于 owner;内存/磁盘事务门负责有序快照提交;调度门只序列化网络操作。明确任何路径不得持内存/调度门等待 Close/drain,网络 await 不持状态门。
- Close 不把自身加入要等待的集合;封门后在锁外执行取消/完成回调,再等待实际已登记工作收尾。预先终结/唤醒未派发队列及票据等待者,不让“没有网络在途的票据”等待成为 drain 的前置环。
- 在途已发送写保留 active/unknown 证据;未发送票据的关闭结果和保留 pending 分开。磁盘事务完成、网络请求实际结束之前不释放文件锁。
计划用例: 在准入检查与登记之间设可控屏障并并发 Close;证明旧调用不能在 Close 完成后写。无网络在途但有立即票据时 Close 能完成;真实传输不响应取消时 Close 不完成且第二 owner 得 Busy。这些是待实施用例,不是已测试结论。
R2:分开“取消等待票据”与“撤销已接受编辑/取消传输”
优先级:P2 级接口合同;3A票据模型和3B调度实施前需明确。 位置:主方案§7.3、§8.1。
当前票据列出 Cancelled,但未规定 caller 的取消是取消等待,还是删除 pending/取消共享批次。例如 A/B 两个立即请求进入同一 Set 批,A 的界面关闭取消等待;若这同时取消批次 HTTP,就把本来仍可正常完成的 B 也推进 UnknownOutcome。若取消会删 A 的 pending,则违背已 Accepted 的可靠本地编辑不得静默丢失。
最小修订建议
- 已成功落盘且返回 Accepted 的编辑不因调用方停止等待而撤销。若要撤销,必须提交新的显式 Set/Delete 意图;本批不新增自动补偿 API。
- 建议票据等待 token 只取消该等待者,返回 Cancelled + 原 sequence,不删除 pending、不改其它票据、不取消共享批次;票据事实与一次 Wait 的结束原因分开。
- owner 生命周期/Close 的 token 才取消传输;若为 Flush 本身另设取消,其影响范围需明确,已发送写按 MayHaveCommitted 保留未知。
- 未进入持久化的预取消不创建编辑;已持久化后收到取消,应保留 sequence/接受事实,不能统一返回一个让上层理解成“从未接受”的失败。
- 明确阻塞、未知、关闭时所有水位内等待者均能获得终态/等待结果;进程重启后的 pending/unknown 是持久事实,旧进程 TaskCompletionSource 不需要也不能反序列化恢复。
计划用例: A/B同批,A取消等待、B正常确认;A待发送时取消等待后重开仍有pending;v1未发被v2替换即Superseded;v1已发送时v2创建,v1按自身精确成功确认、v2仍pending;Close时未发票据与已发票据分别返回可解释结果。
R3:说明磁盘提交与调用结果之间的“不确定”窗口
优先级:P2 级可靠持久化合同;3A编码前需明确。 位置:主方案§4.3、§5结果模型。
“原子替换成功后发布内存、返回Accepted”顺序正确;但“保存失败→StorageError”不能被解释为主文件一定还是旧版本。文件系统调用/后续步骤可能在主文件已替换之后报告失败,或进程在替换与调用者收到结果之间终止。重开时会看到包含该编辑的新revision,即使调用方没有看到Accepted。
这不是要求客户端消除物理介质不确定性,而是不要对上层提供不存在的回滚保证。否则调用者看到 StorageError 后做补偿,下一次加载却重新获得原编辑,仍可能产生重复业务效果。
最小修订建议
- 指定原子主文件替换为提交点;区分确定尚未提交与提交结果不确定。可以加本地
MayHavePersisted/耐久状态,也可在 StorageError 合同中保守规定它不证明“未落盘”,但返回/记录本次 attempted sequence/revision,便于后续诊断与绝对目标重试。 - 失败时停止可靠编辑和网络,不在原 owner 中悄悄退回旧revision继续工作;重开重新读取并验证主文件,而不是把内存旧快照覆盖到磁盘。
- 备份更新失败的处理也必须与提交点一致;不能主文件已新、仍以内存旧状态报普通未接受并继续接受下一笔。
- 立即票据在本地提交无法确定时不能 Confirmed;Stage4业务不得把 StorageError 当作服务端/本地均未改变的证明。
计划用例: 在“替换完成但尚未发布/返回”注入异常和进程中止;打开后看见有效新revision时不自动反向写,不丢已持久化意图;有active的确认保存不确定仍冻结;明确无变更时才报告确定未提交。
R4:明确 Delete 与 localOnly 的层覆盖关系
优先级:P2 级小范围语义补充。 位置:主方案§5。
remote=true 的 Update 明确清同key localOnly,但 Delete 只写“与Set共享sequence和批次规则”。如果 Delete 保留 localOnly,远端删除确认后 ReadLocal 仍返回该本地专用值;两种解释都能从现文字实现出来。
建议明确:Delete 是远端意图,与 remote=true Update 一样在同一次可靠持久化中清同key localOnly;如果业务需要只删远端但保留本地覆盖,必须另设显式接口/另行评审。本批不新增玩法删除入口。补计划用例:pending Set→localOnly→Delete,以及 active Set→localOnly→Delete→旧Set确认,Delete意图/本地显示/基线推进均一致。
已认可、无需重复返工的部分
| 领域 | 独立评审判断 |
|---|---|
| 三批推进 | 3A先状态和磁盘、3B再真实协议协调、3C组合与fixture,避免在未验证缓存上切生产存档,合理。 |
| 固定归属 | 完整基址含路径做环境指纹、规范数字ID、独立锁;不把IP与域名自动合并,不导入旧PlayFab键,合理。实现时ID应采用S101AccountSession同一规范十进制表示,避免101/0101路径别名。 |
| 多版本 | active不可变;pending保留新目标;v1成功只清精确sequence,v2基线推进到v1。当前文档已说明同key同sequence可被active引用,未误判为快照重复。 |
| B/L/R | 存在标记区分缺键/空值/Delete,JSON只精确比较;整次合并落盘后才放行网络,不先发送部分无冲突key,合理。 |
| 未知写 | MayHaveCommitted严格沿用阶段2;重启遗留active保守未知;观察R==B/L不自动解锁,具备安全动机,但须用户确认可用性代价。 |
| 身份失效 | unknown证据、auth失效独立于主枚举,Closed/StorageError不清证据;401之后重新认证也不能仅改状态回Ready。当前新增说明已解决单枚举丢信息问题。 |
| 水位/批次 | 连续同类型、最多10key、按sequence升序、不跨中间类型;水位外新版本不临时加入,水位内被替换计Superseded而非Confirmed,当前补充充分。 |
| remoteStore=false | 明确只改localOnly、不撤销历史远端pending,旧确认不覆盖覆盖层。与原设计相比增加确定语义,不能在实现里再当成“暂停同key全部上传”。 |
| 备份恢复 | 主文件损坏/有遗留而主缺失不静默建空;bak不自动当最新可写状态,逻辑安全但须用户确认。这不是实现“自动恢复”全部成功。 |
| Owner与桥接 | 专属HttpClient固定捕获Cookie;认证session由调用方拥有;生产Logout/CreateOwner接线留阶段4,符合当前源码实际。 |
| 正式联调 | 新前缀、精确fixture请求范围、专用磁盘根,不把全量业务字典装入测试缓存;不在正式服造迟到未知写,合理。 |
其它实施约束与证据边界
- 有限保证保留。 本地 Ordinal 字典不等于服务器 MySQL key collation;本批不能宣称大小写变体是独立服务器key,也不能全局小写化业务key。阶段4盘点实际键空间时另行处理;不因此擅改服务器。
- 跨设备不变强一致。 三方比较只是观察时点,Get与Set之间仍可被别的设备改写;本地锁也不阻止其它设备。这是已声明服务端无CAS的保证边界,不是再给客户端加一把锁就能消除的缺陷。
- Unknown永久冻结的操作后果。 无公开解锁接口意味着部分账号命名空间可能直到后续恢复方案才可写;用户应确认的是这个实际行为,而不是笼统“更安全”。可以诊断读取不等于已恢复游戏可用性。
- 备份规则的准确措辞。 主文件有效时旧bak/tmp不应被用来回退;主文件有效但bak自身损坏是否仅诊断,需与主方案§2“主文件/备份异常必须阻止”措辞统一,避免误解为任何旧备份坏都必须冻结。无需为此新增恢复工具。
- 磁盘严格校验。 不可信快照除了结构还应限定计数/字符串总量/深度,区分本地 sequence 与服务器版本;测试覆盖序号溢出、active关联不可能组合。具体容量阈值应在3A实现常量/配置处记录,不以无界内存队列替代旧50键限制。
- 测试工程通配。 当前KVContractTests的Compile为
Services/PlayerData/*.cs,新增模块会一起进入该宿主编译。3A应继续保持纯逻辑依赖,或在已评审测试变更中显式列出协议两文件,不要靠未声明Unity引用让原42项失去可运行性。 - 平台差异单列。 Windows宿主的File.Replace/独占句柄/故障注入通过不能宣称Android/iOS原子性、断电耐久和进程终止都已通过;最终包仍需对应平台证据。
完整放行顺序
- 主会话修订 R1–R4 的合同并同步主方案;独立复核关闭这些设计歧义。
- 用户书面确认“未知读回不自动解除冻结、旧备份不自动可写恢复”及这份完整阶段方案。当前两条未确认,历史G1不替代确认。
- 放行3A的限定新增文件及离线宿主;完成G3A实际验证再推进3B、3C。测试矩阵仍全部为待执行,不能把本次审查记作58项或42项新测试通过。
- 新增实际问题按差异补裁决;阶段4生产接线、玩法立即扣减语义、缓存迁移与SDK删除不自动放行。
本轮最终状态:方案独立评审已完成,需小范围合同修订及用户确认后实施;没有生产修改、没有阶段3已通过的测试或正式服验收。
复审闭环:R1–R4 已修订
2026-09-22 同日复审。主方案审查快照 SHA256:E3FF0830278B59BB148BD2F36BE81FB90A2E0E7FD1F0100C29A3C49E0AE7C11A。本节覆盖前文初审的技术修订待办;原发现保留以便实现时追踪依据。
| 条目 | 主方案修订与复核 | 裁决 |
|---|---|---|
| R1 操作准入/Close | §8.1 已将 Closed 检查、登记、owner租约放在同一短临界区;finally归还;Cancel在锁外;被动票据等待不计入必须drain的工作;关闭不等自身、不持state/dispatch锁等worker;G3B6新增交错/回调重入用例 | 关闭 |
| R2 取消等待与事实分离 | §7.3 明确Wait/Flush调用方取消只结束该等待,不撤销pending、不取消同批/共享Flush;HTTP只绑定owner生命周期;通过显式WaitForTicketAsync表达;G3B7覆盖同批等待取消不牵连其它票据 | 关闭 |
| R3 本地提交不确定 | §4.3 增加attemptSequence/目标revision/LocalCommitMayHaveSucceeded,StorageError不等于未保存;停止写、不自动补偿。§6区分“HTTP200但确认快照未提交”的未知与“确认快照已提交仅返回异常”的本地核对;G3A9覆盖替换完成后异常 | 关闭 |
| R4 Delete/localOnly | §5 明确Delete与remote=true Set一致,在同一次持久化中清除同key localOnly,不让成功删除继续被旧覆盖层遮蔽 | 关闭 |
最终技术判断
限定独立方案评审通过,当前没有确认的技术方案阻塞项。 状态、磁盘提交、多版本、批次/水位、取消与关闭、固定会话、票据及例外保证已形成可实现合同;R1–R4用例在实施后必须实际验证,不能仅凭本次评审勾选通过。
还需保持两项实现说明,不作为再次阻断已修订方案的理由:
- 主有效但bak损坏/缺失是否仅诊断,应在具体CacheStore加载规则中保持一致;不要误把任何旧备份异常都等同主文件损坏,也不要用备份替换有效主文件。
- 本地提交重新核对能证明目标revision在盘,不等于允许自动重放原业务动作;阶段4必须结合票据/业务调用确定是否需要继续,不能补回再扣或重新生成目标值。
授权和执行门仍然独立
用户还没有在本轮书面确认两条改变异常恢复能力的策略:
- 未知写即使Get观察等于基线/目标,也不自动解除冻结;本批无自动解锁接口。
- 主缓存损坏时旧备份不自动成为可写恢复来源。
这两条不能由独立技术审查替代用户确认,也不能由历史G1推定已批准。 当前可交付完整书面方案供确认;确认后才进入3A,G3A实际验收后再推进3B/3C。没有新增生产代码、没有执行计划中的任何阶段3测试,生产桥接、游戏存档切换与最终打包仍未放行。
最后措辞复核:备份歧义已闭环
主会话随后修订§2/§4.3,明确主文件有效时,bak较旧或损坏仅诊断、不额外冻结;没有有效主档且有遗留材料时才要求RecoveryRequired。本审查已重新读取确认,与主文件优先、旧备份不自动恢复可写状态的方向一致。前文“需保持一致”的备份措辞建议现已关闭;最终技术裁决不变:限定独立评审通过,待用户确认新策略后实施。
此次最终主方案 SHA256:43A5E844536F3769262E282205855F8E041D06CB2F89CB220D387C1C17D2FBF1。
状态同步后的当前存证
主会话随后仅同步主方案顶部/末尾状态及检查表:技术评审和 R1–R4 闭环已完成,用户确认与3A实施仍未勾选。已只读核对当前状态表述符合本报告裁决;没有重跑审查或测试、没有修改主方案。
当前主方案 SHA256 为 097EE0940D1FD41FCA31308C49BB8897EF6D98360BDD56AACE7DBCA58919A61F。上一条 hash 保留为状态同步前的审查快照,当前文件匹配以本条为准。结论保持:技术评审通过,待用户确认新策略及完整方案,尚未进入3A编码。