描述 (Description)
在桌面端点击"新建会话"("New Session" 按钮),UI 会卡住 1-3 秒才出现空白会话。创建新会话这种轻量操作本应是瞬间完成的。
Clicking "New Session" in the desktop app causes noticeable UI freezing for 1-3 seconds before the blank session appears. This should be nearly instantaneous.
触发条件 (When it happens)
- 只要有多个已保存会话(5+ 个)就会有明显卡顿
- 保存的会话越多,卡顿越严重
- 每次创建新标签页/新会话都会触发(包括应用启动时的
buildTabController)
- Windows 上更明显(文件 I/O 较慢),macOS/Linux 也有
根因分析 (Root Cause)
问题代码位置
desktop/tabs.go:921-1079 — buildTabController 方法
创建新会话时,串行执行了多次全量会话文件扫描:
① migrateLegacySessionsIntoGlobalTopics × 2 次
// Line 976 — 第一次调用,全局扫描
migratedGlobalTopics := migrateLegacySessionsIntoGlobalTopics(config.SessionDir())
...
// Line 1024 — 第二次调用,项目扫描
migratedTopics := migrateLegacySessionsIntoGlobalTopics(dir)
migrateLegacySessionsIntoGlobalTopics 内部调用 agent.ListSessions(dir)(internal/agent/save.go:101),它会:
os.ReadDir(dir) — 读取目录所有条目
- 遍历每个
.jsonl 文件 → previewSession(full) — 打开文件、JSON 解码、统计用户消息数
- 再读
.meta 侧车文件
这意味着每次调用都要解码磁盘上每一个会话文件。
② findTopicSession — 再扫一次
// Line 1050 — 第三次扫描
existingPath := findTopicSession(dir, tab.TopicID)
findTopicSession(desktop/tabs.go:3312)又做了一次 os.ReadDir + 遍历所有 .jsonl → 读 .meta 文件 → 比较 TopicID。
③ boot.Build — 额外开销
// Line 999 — 组装 Controller
ctrl, err := boot.Build(buildCtx, boot.Options{...})
boot.Build 内部会初始化 MCP 插件、工具注册表、代码检查、事件系统等,也涉及 I/O。
汇总
一个简单的"新会话"操作触发了:
| 步骤 |
操作 |
I/O 类型 |
migrateLegacySessionsIntoGlobalTopics (全局) |
扫描全部 .jsonl + 解码 |
全量磁盘读 |
migrateLegacySessionsIntoGlobalTopics (项目) |
再扫一次全部 .jsonl + 解码 |
全量磁盘读 |
boot.Build |
配置加载 + MCP 发现 |
配置读 |
findTopicSession |
第三次扫 .jsonl + 读 .meta |
全量磁盘读 |
三次全量扫描都是 O(n) 的完整文件 I/O,且全部在 UI 线程路径上。
修复建议 (Proposed Fix)
方案 A(推荐):给 agent.ListSessions 加内存缓存。缓存的 key 是目录路径 + 文件 mtime 列表,当文件列表或 mtime 无变化时直接返回缓存的 []SessionInfo,避免重复解码。
方案 B:把 migrateLegacySessionsIntoGlobalTopics 改为增量迁移——只在首次运行时执行全量扫描,之后只检查新增文件。当前每次 buildTabController 都跑全量扫描是不必要的。
方案 C:把 findTopicSession 的结果也纳入缓存,或者将 TopicID 索引写入一个独立的索引文件(JSON Map),避免遍历所有文件。
环境 (Environment)
- Reasonix v2 (Go rewrite, main-v2 branch)
- 桌面端 (Wails desktop app)
- Windows 11(复现最明显),macOS / Linux 也存在
相关代码文件
desktop/tabs.go — buildTabController(第 921-1079 行)
desktop/tabs.go — migrateLegacySessionsIntoGlobalTopics(第 2117 行)
desktop/tabs.go — findTopicSession(第 3312 行)
internal/agent/save.go — ListSessions(第 101 行)
补充
已经有 PR #4337 修复了 Windows 上会话切换卡顿的问题,使用了 RLock 替代 Lock 等优化。但此处是全量扫描的问题,与锁类型无关,需要不同的修复策略。
描述 (Description)
在桌面端点击"新建会话"("New Session" 按钮),UI 会卡住 1-3 秒才出现空白会话。创建新会话这种轻量操作本应是瞬间完成的。
Clicking "New Session" in the desktop app causes noticeable UI freezing for 1-3 seconds before the blank session appears. This should be nearly instantaneous.
触发条件 (When it happens)
buildTabController)根因分析 (Root Cause)
问题代码位置
desktop/tabs.go:921-1079—buildTabController方法创建新会话时,串行执行了多次全量会话文件扫描:
①
migrateLegacySessionsIntoGlobalTopics× 2 次migrateLegacySessionsIntoGlobalTopics内部调用agent.ListSessions(dir)(internal/agent/save.go:101),它会:os.ReadDir(dir)— 读取目录所有条目.jsonl文件 →previewSession(full)— 打开文件、JSON 解码、统计用户消息数.meta侧车文件这意味着每次调用都要解码磁盘上每一个会话文件。
②
findTopicSession— 再扫一次findTopicSession(desktop/tabs.go:3312)又做了一次os.ReadDir+ 遍历所有.jsonl→ 读.meta文件 → 比较 TopicID。③
boot.Build— 额外开销boot.Build内部会初始化 MCP 插件、工具注册表、代码检查、事件系统等,也涉及 I/O。汇总
一个简单的"新会话"操作触发了:
migrateLegacySessionsIntoGlobalTopics(全局).jsonl+ 解码migrateLegacySessionsIntoGlobalTopics(项目).jsonl+ 解码boot.BuildfindTopicSession.jsonl+ 读.meta三次全量扫描都是 O(n) 的完整文件 I/O,且全部在 UI 线程路径上。
修复建议 (Proposed Fix)
方案 A(推荐):给
agent.ListSessions加内存缓存。缓存的 key 是目录路径 + 文件 mtime 列表,当文件列表或 mtime 无变化时直接返回缓存的[]SessionInfo,避免重复解码。方案 B:把
migrateLegacySessionsIntoGlobalTopics改为增量迁移——只在首次运行时执行全量扫描,之后只检查新增文件。当前每次buildTabController都跑全量扫描是不必要的。方案 C:把
findTopicSession的结果也纳入缓存,或者将 TopicID 索引写入一个独立的索引文件(JSON Map),避免遍历所有文件。环境 (Environment)
相关代码文件
desktop/tabs.go—buildTabController(第 921-1079 行)desktop/tabs.go—migrateLegacySessionsIntoGlobalTopics(第 2117 行)desktop/tabs.go—findTopicSession(第 3312 行)internal/agent/save.go—ListSessions(第 101 行)补充
已经有 PR #4337 修复了 Windows 上会话切换卡顿的问题,使用了
RLock替代Lock等优化。但此处是全量扫描的问题,与锁类型无关,需要不同的修复策略。