Skip to content

refactor(lsp): Phase 0 latency and mutex containment#3969

Open
rossirpaulo wants to merge 4 commits into
canaryfrom
paulo/lsp-latency-mutex-fix
Open

refactor(lsp): Phase 0 latency and mutex containment#3969
rossirpaulo wants to merge 4 commits into
canaryfrom
paulo/lsp-latency-mutex-fix

Conversation

@rossirpaulo

@rossirpaulo rossirpaulo commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements Phase 0 of the LSP latency and mutex containment design (docs/design/lsp-latency-and-mutex-fix.md):

  • Source revision and owned candidates — monotonic SourceRevision, detached CompilationOutcome / DiagnosticCandidate / EngineCandidate, and revision-conditional engine commits so superseded rebuilds change no runtime state.
  • Coherent runtime state — unified RuntimeState with collection epoch fencing, registry leases, and atomic run snapshots (prepare_function_run).
  • Durable versioned diagnosticsDiagnosticsFence with compare-and-clear dirty tracking, stale-candidate discard, and bounded busy retry on native.
  • Bounded request waits and typed LSP errors — 1s request-lane deadline maps to ContentModified / RequestFailed / InternalError instead of generic -32001.
  • Position encoding negotiation (C1) — new PositionCodec for UTF-8/UTF-16 at the LSP boundary.
  • Debounced native tails (B3) — single-flight engine rebuild and diagnostics/project-update debouncing.
  • Salsa memoization (B1)file_annotations and semantic_tokens as tracked queries.
  • Deferred profiling activation — winning engine commit only activates profiling lifecycle.

Draft for early review and comparison with an alternate approach. Further performance work (snapshot reads off the source gate, startup prewarm) is planned after measurement.

Test plan

  • cargo test --lib -p bex_project
  • cargo test --lib (workspace; SDK harness failures pre-existing)
  • cargo check --target wasm32-unknown-unknown -p bridge_wasm
  • cargo clippy (pre-commit hook)
  • Extension smoke test against release baml-cli on sandbox project
  • Compare startup -32803 behavior vs alternate PR (later)
  • Rerun /tmp/baml-inlay-repro harness after merge candidate selection

Summary by CodeRabbit

  • Improvements
    • Enhanced language-server responsiveness with debounced refresh of diagnostics and rebuild work while editing.
    • Improved correctness of positions, diagnostics, semantic tokens, inlay hints, code actions, and navigation across UTF-8/UTF-16 clients.
    • More reliable run launching and cancellation so actions target the correct project state; improved LSP error-code handling and added support for common notifications.
    • Smoother profiling lifecycle behavior for inactive/discarded runs.
  • Documentation
    • Expanded developer setup, Rust workspace guidance, and updated LSP testing instructions/workflows.

Implement the LSP server rebuild pipeline from docs/design/lsp-latency-and-mutex-fix.md:
source revision and owned candidates, revision-conditional engine commits,
versioned diagnostics fencing, bounded request waits with typed LSP errors,
negotiated position encoding, debounced native tails, and coherent run/test
snapshots. Memoize inlay hints and semantic tokens as Salsa queries.
@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Jul 10, 2026 10:44pm
promptfiddle Ready Ready Preview, Comment Jul 10, 2026 10:44pm
promptfiddle2 Ready Ready Preview, Comment Jul 10, 2026 10:44pm

Request Review

@github-actions

Copy link
Copy Markdown

⏭️ Performance benchmarks were skipped

Perf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to canary/main.

To run them on this PR, do any of the following, then push a commit (or re-run CI):

  • Add RUN_CODSPEED=1 to the PR description, or
  • Include run-perf or /perf in the PR title or any commit message.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 248fafe1-0dce-4f2c-adaa-7590497c2d1b

📥 Commits

Reviewing files that changed from the base of the PR and between 7fc1c18 and 97cda15.

📒 Files selected for processing (2)
  • baml_language/crates/bex_project/src/bex_lsp/mod.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/mod.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • baml_language/crates/bex_project/src/bex_lsp/mod.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/mod.rs

📝 Walkthrough

Walkthrough

This change introduces revision-gated project state, debounced and fenced LSP refreshes, negotiated position encoding, Salsa-tracked editor queries, generation-aware run cancellation, deferred profiling activation, and updated development and testing documentation.

Changes

LSP runtime modernization

