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
- Issue two concurrent MCP tool calls (e.g.
get_diagnostics + get_hover) against a slow or cold rust-analyzer workspace
- 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
bridge/translator.rs — convert lsp_clients, workspace_roots, extension_map to Arc<...> (read-only after serve()); wrap document_tracker in Arc<Mutex<DocumentTracker>>
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)
mcp/handlers.rs — introduce BridgeContext holding independently-lockable components; replace Arc<Mutex<Translator>>
mcp/server.rs — 13 LSP-roundtrip handlers: acquire document_tracker lock only for ensure_open, release before client.request()
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
Description
All 13 LSP tool handlers in
mcp/server.rsholdMutex<Translator>for the full duration of the LSP round-trip — includingclient.request(...)with a 30 s timeout. This serializes all concurrent tool calls: worst-case p99 latency for a hover request whileget_diagnosticsis in-flight is 60 s; under 13 concurrent calls it reaches 390 s.Phase 1 (issue #104) extracts
NotificationCacheto 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
get_diagnostics+get_hover) against a slow or cold rust-analyzer workspaceExpected 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
bridge/translator.rs— convertlsp_clients,workspace_roots,extension_maptoArc<...>(read-only afterserve()); wrapdocument_trackerinArc<Mutex<DocumentTracker>>bridge/translator.rs— split eachhandle_*into a prepare phase (acquiredocument_trackerlock forensure_open, then release) and an execute phase (client.request(...)with no lock held)mcp/handlers.rs— introduceBridgeContextholding independently-lockable components; replaceArc<Mutex<Translator>>mcp/server.rs— 13 LSP-roundtrip handlers: acquiredocument_trackerlock only forensure_open, release beforeclient.request()lib.rs— construct and passArc<BridgeContext>instead ofArc<Mutex<Translator>>State captured before lock release (per correctness analysis)
LspClientclone (Arc+channel, independent of translator mutex)Urifromensure_open(owned value)Invariants that must hold
ensure_openmust complete underdocument_trackerlock to prevent duplicatedidOpenfrom concurrent calls on the same pathclient.request()calls on the sameLspClientare safe by design (each uses its own oneshot channel keyed by unique request ID)What NOT to do
LspClient(it is already an actor with its own command channel)DashMapforNotificationCache(boundedVecDequesemantics require coordinated access)RwLock<Translator>(all handlers need exclusive access until this split is complete)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