Skip to content

fix(macos): #1550 缓存 LSP 设置页语言摘要并减少主线程布局 - #1551

Open
lin-coco wants to merge 4 commits into
1lck:previewfrom
lin-coco:fix/lsp-settings-cpu
Open

lin-coco wants to merge 4 commits into
1lck:previewfrom
lin-coco:fix/lsp-settings-cpu

Conversation

@lin-coco

@lin-coco lin-coco commented Oct 28, 2026 •

Copy link
Copy Markdown

大型工作区反复进入“设置 › LSP”时,页面会在主线程为语言列表和插件提示重复遍历文件清单,导致高 CPU 和暂时无响应。本 PR 将识别移到工作区持有的后台摘要缓存,并减少设置页渲染中的重复插件发现和分栏尺寸测量;保留语言开关、状态与插件提示行为。

Closes #1550

改动

  • LanguageToolingFeatureModel 持有项目语言使用摘要,以工作区身份、文件快照 UUID 和规范化匹配规则作为键;不遍历或哈希整个文件数组生成键,也不长期缓存文件名。
  • utility 后台任务对已有 URL 清单做一次遍历,支持扩展名、完整文件名、文件名前缀及原大小写语义;每 256 个文件检查取消并释放批次临时对象,不读取文件内容或启动语言服务器。
  • 每个模型保留一份完成缓存和一个当前请求,同请求的页面消费方共享扫描。替换、离页和 reset 及时解除消费方等待,新扫描直接启动;模型持有已取消任务至退出,允许最后批次短暂重叠。工作 ID 和同步取消标记隔离陈旧结果;工作区边界清理缓存,语言服务/索引重置保留仍在显示的摘要。
  • 页面从同一已应用快照捕获 request 和文件清单,任务身份只比较工作区、快照 UUID 和规则;保留既有 LSP 叶子订阅,不扩大设置根树通知。首次加载指示器复用 native.loading;热缓存命中不重复发布相同的 .ready。
  • 每次 LSP 页面渲染只构造一份实时插件快照供列表、提示及重启提示共用;同 ID 冲突保留第一项,保持原查找顺序。设置侧栏的 6 个分组共用一次贡献页发现结果,不跨渲染缓存实时插件状态。
  • 分栏布局增加可选的宿主理想尺寸。只有设置窗口提供已知最小页面尺寸,避开递归的 sizeThatFits(.unspecified);其他宿主保留原路径,实际窗口边界仍决定面板尺寸。
  • 增加默认关闭的 LITHE_SETTINGS_LATENCY_TRACE=1 诊断,按同一个真实鼠标输入 ID 关联接收、选择、就绪内容挂载、窗口更新和主队列可运行阶段;避免复用键盘/旧输入,离开时清理窗口观察者。该诊断不关闭正常 FPS 监视器。
  • 补充匹配等价性、缓存发布/失效、取消/陈旧结果、工作区生命周期、偏好、输入关联及分栏尺寸/保留原生视图回归;增加 lsp-control-center-performance 功能矩阵记录,保持 pending。

没有修改公开插件契约、Rust C ABI、设置存储格式或语言服务器启动协议。关闭设置时偶发约 168% CPU 峰值不在本次范围。

