Repository navigation
perf(lsp): 修复补全/跳转卡顿与 pom 修改后的全量重导,调优 jdtls JVM - #1573
Merged
Merged
Conversation
…n actor The engine copied the whole client state for every request, document change, and server message while holding the session locks. That state holds the full text of every open document, so a jdtls project import, which publishes thousands of messages, kept completion and navigation waiting behind the copies. Open document text is now shared (Arc<str>, still a plain JSON string), and each server message is parsed once, outside the state lock, instead of up to three times. macOS drained Core's event queue on the main actor every 10 ms while requests were pending, waiting on the same session lock. The drain now runs off the main actor; a send waits for a drain already in flight, so a drain still never returns a reply before its operation is registered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The JDT -data fingerprint no longer includes build-file mtime or size. jdtls already detects pom and Gradle script edits itself (MavenProjectImporter.needsMavenUpdate, GradleProjectImporter. checkGradlePersistence), so switching to a fresh -data directory on every edit only turned an incremental update into a full re-import. The Gradle wrapper distribution stays part of the key because jdtls does not check it. jdtls now launches with the GC and logging defaults vscode-java and the upstream CI use (ParallelGC with GCTimeRatio=4 and AdaptiveSizePolicyWeight=90, -Xlog:disable, DetectVMInstallationsJob disabled). A plugin option with the same key replaces each default, and a plugin collector choice replaces ParallelGC. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1lck
marked this pull request as ready for review
October 29, 2026 06:19
Lithe Review结论: 未发现需要阻塞合并的问题。以下均为 P2 非阻塞建议。 Findings
Scope Check
Suggestions
Summary文本共享与解析复用符合现有 Core 边界。主要建议集中在 wrapper 解析正确性、并发回归测试的有效性,以及行为记录同步;尚无充分证据量化实际性能收益。 Verification
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
背景
Java 项目里补全、跳转时不时卡一下或"失效一会",jdtls 导入大项目期间界面掉帧。对照 openidea(同样基于 jdtls)检查后,发现我们自己的 LSP 编排层存在可修复的重复复制与主线程等待;这些代码问题尚不能解释所有真实项目中的卡顿。本 PR 包含三部分:会话热路径(1–5)、JDT
-data工作区 key(6–8)、jdtls JVM 默认参数(9)。改动点
Rust Core(
rust/lithe-core/src/lsp/interface/)state.client.clone(),而LspClientState.open_documents带着所有打开文档的完整文本(String)。jdtls 导入期间会连续发出成千上万条 progress / 诊断消息,每条都触发一次全文复制。同一时间outbound_order锁一直被持有,补全请求只能排在后面,甚至超时。LspClientDocument.text由String改为Arc<str>,clone 只增加引用计数;给 serde 开启rc特性,JSON 仍是普通字符串,契约和 fixture 不变。outbound_order锁后又解析 1 次(本地工作区下内容完全相同);reducer 在状态锁内再解析 1 次。client_apply_server_message_for_plugin改为直接接收解析好的&Value。ClientApplyServerMessageRequest只剩无状态 reducer 测试在用,标记为#[cfg(test)]。diagnostics_addressed_to_open_document直接返回改写后的Value。macOS(
LanguageServerSession.swift)@MainActor上,有请求在等结果时每 10ms、空闲时每 50ms 同步调用一次lsp.pollEvents,包括 C ABI 调用、JSON 编解码,并等待被读线程持有的会话锁。导入期间这直接表现为打字、滚动掉帧。sendsInFlight > 0)时不开始 drain。send写请求前,先等待正在进行的 drain 结束。这样后台 drain 返回的事件里,不可能有新请求的回复,回复只会在操作注册之后的下一轮被取到。JDT 工作区 key 不再包含构建文件的修改时间(Core / macOS / Windows)
-data目录的 fingerprint 包含pom.xml/build.gradle(.kts)的修改时间和大小。用户每改一次 pom(哪怕只加一个依赖),key 就会变化,jdtls 拿到一个全新的空-data目录,整个工作区重新导入、重建索引,期间补全和跳转全部不可用。MavenProjectImporter.needsMavenUpdate比较 pom 时间戳和上次保存的工作区状态;Gradle 由GradleProjectImporter.checkGradlePersistence比较*.gradle(.kts)的修改时间。我们配置了updateBuildConfiguration: "automatic"。换-data目录不仅多余,还把增量更新变成了全量重建。JdtBuildFileObservation只保留path。fingerprint 改为build=<路径>|modules=…|jdtls=…,不再出现 mtime 或 size。gradle/wrapper/gradle-wrapper.properties。升级 Gradle 版本后,旧的持久化模型可能与新版本不兼容。gradleWrapperDistribution。Windows 端读取distributionUrl(支持=/:分隔、#/!注释、反斜杠转义),存在时在 fingerprint 末尾追加|gradle=<url>。空值返回 InvalidRequest。pom.xml、build.gradle、build.gradle.kts,不发送 wrapper 版本,这与改动前一致,契约里有说明。invalidBuildFileMetadata和对应的 i18n 词条native.couldNotReadJavaBuildFileMetadataFor一起删除(Localizable.strings只有删除)。shared/contracts/rust-core-api.md、fixtureshared/fixtures/lsp/jdt-workspace-fingerprint-v1.json(新增 wrapper 版本用例)和 Agent Note2026-10-11-gradle-project-import-settings.md同步更新。jdtls 的 JVM 默认参数(
rust/lithe-core/src/lsp/languages/jdt.rs)java启动两条路径都加:-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90:堆上限是 2 GB,默认的 G1 会长期占住接近上限的堆;换成这组参数后,导入结束时堆会收缩,把内存还给系统。-Xlog:disable:JVM 统一日志的告警默认写到 stdout,而 stdout 正是 LSP 通道。-DDetectVMInstallationsJob.disabled=true:宿主已经通过java.configuration.runtimes传入了 JDK,jdtls 启动时不必再扫描本机 JVM。-XX:选项按名字算,所有-Xlog算一个键,其余按=之前的部分),用插件的值;插件选了任何收集器(-XX:±Use*GC)时,去掉默认的 ParallelGC。与默认值完全相同的参数视为宿主所有,重复适配的结果不变。-Daether.dependencyCollector.impl=bf会改变 Maven 解析策略,且没有核实 m2e 是否读取它;-Dsun.zip.disableMemoryMapping=true在 JDK 9+ 已经无效;-Xms保持 256m。另外确认了-Dlog.protocol=true在 jdtls 源码中没有任何读取方,是无效参数,删掉它也没有收益,所以保持不动。2026-10-09-jdtls-heap-ceiling-and-duplicate-maven-coordinates.md(决策第 5 条)和2026-10-14-declarative-plugin-settings.md。跨平台功能矩阵
shared/platform-feature-matrix/features/lsp-lifecycle.json:两端补充证据路径、验证动作(改 pom 后-data目录不变、wrapper 版本变化会切换目录、JVM 参数和导入后内存回落、macOS 导入期间不掉帧)和说明。两端验证状态仍是pending。测试
client_core_reduction_shares_open_document_text:reduce 一条服务端消息后,文档文本与原状态仍是同一份(Arc::ptr_eq),且序列化 / 反序列化后仍是普通字符串。gradle-wrapper-version-selects-the-state。java_start_lets_plugin_jvm_options_replace_the_tuning_defaults:插件指定 G1、GCTimeRatio 和-Xlog时替换对应的默认值,未覆盖的默认值保留,重复适配结果稳定。两条启动路径的完整参数列表测试同步更新。aHeldEventDrainNeitherBlocksTheMainActorNorOvertakesARequest:fake core 把 drain 挂在后台。MacJdtWorkspaceStateTests:改 pom 内容和修改时间后,构建文件观察结果不变。editing_a_root_build_file_keeps_the_workspace_observations和the_gradle_wrapper_version_selects_the_workspace_state。仓库 Skill 自审(2026-10-29)
结论:未发现需要阻塞合并的问题。 已按
lithe-code-review、develop-lithe、write-stable-tests和agent-notes审查本 PR 的直接调用链、并发顺序、共享契约、测试及决策记录。d3072abd4←e57008986;相较已通过完整 CI 的51d3021bf,本轮仅同步 README/截图更新,产品实现未再修改。🤖 Generated with Claude Code
真实 macOS 应用验收补充(2026-10-29)
在包含两项 PR 的
145ddc33d预览包中实际操作 RuoYi-Vue(13db1fc,266 个 Java 源文件):打开项目自动出现运行配置并默认选中 ruoyi-admin;Java 导入完成;输入 SpringApplication.r 得到 run 方法补全,F12 能进入 Spring 依赖源码。独立 Hello.java 样例经界面完成编译、启动、输出和停止,未留下样例进程。完整退出并启动新进程后,使用 Core 文档转换准备的已保存 build.provider 测试任务与自动检测的 8 个 Java/服务入口同时显示,无需手动识别,验证了本次重启回归修复。此项未覆盖通过 UI 保存任务。操作前后 1,394 个 bundle 文件哈希一致;基线在初次启动后采集,因此不将此结果描述为完整启动期间的哈希验证。验收后应用与 Java 子进程全部退出。
限制:未运行 Windows 虚拟机,未做前后版本性能基准,也未启动依赖数据库/Redis 的若依服务。真实界面还暴露出既有检测器把继承 Spring Boot 插件的库模块列作服务的问题;该检测器不在两项 PR 的改动中,保留后续优化项。自动识别未创建 generated.json、requirements.json、project.json;项目仍会出现原有的 local.json。不能据此声称所有 Windows 体验问题已解决。