Mark reqnroll project lifecycle notifications as Serial dispatch - #493
Merged
Conversation
reqnroll/projectLoaded, reqnroll/projectUnloaded, and reqnroll/projectFiles are registered via manual OnNotification with no JsonRpcHandlerOptions, which defaults to this server's global Parallel dispatch identifier (JsonRpcServerOptions' default). All three mutate the same ILspWorkspaceScopeManager scope table via a ConcurrentDictionary, which guards against concurrent corruption but not completion order: under Parallel dispatch a fast projectUnloaded could finish before a slower, earlier-sent projectLoaded for the same project, leaving the scope loaded when the client already thinks it's gone (or the reverse, on a rapid solution reload). Pass RequestProcessType.Serial explicitly for these three so the client's send order is preserved. Left every other manual route (resolveTestTargets, goToStepDefinitions/Hooks/MatchingScenarios, findStepUsages, findUnusedStepDefinitions, codeLens family, inlayHint/foldingRange, rename family) on the default Parallel lane — they're read-only queries or already internally synchronized (RenameSessionManager), and per issue #471 forcing Serial has a real cost: an in-flight Serial item blocks the *start* of new Parallel work too, on the same shared FIFO lane as didOpen/didChange/didSave. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This was referenced Aug 26, 2026
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.
🤔 What's changed?
reqnroll/projectLoaded,reqnroll/projectUnloaded, andreqnroll/projectFiles(inLanguageServerOptionsExtensions.InitializeCustomProtocolRouting) now passnew JsonRpcHandlerOptions { RequestProcessType = RequestProcessType.Serial }toOnNotification, instead of relying on the implicit default. AddedLanguageServerOptionsExtensionsTeststo assert this at the registration level (and a sanity check thatreqnroll/resolveTestTargets— a read-only route — stays off the Serial lane).⚡️ What's your motivation?
While investigating #491 I traced through OmniSharp's actual request-dispatch resolution (decompiled the library to confirm, rather than going by the docs): this server uses the stock
ParallelRequestProcessIdentifier(the library's own default), and every customreqnroll/*message is registered via manualOnRequest/OnNotificationwith noJsonRpcHandlerOptionsargument. That combination means every customreqnroll/*handler currently runs Parallel — including the three project lifecycle notifications.ILspWorkspaceScopeManager's scope table is aConcurrentDictionary, which protects against concurrent corruption but makes no guarantee about completion order. Under Parallel dispatch, a fastprojectUnloadedcould finish before a slower, earlier-sentprojectLoadedfor the same project completes — leaving the scope loaded when the client already believes it's gone (or the reverse, on a rapid solution reload/rename). That's a real correctness hazard for project lifecycle specifically, since these notifications must apply in the order the client sent them.I deliberately did not extend this to any other manual route. Per the existing rationale in
IFeatureParseCoordinator's remarks (issue #471), Serial is not free: an in-flight Serial item blocks the start of new Parallel work too, since it shares one global FIFO lane withdidOpen/didChange/didSave. Every other manual route (resolveTestTargets,goToStepDefinitions/Hooks/MatchingScenarios,findStepUsages,findUnusedStepDefinitions, the codeLens family,inlayHint/foldingRange, the rename family) is either a read-only query or already internally synchronized (RenameSessionManagerkeys sessions by(uri, version)in aConcurrentDictionarywith atomicTryRemove-based consumption) — forcing those onto the Serial lane would only add latency with no correctness benefit.🏷️ What kind of change is this?
🧩 Area(s) touched
src/LSP)📋 Checklist:
docs/🤖 Generated with Claude Code