Lithe Review 修复与本轮验证(2026-10-28)

  • 修复替换请求等待旧扫描、消费方取消后仍等待扫描完成的问题;覆盖多消费方共享、最后一方取消、重新进入、reset、模型释放与陈旧结果隔离。
  • 核验工作区通知链:现有 AppModel.objectWillChange 已能刷新 LSP 叶子。补充保持同一完整 SettingsView 挂载、连续应用 README→Go→Java→README 四份快照的测试,settingsChanges 保持 0。
  • 加载提示复用已有 native.loading 中英文词条;原生 AX 测试使用真实翻译资源,在相反父 locale 下直接断言 “Loading…” / “正在加载…”。
  • 代码树整合到已验证的 preview@0627017d,保留原 PR 提交历史。51 项定向 Swift 测试、7 个 suite 通过;最慢改动测试为原生挂载用例,两种语言合计 1.332 秒,未超过 15 秒预算。35 项 i18n 测试通过。
  • 独立原生测试宿主另通过 5 项补充测试,导出 16 张中英文加载、插件提示、空状态及窗口放大/恢复截图;最慢挂载用例 1.577 秒。宿主运行前后 584 个文件/链接条目及内容哈希不变,签名验证通过,临时宿主和窗口已清理。截图和逐项 HTML/JUnit 报告保存在本地工作树 .artifacts/,未上传含本机路径的原始诊断。
  • 独立代码审查未发现阻塞问题;本轮通过测试稳定性、i18n、服务边界、运行时包只读、应用构建安全、Agent Note、213 项功能矩阵及实际 base/head 变更门禁、diff 检查。
  • 本轮未重测 30 万文件工作区性能;下述原始性能与安装包证据保留各自来源,不作为本次修复的重测结果。100 ms 目标和 macOS 13 运行兼容性继续保持待验证。

本轮定向测试命令:

./.agents/skills/write-stable-tests/scripts/test-stability-macos.sh \
  --report .artifacts/test-stability/lsp-review.json --suite-timeout-seconds 1800 -- \
  --build-system native --jobs 3 \
  --filter 'LSPProjectUsageTests|LSPControlCenterPresentationTests|WorkspaceLanguageServerPreferencesTests|AppModelSettingsChangesTests|SettingsPageLatencyTraceTests|LitheSplitPaneCollapseTests|SettingsAppearanceContainerTests'

原始版本已有验证与结果

  • 本地 ARM64 Release 构建成功;Swift 6.4 / Xcode 27.1,最后一次构建约 256 秒。原始构建/性能证据基于 0beb43c936f6 上本 PR 的实现;随后已无冲突同步到 preview@9a78c66790f2,不把早先的运行结果冒充最新基线或固定工具链 CI 结果。
  • 原始版本的 42 项定向 Swift 测试、6 个 suite 通过,测试执行合计约 0.910 秒,均有逐项 HTML/JUnit 计时;测试稳定性检查通过。
  • 300000 个预构造 URL、三种规则场景的算法对照中,耗时降低 82.68%~92.83%;额外 physical footprint 的采样峰值最高约 0.27 MiB。该内存数据只属于算法基准,不代表整个 App 历史峰值。
  • 300007 个真实合成文件工作区:10 次热切页扫描入口命中为 0,稳定停留 69.87 秒的主进程平均 CPU 为 1.70%(此前独立测量)。最终关闭诊断后,另完成 10 次切页、10 次设置窗口关闭重开及窗口放大/缩回,项目、语言列表、Go/PHP 提示与 Java 关闭偏好均正确。
  • 20 轮同类被动测量中,原生输入至窗口更新的中位数/p95 从 139.87/154.61 ms 降至 121.02/132.45 ms;输入至主队列可运行 p95 从 155.13 ms 降至 132.85 ms。点击后不查询 AX,待应用记录里程碑后才验证控件。前后观测未严格控制所有系统负载,不能称严格 A/B。
  • 稳定页单次 AX 查询 p95 约 2.64 ms。此前 183.82 ms 是含工具准备和 AX 观察的端到端值,无法用两个 p95 相减分摊。windowUpdated / mainQueueAvailable 也不等于显示器物理呈现。
  • 最终原生运行前后,测试包 1407 个条目与安装版 1401 个条目的文件/链接及内容哈希保持不变,代码签名验证通过;本任务应用、监督进程和临时夹具已清理,未替换安装版。
  • 独立代码审查未发现需要阻塞合并的问题,独立计时复核未发现汇总与同 ID 原始阶段的关键矛盾。
  • 原始版本在 preview@9a78c667 上通过测试稳定性静态检查、服务边界、运行时包只读、应用构建安全、共享 i18n 检查、212 项功能矩阵、实际 base/head 矩阵变更门禁及 diff 检查。

