Skip to content

perf(bridge): reduce Mutex<Translator> hold time — release lock before client.request() #108

Description

@bug-ops

Description

All 13 LSP tool handlers in mcp/server.rs hold Mutex<Translator> for the full duration of the LSP round-trip — including client.request(...) with a 30 s timeout. This serializes all concurrent tool calls: worst-case p99 latency for a hover request while get_diagnostics is in-flight is 60 s; under 13 concurrent calls it reaches 390 s.

Phase 1 (issue #104) extracts NotificationCache to eliminate pump starvation. This issue tracks the remaining lock granularity problem: the translator lock is still held across the entire LSP network wait.

Reproduction Steps

  1. Issue two concurrent MCP tool calls (e.g. get_diagnostics + get_hover) against a slow or cold rust-analyzer workspace
  2. Observe that the second call queues behind the first for up to 30 s

Expected Behavior

Concurrent tool calls execute in parallel. Lock is held only for the short preparation phase (~2 ms: path validation + ensure_open), not for the LSP round-trip.

Actual Behavior

Lock held for up to 30 s (LSP timeout) serializing all 13 handlers.

Scope

Mechanical refactor, ~150–250 LOC, no architectural rewrite.

Required changes

  1. bridge/translator.rs — convert lsp_clients, workspace_roots, extension_map to Arc<...> (read-only after serve()); wrap document_tracker in Arc<Mutex<DocumentTracker>>
  2. bridge/translator.rs — split each handle_* into a prepare phase (acquire document_tracker lock for ensure_open, then release) and an execute phase (client.request(...) with no lock held)
  3. mcp/handlers.rs — introduce BridgeContext holding independently-lockable components; replace Arc<Mutex<Translator>>
  4. mcp/server.rs — 13 LSP-roundtrip handlers: acquire document_tracker lock only for ensure_open, release before client.request()
  5. lib.rs — construct and pass Arc<BridgeContext> instead of Arc<Mutex<Translator>>

State captured before lock release (per correctness analysis)

  • LspClient clone (Arc+channel, independent of translator mutex)
  • Uri from ensure_open (owned value)
  • All LSP request params (built from owned values)

Invariants that must hold

  • ensure_open must complete under document_tracker lock to prevent duplicate didOpen from concurrent calls on the same path
  • After lock release, concurrent client.request() calls on the same LspClient are safe by design (each uses its own oneshot channel keyed by unique request ID)

What NOT to do

Trigger

Escalate to active development when field measurement or integration tests show >1 s p99 added latency under concurrent tool calls. Until then: file and defer.

Blocked by

#104

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium: suboptimal behavior, minor inconsistencyenhancementNew feature or requestmcpls-coremcpls-core crate changes

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions