fix: 修复多窗口并发导致的全局设置静默丢失 - #2
Merged
Lianues merged 2 commits intoJul 29, 2026
Merged
Conversation
多窗口下 B 窗口设置面板持有的列表可能早于 A 窗口刚保存的结果,而存储层是全量保存:saveLlmProviderConfigsSettings 会把不在提交列表里的记录判定为「用户删除」并真删。每一步都正常持有文件锁,但陈旧快照被当成权威,这类丢失靠锁防不住。 - 复用文件内已有的 savedAt 作为 revision,不新增持久化字段、不改 STORAGE_VERSION,可安全降级 - writeGlobalSettingsFile / saveRecordStore 增加可选 expectedRevision,在已持有的资源锁内比对;recordStore 的比对搭在已有的 previousIndex 读取上,零额外 IO - 不传 expectedRevision 时完全保持旧行为,内部初始化与迁移路径不受影响 - provider/compression 配置改用 pruneMissing 一次原子提交,取代「先 remove 再 write」,消除冲突时记录已被删的破坏性窗口 - 冲突时先推回最新快照再发错误信封,面板不会停留在陈旧数据 - 新增 6 个回归测试,含原缺陷复现:A 加 P1 -> B 持旧 revision 加 P2 -> B 被拒且 P1 存活
其他窗口修改全局设置后,本窗口面板会停留在陈旧值,需要手动 reload window 才能看到。 - 新增 GlobalSettingsWatcher 监听 data root 下的全局设置文件,180ms 防抖后按 section 收敛刷新 - 监听范围使用精确白名单:settings 目录下绝大多数文件是每会话一份的 conversation-*-llm.json,多 agent 任务会短时间批量写入,用通配会把刷新事件刷爆 - 刷新动作是重新读盘再广播的幂等纯读,因此无需自写抑制 - data root 被切换后通过 onDidChangeStorageRoot 事件重建监听器,不使用轮询 - 面板存在未落盘修改时,外部快照暂存而不覆盖表单,改为顶部提示条由用户选择载入 - 以 lastSyncedRevisions 区分真外部变更与本窗口自身写入的回送,避免误报
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
多窗口使用时,全局设置(渠道配置、压缩配置、MCP 服务等)会被静默覆盖,表现为「刚加的渠道过一会儿就没了」。
复现
[P0]P1并保存,磁盘变为[P0, P1][P0]P2,提交[P0, P2]P1被删除,最终只剩[P0, P2]根因
存储层是全量保存语义。
saveLlmProviderConfigsSettings会把「不在提交列表里的记录」判定为用户删除并真删:每一步都正确持有了
storageResourceLock,文件层面也没有撕裂 —— 问题在于陈旧快照被当成了权威。这类丢失靠文件锁防不住,只能靠写入前的版本比对。会话历史(
conversationHistoryStore/conversationTimelineStore)已有「锁内重读 + generation 校验」,不存在此问题,本 PR 未改动。方案
commit 1 — 乐观并发校验(安全底线)
savedAt作为 revision,不新增持久化字段、不改STORAGE_VERSIONwriteGlobalSettingsFile/saveRecordStore增加可选expectedRevision,在已持有的资源锁内比对;recordStore的比对搭在已有的previousIndex读取上,零额外 IOexpectedRevision时完全保持旧行为,内部初始化与迁移路径不受影响pruneMissing一次原子提交,取代「先 remove 再 write」,消除冲突时记录已被删的破坏性窗口冲突时的行为是拒绝保存并载入最新值,宁可让用户重做一次修改,也不静默删除数据。
commit 2 — 跨窗口自动刷新(体验)
GlobalSettingsWatcher,180ms 防抖后按 section 收敛刷新settings/下绝大多数文件是每会话一份的conversation-*-llm.json(实测 92 个文件中 86 个属此类,多 agent 任务单小时批量写入 14 个),用通配会把刷新事件刷爆onDidChangeStorageRoot事件重建监听器,不使用轮询lastSyncedRevisions区分「真外部变更」与「本窗口自身写入的回送」,避免误报兼容性
STORAGE_VERSION仍为 1,无新增持久化字段),可安全降级回旧版本读写revision?/expectedRevision?均为可选测试
npm run check全绿:201 pass / 0 fail(原基线 195 + 新增 6)。新增
tests/settingsRevisionConflict.test.cjs:expectedRevision→ 旧行为完全兼容settings/前缀、不含conversation-、顶层文件必须枚举且禁用*已知取舍
revision为 ISO 毫秒串,两窗口在同一毫秒内写入理论上可漏检;为不改变文件格式未引入计数器llm/common两个 section 后端未接 CAS(前者仅存单个activeProviderConfigId,后者存于 globalState)。前端仍会携带expectedRevision,后端忽略,无副作用consumeExpectedRevision),直到收到新快照才恢复校验,否则同一窗口连发多次保存会产生假冲突