定向测试命令:

./.agents/skills/write-stable-tests/scripts/test-stability-macos.sh \
  --report .artifacts/test-stability/lsp-layout-latency.json -- \
  --build-system native --jobs 2 \
  --filter 'LSPProjectUsageTests|LSPControlCenterPresentationTests|WorkspaceLanguageServerPreferencesTests|SettingsPageLatencyTraceTests|LitheSplitPaneCollapseTests|SettingsAppearanceContainerTests'

CI 与待验证项

  • 原提交 59ea6353 已通过 Swift 6.3.3 / Xcode 26.6 的 Swift 测试和 ARM64 打包 CI;本次修复 f9d28d91 的固定工具链结果仍待新一轮 CI。PR 保持 Ready;本机使用 Swift 6.4 / Xcode 27.1。
  • 100 ms 是优化目标,当前窗口更新 p95 132.45 ms,尚未达到;不能声称全部性能验收完成。此次缺陷修复的核心证据是主线程不再反复识别全部文件和同一快照复用。
  • 本机 native 构建系统绕过了缺失 Metal Toolchain 的默认构建路径;Rust 宏去符号兼容问题通过本次构建环境处理,未修改仓库构建要求。存在 macOS 26.0/13.0 链接警告,未验证 macOS 13 运行兼容性。
  • 本次是 macOS 页面修复,Windows 运行验证不适用;没有进行固定工具链下的原生 GUI 性能重测。

@ghfind-review ghfind-review Bot added the review: medium ghfind author score; see https://ghfind.com label Oct 28, 2026
@lin-coco
lin-coco marked this pull request as ready for review October 28, 2026 09:41
@lithe-code-review

lithe-code-review Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Lithe Review

结论: ⛔ 存在阻塞问题
范围: preview (9a78c667) ← fix/lsp-settings-cpu · head: 59ea6353

Findings

  1. [P1] 工作区文件快照变化不会触发新的摘要计算
    macos/Sources/Lithe/Application/Features/LSPProjectUsage.swift:132-180

    • 实际影响:LSP 设置页会继续显示旧工作区的语言/插件使用结果。用户在设置页打开后新增、删除或重命名匹配文件时,WorkspaceSnapshot 会产生新的 id,但当前页的 .task(id: request) 只在 SwiftUI 重新计算并取得新 request 时才重新启动;而 LSPProjectUsageModel 自身没有订阅工作区快照更新。当前摘要的 files 是在任务启动时从 model.projectFiles 复制出来的;快照变更后若页面未发生足以重建 LSPControlCenterView 的设置相关发布,页面会一直使用旧摘要,直到离开并重新进入或其他状态触发重绘。
    • 触发条件:进入 LSP 设置页并完成一次摘要扫描,然后在该页保持打开时让工作区观察器发现匹配文件变化。
    • 复现步骤:打开仅含 README 的工作区并进入 LSP 设置,等待完成;在工作区创建 main.go 或 go.mod,等待项目文件快照更新;保持设置页不动并观察语言/插件列表。当前实现没有工作区快照/文件列表到 projectUsage.load 的实时触发链。
    • 来源:本次引入。
    • 修复方向:让 LSP 页面或专属 feature model 订阅工作区快照/文件列表变化,并把当前快照与文件数组作为同一不可变输入重新提交给 LSPProjectUsageModel;快照变化时取消旧任务、启动新任务并拒绝陈旧结果。若设置窗口按设计只接收 settingsChanges,则增加明确的、低频的“工作区文件摘要已变更”通道。
  2. [P1] 后台扫描任务的取消会被串行等待旧任务阻塞
    macos/Sources/Lithe/Application/Features/LSPProjectUsage.swift:145-153

    • 实际影响:替换请求时,新 Task.detached 在调用新扫描器前等待 previous.value。扫描器若在 URL 访问或批次内不能立即观察取消,工作区切换和 LSP 页面重新进入会持续 loading,违背离页取消和有界响应目标。
    • 触发条件:已有扫描进行时 request 变化,且旧扫描未能在下一批次及时终止。
    • 复现步骤:注入忽略取消并在 TaskGate 上等待的 scanner;启动旧 load,再提交新 request。新任务必须等待旧 gate 释放,期间不会调用新 scanner,页面保持 .loading。
    • 来源:本次引入。
    • 修复方向:不要让新任务等待旧任务的 value;用 generation/request ID 做发布保护,让旧任务由 owner 在后台清理。若必须防止重叠,使用明确的 actor/worker 队列并提供有界取消超时,不把旧任务的不可控完成时间放进新页面任务的关键路径。
  3. [P2] 新增诊断路径包含未统一 i18n 的用户可见文案
    macos/Sources/Lithe/Views/Language/LSPControlCenterView.swift:51

    • 实际影响:应用语言与系统语言不一致时,首次摘要计算期间的“Loading…”可能按系统默认语言显示,违反应用语言统一策略。
    • 触发条件:LSP 设置页首次加载或摘要 request 变化时显示 ProgressView。
    • 复现步骤:将 Lithe 应用语言设为中文、系统语言设为英文,进入 LSP 设置页,在扫描完成前观察加载控件的无障碍名称。
    • 来源:本次引入。
    • 修复方向:在 shared/i18n/messages.json、en-US.json、zh-CN.json 增加加载词条,生成本地化结果,并使用现有统一语言接口提供 ProgressView 的 accessibility label。提交前提供两项本地化检查结果。