Layer / File(s) Summary
Developer workflow documentation
CONTRIBUTING.md, README-DEV.md, baml_language/TEST_INSTRUCTIONS.md, docs/design/*
Adds setup, Rust workspace, testing, and implementation design guidance.
Salsa-backed editor queries
baml_language/crates/baml_lsp2_actions/*, baml_language/crates/baml_lsp2_actions_tests/*, baml_language/crates/baml_cli/src/paint.rs
Makes annotations and semantic tokens tracked queries and updates consumers to clone cached values.
Revision-gated project runtime
baml_language/crates/bex_project/src/project.rs, baml_language/crates/bex_engine/src/lib.rs, baml_language/crates/bex_project/src/bex_lsp/*, baml_language/crates/baml_lsp_server/src/playground_server.rs
Adds source revisions, owned candidates, conditional engine commits, run snapshots, generation-aware cancellation, and deferred profiling activation.
LSP error and position contracts
baml_language/crates/bex_project/src/bex_lsp/position_codec.rs, .../request.rs, .../diagnostics.rs
Centralizes error mapping and converts LSP positions, ranges, diagnostics, symbols, formatting edits, and semantic tokens through negotiated encodings.
Refresh, publication, and test fencing
baml_language/crates/bex_project/src/bex_lsp/multi_project/*, baml_language/crates/bex_project/Cargo.toml
Adds versioned overlays, bounded reads, diagnostic fences, debounced rebuilds, stale-result suppression, registry leases, and notification handling.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LSPClient
  participant MultiProjectLSP
  participant BexProject
  participant PositionCodec
  participant PlaygroundServer

  LSPClient->>MultiProjectLSP: initialize(position encodings)
  MultiProjectLSP->>PositionCodec: negotiate encoding
  MultiProjectLSP-->>LSPClient: capabilities with negotiated encoding
  LSPClient->>MultiProjectLSP: document change or request
  MultiProjectLSP->>BexProject: mutate sources or read current revision
  BexProject-->>MultiProjectLSP: revision-tagged candidate or snapshot
  MultiProjectLSP->>PositionCodec: convert LSP positions and ranges
  MultiProjectLSP-->>LSPClient: diagnostics, symbols, tokens, or edits
  PlaygroundServer->>BexProject: prepare_function_run(...)
  BexProject-->>PlaygroundServer: generation-pinned engine
Loading

Possibly related PRs

Poem

A rabbit hops through revisions bright,
While tokens cache and ranges align right.
Engines wait till snapshots agree,
Diagnostics fence stale work free.
“Nibble the code—test green!” says me.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main focus on Phase 0 LSP latency and mutex containment work.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch paulo/lsp-latency-mutex-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

Binary size checks failed

1 violations · ✅ 6 passed

⚠️ Please fix the size gate issues or acknowledge them by updating baselines.

Artifact Platform File Gzip Gated on Baseline Delta Status
baml-cli Linux 🔒 22.0 MB 9.4 MB file 21.5 MB +518.2 KB (+2.4%) OK
packed-program Linux 🔒 16.0 MB 6.7 MB file 15.6 MB +361.9 KB (+2.3%) OK
baml-cli macOS 🔒 17.0 MB 8.2 MB file 16.6 MB +414.9 KB (+2.5%) OK
packed-program macOS 🔒 12.4 MB 5.9 MB file 12.1 MB +281.9 KB (+2.3%) OK
baml-cli Windows 🔒 18.5 MB 8.4 MB file 18.1 MB +432.6 KB (+2.4%) OK
packed-program Windows 🔒 13.3 MB 6.0 MB file 13.0 MB +294.8 KB (+2.3%) OK
bridge_wasm WASM 14.8 MB 🔒 4.2 MB gzip 4.1 MB +129.3 KB (+3.2%) FAIL

🔒 = the size this artifact is GATED on (ceiling + delta). Binaries gate on file size (installed binary); WASM gates on gzip (download size). The other size is shown for information only.

Details & how to fix

Violations:

  • bridge_wasm (WASM) gzip_bytes: 4.2 MB exceeds limit of 4.2 MB (exceeded by +7.5 KB, policy: max_gzip_bytes)
  • bridge_wasm (WASM) gzip_delta_pct: +3.2% exceeds limit of 3.0% (exceeded by +0.2pp, policy: max_delta_pct)

Add/update baselines:

.ci/size-gate/wasm32-unknown-unknown.toml:

[artifacts.bridge_wasm]
file_bytes = 14763593
gzip_bytes = 4188237

Generated by cargo size-gate · workflow run

- DiagnosticsFence::admit now compares candidates against the live
  source revision (read under the fence lock), so a superseded rebuild's
  diagnostics or a candidate racing mark_dirty can never regress markers
  a newer revision already published (design 0B "Ready(stale revision)
  -> discard"; acceptance: no v7 publication after v8 arrives).
- Add a commit_engine_if_current_with_hook seam and port the
  commit-boundary rendezvous test: an edit placed exactly against the
  conditional commit is serialized after it and immediately makes the
  installed engine non-current.
- Extract classify_request_timeout and cover the request lane's timeout
  classification (revision moved -> ContentModified-stale; unchanged ->
  congestion) with channel-sequenced tests, no sleep margins.
- Correct false lock/revision comments: run_cfgs is a leaf mutex taken
  under the source gate in prepare_function_run; the source revision
  advances on every applied batch; playground cancel is best-effort and
  cannot reach a run pinned to a superseded engine (D8 registration
  deferred).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VEMxti5WQ8bCgJUuX9AQvo
@rossirpaulo

Copy link
Copy Markdown
Contributor Author

Pushed 7fc1c188 addressing the pre-merge findings from the deep comparative review of this PR vs #3961:

Correctness fixes

  • DiagnosticsFence::admit now judges staleness against the authoritative source revision (read under the fence lock via the new lock-free BexProject::current_revision()), closing both verified defects in one check: a superseded rebuild's late diagnostics can no longer republish over a newer revision's markers, and a candidate racing mark_dirty (the window between mutate_sources returning and the dirty mark landing) is discarded. This makes the design's "Ready(stale revision) → discard" and the 0B acceptance item ("complete v7 diagnostics after v8 arrives and observe no v7 publication") hold unconditionally. New unit test pins both interleavings.
  • Corrected three comments that overclaimed invariants: the run_cfgs lock-order note (it is a leaf mutex taken under the source gate in prepare_function_run's fast path), the module header's "revision advances only when text actually changed" (it advances on every applied batch), and the playground cancel comment (cancel is best-effort; a run pinned to a superseded engine is unreachable until D8 run registration lands).

Test hardening (ported from #3961's strongest asset)

  • Added the commit_engine_if_current_with_hook rendezvous seam and edit_at_commit_boundary_never_leaves_a_false_current_engine: an edit placed exactly against the conditional commit — no sleeps, no scheduler timing assumptions.
  • Extracted classify_request_timeout and added deterministic coverage for the previously untested 1s bounded-wait classification (gate held with no mutation → revision_changed: false → RequestFailed; revision moved → revision_changed: true → ContentModified). The writer releases the gate only after the assertion, so no sleep margins.

Verification

  • cargo test --lib -p bex_project 39/39, -p baml_lsp_server 29/29 (post-rebase onto the canary merge), cargo clippy --all-targets -- -D warnings clean, cargo check --target wasm32-unknown-unknown -p bridge_wasm clean, pre-commit hooks pass.
  • Latency harness rerun (/tmp/baml-inlay-repro, 400 functions, release build of this branch):
Metric 0.14.1 (regressed) this branch
Typing inlayHint p50 / p95 313.6 / 379.4 ms 0.8 / 51.9 ms
Post-pause -32001 failures 46–57 per feature/window 0
Unanswered requests 144–160 0
Rebuild-window inlay/hover/completion mass -32001 218/218 each, 0 errors
Fifth queued idle inlayHint 317.0 ms 12.9 ms
Final-edit diagnostics ~59.5 ms 191.2 ms (intentional 150 ms trailing debounce)
workspace/inlayHint/refresh storms per 10-edit burst 0

Explicit $/cancelRequest is accepted and the request completes normally — the Phase 0 no-op semantics (full cancellation ownership is Phase 1 scope).

Remaining known trade-off for reviewers: D7 fail-closed run admission means playground runs return projectNotReady during post-edit rebuild windows while the unchanged client UI still shows "using last successful build" — a client-side gating follow-up is planned as a separate TS PR.

Semantic conflict fix: canary's #3973 added list_tests_with_metadata(db)
in build_project_update, where this branch replaced the raw db binding
with the source-guard read. The tests listing now rides the same guard
as the functions listing, so the payload stays one coherent snapshot.
@rossirpaulo

Copy link
Copy Markdown
Contributor Author

CI note: the failures on 7fc1c188 were a semantic (textually clean) merge conflict with canary, not a defect in this branch — canary's #3973 added list_tests_with_metadata(db) inside build_project_update, where this branch replaced the raw db binding with the bounded source-guard read. Pushed 97cda15a merging latest canary and adapting that hunk: the tests listing now reads under the same source guard as the functions listing, so the ProjectUpdate payload stays one coherent snapshot. All local gates re-verified post-merge: bex_project 39/39, baml_lsp_server 29/29, workspace cargo check --lib clean, clippy -D warnings clean, wasm check clean.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@baml_language/crates/baml_lsp2_actions_tests/src/runner.rs`:
- Line 7: The annotation collection currently bypasses Salsa memoization because
file_annotations is a plain function. Update file_annotations in annotations.rs
to be the tracked returns(ref) query, or delegate to that tracked query and
clone only at the ownership boundary; ensure the production LSP handlers and
runner use this tracked path rather than recomputing annotation aggregation.

In `@baml_language/TEST_INSTRUCTIONS.md`:
- Around line 71-76: Add the required `cargo test --lib` command to the “Verify
all tests pass” section, alongside the existing Rust test commands, so the
documented workflow covers library tests for Rust changes.
- Line 111: Correct the environment variable reference in the “DO NOT EDIT”
instruction to use the case-sensitive name UPDATE_EXPECT=1, matching the
commands documented above.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c938a392-17fe-414e-9d51-9b4113bc0ea4

📥 Commits

Reviewing files that changed from the base of the PR and between 8349784 and 7fc1c18.

⛔ Files ignored due to path filters (1)
  • baml_language/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • CONTRIBUTING.md
  • README-DEV.md
  • baml_language/TEST_INSTRUCTIONS.md
  • baml_language/crates/baml_cli/src/paint.rs
  • baml_language/crates/baml_lsp2_actions/src/annotations.rs
  • baml_language/crates/baml_lsp2_actions/src/lib.rs
  • baml_language/crates/baml_lsp2_actions/src/tokens.rs
  • baml_language/crates/baml_lsp2_actions_tests/src/runner.rs
  • baml_language/crates/baml_lsp_server/src/playground_server.rs
  • baml_language/crates/bex_engine/src/lib.rs
  • baml_language/crates/bex_project/Cargo.toml
  • baml_language/crates/bex_project/src/bex_lsp/mod.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/diagnostics.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/mod.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/notification.rs
  • baml_language/crates/bex_project/src/bex_lsp/multi_project/request.rs
  • baml_language/crates/bex_project/src/bex_lsp/position_codec.rs
  • baml_language/crates/bex_project/src/bex_lsp/request.rs
  • baml_language/crates/bex_project/src/lib.rs
  • baml_language/crates/bex_project/src/project.rs
  • docs/design/lsp-latency-and-mutex-fix.md

use baml_compiler_diagnostics::{RenderConfig, render_diagnostic};
use baml_lsp2_actions::{
annotations::{AnnotationKind, InlineAnnotation, annotations},
annotations::{AnnotationKind, InlineAnnotation, file_annotations},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Keep inlay-hint collection on the Salsa-tracked query path.

The supplied baml_lsp2_actions/src/annotations.rs defines file_annotations as a plain pub fn, so this change appears to bypass the B1 memoized query. The production LSP handlers also call this API while claiming the results are Salsa-memoized, which can re-run annotation aggregation on every request.

Make file_annotations the tracked returns(ref) query, or delegate to the tracked query and clone only at the ownership boundary.

Also applies to: 168-170

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/baml_lsp2_actions_tests/src/runner.rs` at line 7, The
annotation collection currently bypasses Salsa memoization because
file_annotations is a plain function. Update file_annotations in annotations.rs
to be the tracked returns(ref) query, or delegate to that tracked query and
clone only at the ownership boundary; ensure the production LSP handlers and
runner use this tracked path rather than recomputing annotation aggregation.

Comment on lines 71 to +76
### 7. Verify all tests pass

```bash
# Run all tests (can skip slow parser_stress with --skip parser_stress)
cargo test --package baml_tests -- --skip parser_stress
cargo test --package lsp_actions_tests
cargo test --package baml_lsp2_actions_tests

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Include the required library test command.

This verification block omits cargo test --lib. Because this PR changes Rust code, add that command to the documented verification workflow.

As per coding guidelines, always run cargo test --lib when Rust code changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/TEST_INSTRUCTIONS.md` around lines 71 - 76, Add the required
`cargo test --lib` command to the “Verify all tests pass” section, alongside the
existing Rust test commands, so the documented workflow covers library tests for
Rust changes.

Source: Coding guidelines



DO NOT EDIT the diagnostics manually in lsp_actions_tests. Use update_expect=1
DO NOT EDIT the diagnostics manually in baml_lsp2_actions_tests. Use update_expect=1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the correct environment variable name.

update_expect=1 is lowercase here, but the commands above use the case-sensitive variable UPDATE_EXPECT=1.

Proposed fix
-DO NOT EDIT the diagnostics manually in baml_lsp2_actions_tests. Use update_expect=1
+DO NOT EDIT the diagnostics manually in baml_lsp2_actions_tests. Use UPDATE_EXPECT=1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DO NOT EDIT the diagnostics manually in baml_lsp2_actions_tests. Use update_expect=1
DO NOT EDIT the diagnostics manually in baml_lsp2_actions_tests. Use UPDATE_EXPECT=1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/TEST_INSTRUCTIONS.md` at line 111, Correct the environment
variable reference in the “DO NOT EDIT” instruction to use the case-sensitive
name UPDATE_EXPECT=1, matching the commands documented above.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant