Ensure the language client is always ready before using it - #14617
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a TypeScript wrapper around the VS Code LanguageClient to ensure RPC calls into cpptools are gated on a single “ready” signal, aiming to prevent crashes caused by early event/RPC usage before the language client is usable.
Changes:
- Added a
LanguageClientwrapper that awaits readiness forsendRequest/sendNotification, and updated the core client startup flow to use it. - Removed many explicit
await client.readycall sites across providers/commands, relying on the wrapper to enforce readiness. - Removed the
enqueue-based sequencing from client/extension event wiring and performed minor provider cleanup.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Extension/src/LanguageServer/Providers/workspaceSymbolProvider.ts | Constructor simplification; relies on centralized readiness behavior. |
| Extension/src/LanguageServer/Providers/semanticTokensProvider.ts | Formatting-only style adjustment. |
| Extension/src/LanguageServer/Providers/renameProvider.ts | Removes explicit readiness await in favor of wrapper. |
| Extension/src/LanguageServer/Providers/onTypeFormattingEditProvider.ts | Removes explicit readiness await in favor of wrapper. |
| Extension/src/LanguageServer/Providers/HoverProvider.ts | Removes explicit readiness await in favor of wrapper. |
| Extension/src/LanguageServer/Providers/foldingRangeProvider.ts | Removes explicit readiness await in favor of wrapper. |
| Extension/src/LanguageServer/Providers/findAllReferencesProvider.ts | Removes explicit readiness await in favor of wrapper. |
| Extension/src/LanguageServer/Providers/documentSymbolProvider.ts | Switches to using client.languageClient.sendRequest without manual readiness await. |
| Extension/src/LanguageServer/Providers/documentRangeFormattingEditProvider.ts | Removes explicit readiness await in favor of wrapper. |
| Extension/src/LanguageServer/Providers/documentFormattingEditProvider.ts | Removes explicit readiness await in favor of wrapper. |
| Extension/src/LanguageServer/Providers/CopilotHoverProvider.ts | Removes explicit readiness await in favor of wrapper. |
| Extension/src/LanguageServer/Providers/codeActionProvider.ts | Removes explicit readiness await in favor of wrapper. |
| Extension/src/LanguageServer/Providers/callHierarchyProvider.ts | Removes explicit readiness awaits in favor of wrapper. |
| Extension/src/LanguageServer/protocolFilter.ts | Stops manually awaiting client.ready before sending visible-editor updates. |
| Extension/src/LanguageServer/languageClient.ts | New wrapper class that gates requests/notifications on a “ready” signal. |
| Extension/src/LanguageServer/extension.ts | Removes enqueue usage from editor event hooks; removes explicit ready awaits for some commands. |
| Extension/src/LanguageServer/codeAnalysis.ts | Swaps to wrapper LanguageClient and adjusts languageclient imports accordingly. |
| Extension/src/LanguageServer/clientCollection.ts | Removes await newClient.ready during crash-recovery ownership transfer. |
| Extension/src/LanguageServer/client.ts | Central refactor to use wrapper, remove enqueue, and shift readiness responsibility to the wrapper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
With these changes, I'm seeing an unusual number of |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
Extension/src/LanguageServer/client.ts:3116
restartIntelliSenseForFilereadsthis.languageClient.code2ProtocolConverterbefore any readiness await. If the language client wrapper hasn’t been initialized yet, that getter can throw synchronously during argument evaluation, bypassing the.catch(...)and crashing the event path. Await readiness before accessing the converters so the call is reliably deferred until the RPC client exists.
public async restartIntelliSenseForFile(document: vscode.TextDocument): Promise<void> {
return this.languageClient.sendNotification(RestartIntelliSenseForFileNotification, this.languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document)).catch(logAndReturn.undefined);
Extension/src/LanguageServer/protocolFilter.ts:49
cppEditorsis captured before the language client is ready, butonDidChangeVisibleTextEditorsbuildsparamssynchronously and only awaits readiness insidesendNotification. If initialization is still in progress, the visible editor/selection snapshot may be stale by the time the notification is actually sent. Defer collecting editors until afterclient.readyresolves (as before) so the server receives a current snapshot.
const cppEditors: vscode.TextEditor[] = vscode.window.visibleTextEditors.filter(e => util.isCpp(e.document));
void client.onDidChangeVisibleTextEditors(cppEditors).catch(logAndReturn.undefined);
Extension/src/LanguageServer/extension.ts:192
- The
onDidChangeTextEditorVisibleRanges/onDidChangeVisibleTextEditorshandlers areasyncand can reject, but their returned Promises are currently ignored by the event registration callbacks. If either throws/rejects, it can surface as an unhandled promise rejection. Wrap the calls withvoid ... .catch(logAndReturn.undefined)like other event hooks in this file.
disposables.push(vscode.window.onDidChangeTextEditorVisibleRanges(e => onDidChangeTextEditorVisibleRanges(e)));
disposables.push(vscode.window.onDidChangeActiveTextEditor(e => onDidChangeActiveTextEditor(e)));
ui.didChangeActiveEditor(); // Handle already active documents (for non-cpp files that we don't register didOpen).
disposables.push(vscode.window.onDidChangeTextEditorSelection(e => onDidChangeTextEditorSelection(e)));
disposables.push(vscode.window.onDidChangeVisibleTextEditors(e => onDidChangeVisibleTextEditors(e)));
I can check... EDIT: I did not reproduce this problem, but put out a separate PR to filter unnecessary settings change events which dropped my count down to 0. |
|
Bob Brown (@bobbrow) Do you want us to try to check this in? |
Yes, I was hoping it would get a review and sign off for 1.34.0. |
Sean McManus (sean-mcmanus)
left a comment
There was a problem hiding this comment.
✨Copilot (agent8): I found one additional startup-ordering issue on the current head.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
Extension/src/LanguageServer/extension.ts:580
- The wrapper cannot replace this readiness wait because
promptSelectIntelliSenseConfiguration()performs no RPC before checkingcompilerDefaults; during startup that value is still undefined and the command silently returns (client.ts:1286–1289). Restore the wait so invoking the command while initialization is in progress opens the picker after compiler discovery instead of doing nothing.
return clients.ActiveClient.promptSelectIntelliSenseConfiguration(sender);
Sean McManus (sean-mcmanus)
left a comment
There was a problem hiding this comment.
✨Copilot (agent8): One suppressed startup-timing observation remains valid on the current head.
* Use npm ci in issue workflows (#14702) * Pin the Yarn bootstrap and add SHA-512 lock checksums (#14703) * Add SHA-512 checksums to yarn.lock * Pin the Yarn bootstrap install * Register LLVM component for LLDB-MI (#14704) * Add xobjgen to gitignore (for Linux/Mac). (#14707) * Retry transient Yarn install failures (#14708) * Ensure the language client is always ready before using it (#14617) * Update 1.34.0 changelog (#14710) * Update changelog and version for 1.34.1 (#14714) * Update clang-tidy checks to 23.1.0 (#14713) * Use native file type mappings for TypeScript-side classification (#14711) * Add 1.34.2 changelog (#14722) * Add 1.34.2 changelog * Remove ignored network isolation policy (#14728) * Bump fast-uri from 3.1.5 to 3.1.6 in /ExtensionPack (#14731) Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.5 to 3.1.6. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](fastify/fast-uri@v3.1.5...v3.1.6) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump @xmldom/xmldom from 0.8.13 to 0.8.15 in /Extension (#14733) Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.8.13 to 0.8.15. - [Release notes](https://github.com/xmldom/xmldom/releases) - [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md) - [Commits](xmldom/xmldom@0.8.13...0.8.15) --- updated-dependencies: - dependency-name: "@xmldom/xmldom" dependency-version: 0.8.15 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump browserslist from 4.28.1 to 4.28.8 in /Extension (#14732) Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.28.1 to 4.28.8. - [Release notes](https://github.com/browserslist/browserslist/releases) - [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md) - [Commits](browserslist/browserslist@4.28.1...4.28.8) --- updated-dependencies: - dependency-name: browserslist dependency-version: 4.28.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Remove unused gulp-sourcemaps dependency (#14729) * Remove unused gulp-sourcemaps dependency * Remove orphaned dependency resolutions * Fix custom configuration provider regression (#14725) * Restore custom configuration provider checks * Ignore empty crash report files (#14730) * Ignore empty crash report files * Preserve pending crash reads across clients * Keep crash writing state for pending reports * Fix fast-uri dependency. (#14735) * Fix fast-uri dependency. * Also for Themes. * Implement session state tracking for "Run and Debug" button when Inte… (#14719) * Implement session state tracking for "Run and Debug" button when IntelliSense is disabled and add corresponding tests * Enhance session state tracking for build and debug by updating folder open status and adding tests * Fix build and debug folder session state tracking * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Sean McManus <seanmcm@microsoft.com> * Fix #11263: Make Edit Configurations UI fully theme-aware in custom themes (#14692) * Fix #11263: use theme-aware colors in Edit Configurations UI * Enhance dropdown styling with theme-aware colors in settings UI --------- Co-authored-by: Sean McManus <seanmcm@microsoft.com> * Add processFilter for remote attach process selection (#14684) * Add processFilter for remote attach process selection When attaching to a process on a remote target, the process always has to be selected by hand, even though the launch configuration already knows which executable it belongs to. A generated configuration cannot hard-code processId either, because the pid changes on every boot and on every restart of the service, so the picker is the only option. Add an optional processFilter regular expression to the cppdbg attach configuration. When set, it is matched against the label, description and detail of the remote process list: exactly one match attach to that process directly more than one show the picker with only the matching entries no match show the full picker, as before All three fields are considered because the item format depends on the transport: useExtendedRemote reports the user and the full command line in the label, while pipeTransport reports the process name in the label and the command line in the detail. An invalid regular expression is reported instead of being silently ignored. This affects remote attach only (pipeTransport and useExtendedRemote); local attach continues to use program-based matching. Closes #14682 * Extract remote process filtering into a helper Move the matching logic out of RemoteAttachPicker into a standalone function so that it can be unit tested without a VS Code quick pick or a live connection to a remote target. No functional change. * Add unit tests for processFilter matching Cover empty and non-string filter values, matching against label, description and detail, multiple matches, an invalid regular expression, and a regression case ensuring missing fields are not treated as empty strings. --------- Co-authored-by: Adrian Freihofer <adrian.freihofer@siemens.com> Co-authored-by: Sean McManus <seanmcm@microsoft.com> * Bump the github-actions group with 2 updates (#14738) Bumps the github-actions group with 2 updates: [github/codeql-action/init](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action). Updates `github/codeql-action/init` from 4.37.7 to 4.37.9 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@ff2f1c6...cdf488f) Updates `github/codeql-action/analyze` from 4.37.7 to 4.37.9 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@ff2f1c6...cdf488f) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: github/codeql-action/analyze dependency-version: 4.37.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix localization translation errors (#14737) * Localization - Translated Strings * Fix localization translation errors * Address localization review feedback * Fix localization review feedback --------- Co-authored-by: csigs <csigs@users.noreply.github.com> * Update changelog for 1.34.3 (#14740) * Update changelog and version for 1.34.4. (#14748) * Enable PR CI for release and insiders (#14751) * Fix localization string import (#14743) * Fix localization string import * Correct conversion cycle translations * Add Run and Debug session state tests (#14736) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Bob Brown <bobbrow@users.noreply.github.com> Co-authored-by: Colen Garoutte-Carson <49173979+Colengms@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Prashant Kumar Rai <prashant.kumar2021@vitbhopal.ac.in> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: afreof <adrian.freihofer@gmail.com> Co-authored-by: Adrian Freihofer <adrian.freihofer@siemens.com> Co-authored-by: csigs <csigs@users.noreply.github.com>
There are some crash reports indicating that some events attempting to communicate with cpptools are firing before the language client is ready to service them. While investigating, I noticed that several events are not awaiting the
readyevent.This PR wraps the LanguageClient interface in a class that ensures the client is ready before making any RPC calls into cpptools. Some other minor cleanup in the provider classes is included. I did not root out all places where
DefaultClient.readyis being unnecessarily awaited as it will require more testing, but those remaining are harmless.Note: I did not see a need to keep the
enqueuefunctionality based on my findings. On the native side, we already defer all messages in a queue until configuration happens so we can let the messages come in the order they are queued on the TypeScript side. Historically, I remember there being a need for "blocking" the language client "Middleware" (protocolFilter.ts), but that was already removed a while ago.enqueueis incompatible with this change as it results in deadlocked awaiting (an enqueued task that makes an RPC call to cpptools).