Scope Check

  • 关联 Issue 或需求是否完成:Issue [Bug] macOS 大型工作区反复进入 LSP 设置页触发高 CPU 和界面卡顿 #1550 的核心目标已部分覆盖;摘要扫描、缓存和展示路径已加入,但工作区变化后的失效链与替换任务响应性存在缺口。
  • 已完成范围:新增 LSPProjectUsageScanner 与 workspace/request keyed cache;LSP 页面使用摘要;设置侧栏贡献页计算合并;分栏布局支持已知 ideal size;延迟诊断和针对性测试/功能矩阵记录已加入。
  • 未完成范围:未能从当前 diff 证明工作区文件快照变化会自动使已显示摘要重新计算;固定 Swift 6.3.3/Xcode 26.6 CI 结果在给定上下文中尚未提供。
  • 是否复用了已有上游能力,或对自研边界给出充分证据:本次主要是 Lithe 自有摘要编排,没有新增语言服务领域事实;未见需要另报并行上游实现问题的证据。
  • 每个 Finding 是否由本次变更引入或加剧:是,均指向新增摘要模型/页面 task 及新增文案。
  • 是否属于本次变更职责范围:是。

Suggestions

  1. 修复快照更新触发链,并补充“页面保持打开、文件变化后列表更新”的确定性测试。
  2. 修复替换任务调度,增加旧任务忽略取消时新 request 仍能结束或明确失败/超时的测试;现有替换测试在释放 gate 后才完成,不能证明有界响应。
  3. 将 Loading… 纳入共享 i18n,并给出 verify-app-language-localization 与 generate-i18n --check 结果。
  4. PR 上下文没有提供受影响界面的截图或前后对比。请补充 LSP 设置页首次加载、热缓存、空状态以及设置侧栏/分栏尺寸变化截图,当前视觉效果未验证。

Summary

摘要扫描把昂贵的文件匹配移出 SwiftUI 渲染路径,并保留 workspace/snapshot/rule 身份和陈旧结果保护,方向与问题目标一致。当前缓存生命周期与请求替换的任务编排仍有可复现的状态一致性和响应性缺口;新增 accessibility 文案也没有走共享 i18n。修复后应重新核对快照更新、取消清理和固定工具链测试证据。

Verification

  • 实际检查的文件、提交和调用链:给定的 13 个文件;git diff 9a78c66790f285a78a2229880a1b653c346d9fe8...59ea6353ef79f1653c756d1f286ce480bc18e0e0;重点阅读 LSPProjectUsageModel、LanguageToolingFeatureModel、LSPControlCenterView、SettingsView、LitheSplitPaneView、延迟探针及新增测试,并沿 AppModel.workspaceSnapshotID、WorkspaceFeatureModel.appliedSnapshot 和语言工具模型调用方追踪。
  • 已提供的测试或 CI 结果:PR 上下文声称 Swift 6.4/Xcode 27.1 ARM64 Release 构建、42 项定向测试、算法/合成工作区测量及静态检查通过;固定工具链 Swift 6.3.3/Xcode 26.6 正式编译/测试尚无通过记录。以上均为用户提供上下文,本次未执行。
  • 本次未运行的测试:未运行仓库代码、测试、构建、应用、脚本或网络检查。
  • 仍存在的非阻塞风险或平台验证缺口:100ms 性能目标尚未达到;macOS 13 运行兼容性及固定工具链验证缺口;未提供 UI 截图,视觉效果未验证;Windows 运行验证按 PR 范围不适用。

新请求直接启动,消费方离页、替换和重置及时解除等待;模型持有退出中的
已取消扫描,使用唯一工作 ID 拒绝陈旧结果,并正确处理共享消费方和释放。

从同一已应用快照捕获 request 和文件清单,保留现有 LSP 叶子订阅与热缓存。
加载无障碍名称复用 native.loading,原生测试注入真实语言资源并验证中英文 AX。
补齐并发、生命周期、持续快照刷新和原生布局测试,更新所有权笔记及功能矩阵。

同时整合已验证的 preview@0627017d,保留 PR 1lck#1551 原提交 59ea635 的历史。
验证:51 项 Swift 测试、35 项 i18n 测试、5 项补充原生测试和相关静态门禁通过。
本机 Swift 6.4/Xcode 27.1;固定工具链 CI、100ms 目标和 macOS13 验收仍待完成。
1lck
1lck previously approved these changes Oct 29, 2026

@1lck 1lck left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复核当前 head f9d28d9:未发现需要阻塞合并的问题。

旧评审针对 59ea635:最新实现已移除新扫描等待旧任务结束的依赖,使用工作 ID、消费者取消标记和缓存身份拒绝过期结果;补充保持同一 SettingsView 挂载时 README→Go→Java→README 的快照回归;加载提示复用 native.loading,并覆盖应用语言与父 locale 相反的场景。当前 Swift CI 已通过。

本次为静态代码与已有 CI 结果复核,未独立重跑测试或 GUI。100ms 性能目标、macOS 13 运行兼容性和固定工具链下 GUI 性能重测仍是已披露的验证边界。建议同步最新 preview 后由必需检查完成合并门禁;旧提交的阻塞评论不应直接作为当前 head 的结论。

@1lck 1lck left a comment •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复核功能修订 f9d28d9,并检查同步 preview 后的 a5c4567:未发现新的阻塞性代码问题。

取消与过期结果隔离、保持 SettingsView 挂载的快照变化回归、native.loading 本地化已覆盖原评审问题。本次还修正两处因基线脚本迁移失效的 Agent Note 命令路径;本地 167 篇笔记检查及最新 Verify Agent Notes CI 通过。

功能判断依据静态审查及此前已有 CI;未独立重跑此 PR 的 GUI/性能测试。100ms 性能目标、macOS 13 实机兼容性仍待验证。当前仍应遵守仓库所需的独立审批和最新 CI 门禁,本次批准不代表可以绕过这些要求。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: medium ghfind author score; see https://ghfind.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants