Repository navigation
[flutter_tools] Fix DTD "Stream already subscribed" error and wait for analysis over DTD in widget preview - #192930
Conversation
…review During widget preview startup, concurrent calls to retrieve previews (e.g., initial command startup racing with file watcher events) could both call `_waitForLspService()`, causing concurrent `streamListen` calls to the Dart Tooling Daemon on the `Service` stream. DTD throws JSON-RPC error 103 (`Stream already subscribed`) when a client attempts to subscribe to a stream it is already listening to. This change: - Deduplicates in-flight calls to `_waitForLspService()` using a shared future so concurrent callers await the same LSP registration event instead of issuing duplicate DTD requests. - Safely catches and ignores `RpcErrorCodes.kStreamAlreadySubscribed` on `streamListen` calls in `WidgetPreviewDtdServices`. - Adds hermetic regression tests covering concurrent and pre-subscribed stream scenarios. Fixes flutter#192424
There was a problem hiding this comment.
Code Review
This pull request introduces changes to WidgetPreviewDtdServices to handle concurrent calls to _waitForLspService and avoid stream already subscribed exceptions. It caches the future of _waitForLspService and adds a safeStreamListen extension on DartToolingDaemon to ignore kStreamAlreadySubscribed RPC exceptions. Unit tests are added to verify these behaviors. Feedback suggests refactoring the immediately invoked asynchronous function expression (IIFE) in _waitForLspService to use standard control flow with the cached future for better readability.
…dard control flow
…n WidgetPreviewDtdServices
…-existing analysis server
DanTup
left a comment
There was a problem hiding this comment.
Awesome, thank you! I'll try running a branch of my E2E tests against your fork/branch and see if this resolves the remaining flakes :-)
DanTup
left a comment
There was a problem hiding this comment.
Oh, I'm seeing failures here using your fork:
[9:21:01 AM] [FlutterWidgetPreview] [Info] <== [{"event":"widget_preview.logMessage","params":{"level":"error","message":"Oops; flutter has exited unexpectedly: \"JSON-RPC error -32601 (method not found): Unknown method \"Lsp.dart/workspace/analysis/complete\".\"."}}]
I'm just debugging, will post back when I understand what's happening...
… Lsp methods on DTD
DanTup
left a comment
There was a problem hiding this comment.
Thanks! The changes LGTM. I will test my E2E tests against this on Monday (I'll leave it to you if you want to wait for the result of that, or land this and if any issues do come up address seperately).
|
Unfortunately still seeing something wrong here on the GH bots: I can see we're passing the right DTD URI: And in the analysis server log: The times of the server request are 09:27:12.420 and 09:27:12.422. I'll will add some more verbose logging to try and track this down and post back. |
|
@bkonyi it's not the issue I noted above (still trying to repro that with verbose logging, it seems intermittent), but I hit another issue where a new analysis server was spawned despite passing a DTD URI: I think the logic here might be wrong: It will spawn an analysis server if "the DTD LSP service is not yet available", even if it might become available seconds later? |
|
@bkonyi despite many tries, I haven't been able to repro the first issue I posted about this morning above (I'm running with more verbose logging added to a copy of your branch, but it never happens). However, in addition to the issue noted above, I do also seem other weird errors (I tried rebasing the branch on master to see if that help, but it did not): I don't know if either of these issues are related to this PR, so I can file them separately if you think this PR is still a net improvement. |
|
@bkonyi I just tried again with the latest commit you pushed, but I got this error again: Full log is here: GH Actions results here: https://github.com/Dart-Code/E2E-Tests/actions/runs/36434945313/job/108970314634 I double-checked to ensure it was the right code, and it shows: Edit: I'll note that I think it's spawning an analysis server when it shouldn't, which may or may not be the cause of the error. |
…s not provided When --dtd-url is provided, an external tool (such as an IDE) owns the DTD instance and connects its own analysis server. Because analysis_server's dart/connectToDtd responds before Lsp services finish registering on DTD, checking dtd.lspServiceAvailable during LspPreviewDetector.initialize() raced with service registration and caused flutter_tools to spawn a duplicate analysis server. Only launch an internal analysis server and DTD instance when dtd.dtdUri is null, and rely on dtd.waitForAnalysis() to wait for the external analysis server to finish registering the Lsp service on DTD.
|
@bkonyi I'm still hitting this error when trying to run the tests against your branch, but I'm struggling to understand why. It doesn't seem to repro locally. I feel like something ( My |
|
@bkonyi I re-ran my tests 10 times after your This change definitely resolves multiple issues so I think we should land it. If I find any other flakes I'll file separate issues with the details. Thanks! |
|
reason for revert: widget preview failures in |
|
Successfully created revert PR: #193637 |
… wait for analysis over DTD in widget preview (flutter#193637) Reverts: [[flutter_tools] Fix DTD "Stream already subscribed" error and wait for analysis over DTD in widget preview](flutter#192930) Initiated by: @jtmcdole Reason for reverting: ``` Original PR Author: @bkonyi Reviewed By: @DanTup The original PR description is provided below: ## Description During widget preview startup or file updates: 1. Concurrent calls to retrieve previews or wait for analysis (e.g., initial command startup racing with file watcher events) could concurrently invoke `_waitForLspService()`, causing duplicate `streamListen` calls to the Dart Tooling Daemon. DTD throws JSON-RPC error 103 (`Stream already subscribed`) when a client attempts to subscribe to a stream it is already listening to. 2. `WidgetPreviewDtdServices` previously considered the `Lsp` service ready as soon as *any* `Lsp` method was registered on DTD (or upon receiving the first `ServiceRegistered` event for `Lsp`). Because the analysis server registers its LSP methods sequentially before posting an `initialized` event on the `Lsp` stream, calls to `Lsp.dart/workspace/analysis/complete` or `Lsp.dart/workspace/getFlutterWidgetPreviews` could race ahead of their registration and fail with `-32601 (method not found)`. 3. When connecting to a pre-existing analysis server over DTD (`--dtd-url`), `LspPreviewDetector.analysisServer` is `null`, causing `analysisServer?.waitForAnalysis()` calls during startup and file updates to be no-ops. This change: - Deduplicates in-flight calls to `_waitForLspService()` using a shared future (`_waitForLspServiceFuture`) so concurrent callers await the same LSP registration check instead of issuing duplicate DTD requests. - Separates `lspServiceAvailable` (whether an analysis server has begun registering `Lsp` services on DTD) from `_allLspServicesRegistered` (whether all required widget preview LSP methods in `kRequiredLspServices` are registered), and updates `_waitForLspServiceHelper()` to listen for the `initialized` event on the `Lsp` stream before checking `getRegisteredServices()`. - Consolidates LSP DTD calls into `_callLspService()` and removes the `kMethodNotFound` polling loop now that `_waitForLspService()` ensures all required LSP methods are registered. - Safely catches and ignores `RpcErrorCodes.kStreamAlreadySubscribed` on `streamListen` calls in `WidgetPreviewDtdServices` (`safeStreamListen`). - Adds `WidgetPreviewDtdServices.waitForAnalysis()` (invoking `Lsp.dart/workspace/analysis/complete` over DTD), unifies `LspPreviewDetector.waitForAnalysis()` so widget preview waits for analysis completion on both tool-managed and pre-existing analysis servers, and moves the startup `waitForAnalysis()` call inside the DTD error-handling `try / catch` block in `WidgetPreviewStartCommand`. - Adds hermetic regression tests covering concurrent/pre-subscribed DTD streams, partial vs. full LSP service registration (`Lsp.initialized`), and waiting for analysis over DTD. Fixes flutter#192424 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
…13104) Manual roll requested by [email protected] flutter/flutter@d649d2b...e89fd0a 2026-10-01 [email protected] Manual roll ICU from 6ebb40c59477 to 5aa526207171 (2 revisions) (flutter/flutter#193661) 2026-10-01 [email protected] fix(web): hash source maps before rewriting sourceMappingURL with --web-content-hash (flutter/flutter#193614) 2026-10-01 [email protected] [flutter_tools] Bootstrap DeviceManager in ToolDependencies (flutter/flutter#193524) 2026-10-01 [email protected] [wasm] Set flutter_runtime_mode in tools/gn to_gn_wasm_args (flutter/flutter#193407) 2026-10-01 [email protected] Remove remaining `download_jdk` references (flutter/flutter#193562) 2026-10-01 [email protected] Synchronize dart2wasm feature probes in roll script and flutter_bootstrap.js (flutter/flutter#193271) 2026-10-01 [email protected] [web] Unskip web tests that now pass in gestures, services, and widgets (flutter/flutter#193512) 2026-10-01 [email protected] Roll Fuchsia Test Scripts from whZAV1mfT-Rdc0rQ8... to fCNknap4ZI0AMcvIf... (flutter/flutter#193640) 2026-10-01 [email protected] Roll Skia from 0575a37d0619 to f2d68e0b8863 (2 revisions) (flutter/flutter#193621) 2026-10-01 [email protected] perf(widgets): use const <Never>{} in InheritedModelElement and avoid redundant casts in RenderObjectElement.update (flutter/flutter#192913) 2026-10-01 [email protected] [web] Unskip and strengthen missing glyph tofu test on Wimp (flutter/flutter#193426) 2026-10-01 [email protected] [flutter_tools] Fix `flutter drive -d chrome` hanging on web (flutter/flutter#193459) 2026-10-01 [email protected] [web] Fix hang when Chromium cannot bind remote debugging port (flutter/flutter#193461) 2026-10-01 [email protected] [flutter_tools] Bundle data assets from hooks in flutter test (flutter/flutter#193435) 2026-10-01 [email protected] Roll Packages from 0ba9a82 to d5ec6db (11 revisions) (flutter/flutter#193641) 2026-10-01 [email protected] Revert: [flutter_tools] Fix DTD "Stream already subscribed" error and wait for analysis over DTD in widget preview (flutter/flutter#193637) 2026-10-01 [email protected] Roll Skia from 6182a3cf8f98 to 0575a37d0619 (5 revisions) (flutter/flutter#193613) 2026-10-01 [email protected] [flutter_tools] Fix DTD "Stream already subscribed" error and wait for analysis over DTD in widget preview (flutter/flutter#192930) 2026-09-30 [email protected] [Impeller] Sample the render target rect in offscreen advanced blends (flutter/flutter#192988) 2026-09-30 [email protected] Roll Skia from 01524014dff5 to 6182a3cf8f98 (11 revisions) (flutter/flutter#193594) 2026-09-30 [email protected] [web] Use Xvfb -displayfd and clean up Xvfb after Firefox test suites (flutter/flutter#193510) 2026-09-30 [email protected] Fixes bug where sibling node fail to update size (flutter/flutter#191220) 2026-09-30 [email protected] Roll Skia from c7b323126bf0 to 01524014dff5 (3 revisions) (flutter/flutter#193577) 2026-09-30 [email protected] Remove some Undefined Behavior from run_customer_testing_tests.dart. (flutter/flutter#193582) 2026-09-30 [email protected] Fix pointer id/coordinate mismatch in Android platform view multi-touch (flutter/flutter#191631) 2026-09-30 [email protected] Roll Packages from 3c6ce59 to 0ba9a82 (11 revisions) (flutter/flutter#193578) 2026-09-30 [email protected] Fix unresolved doc comment references in dev/ tooling (flutter/flutter#193501) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC [email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
… wait for analysis over DTD in widget preview" (flutter#193658) ## Description Relands flutter#192930 (which was reverted in flutter#193637). ### What changed since the original PR - In `AnalysisServer`, store and cancel the `stdout` and `stderr` stream subscriptions in `dispose()` before closing `_analyzingController` and `_errorsController`, and guard `_analyzingController.add` with `!_analyzingController.isClosed` in `_handleProgress`. Previously, `$/progress` notifications arriving from the language server during or after `AnalysisServer.dispose()` threw `Bad state: Cannot add new events after calling close` in `widget_preview_scaffold_test.dart`. - In `widget_preview_test_helpers.dart`, await `analysisServer.onExit` after `analysisServer.dispose()` in `addTearDown` so the analysis server process has fully exited before temporary directory cleanup on Windows. - Added a hermetic unit test in `analyze_continuously_test.dart` verifying that `AnalysisServer` does not throw when `$/progress` notifications arrive during or after `dispose()`. ### Original description During widget preview startup or file updates: 1. Concurrent calls to retrieve previews or wait for analysis (e.g., initial command startup racing with file watcher events) could concurrently invoke `_waitForLspService()`, causing duplicate `streamListen` calls to the Dart Tooling Daemon. DTD throws JSON-RPC error 103 (`Stream already subscribed`) when a client attempts to subscribe to a stream it is already listening to. 2. `WidgetPreviewDtdServices` previously considered the `Lsp` service ready as soon as *any* `Lsp` method was registered on DTD (or upon receiving the first `ServiceRegistered` event for `Lsp`). Because the analysis server registers its LSP methods sequentially before posting an `initialized` event on the `Lsp` stream, calls to `Lsp.dart/workspace/analysis/complete` or `Lsp.dart/workspace/getFlutterWidgetPreviews` could race ahead of their registration and fail with `-32601 (method not found)`. 3. When connecting to a pre-existing analysis server over DTD (`--dtd-url`), `LspPreviewDetector.analysisServer` is `null`, causing `analysisServer?.waitForAnalysis()` calls during startup and file updates to be no-ops. This change: - Deduplicates in-flight calls to `_waitForLspService()` using a shared future (`_waitForLspServiceFuture`) so concurrent callers await the same LSP registration check instead of issuing duplicate DTD requests. - Separates `lspServiceAvailable` (whether an analysis server has begun registering `Lsp` services on DTD) from `_allLspServicesRegistered` (whether all required widget preview LSP methods in `kRequiredLspServices` are registered), and updates `_waitForLspServiceHelper()` to listen for the `initialized` event on the `Lsp` stream before checking `getRegisteredServices()`. - Consolidates LSP DTD calls into `_callLspService()` and removes the `kMethodNotFound` polling loop now that `_waitForLspService()` ensures all required LSP methods are registered. - Safely catches and ignores `RpcErrorCodes.kStreamAlreadySubscribed` on `streamListen` calls in `WidgetPreviewDtdServices` (`safeStreamListen`). - Adds `WidgetPreviewDtdServices.waitForAnalysis()` (invoking `Lsp.dart/workspace/analysis/complete` over DTD), unifies `LspPreviewDetector.waitForAnalysis()` so widget preview waits for analysis completion on both tool-managed and pre-existing analysis servers, and moves the startup `waitForAnalysis()` call inside the DTD error-handling `try / catch` block in `WidgetPreviewStartCommand`. - Adds hermetic regression tests covering concurrent/pre-subscribed DTD streams, partial vs. full LSP service registration (`Lsp.initialized`), and waiting for analysis over DTD. Fixes flutter#192424 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Description
During widget preview startup or file updates:
_waitForLspService(), causing duplicatestreamListencalls to the Dart Tooling Daemon. DTD throws JSON-RPC error 103 (Stream already subscribed) when a client attempts to subscribe to a stream it is already listening to.WidgetPreviewDtdServicespreviously considered theLspservice ready as soon as anyLspmethod was registered on DTD (or upon receiving the firstServiceRegisteredevent forLsp). Because the analysis server registers its LSP methods sequentially before posting aninitializedevent on theLspstream, calls toLsp.dart/workspace/analysis/completeorLsp.dart/workspace/getFlutterWidgetPreviewscould race ahead of their registration and fail with-32601 (method not found).--dtd-url),LspPreviewDetector.analysisServerisnull, causinganalysisServer?.waitForAnalysis()calls during startup and file updates to be no-ops.This change:
_waitForLspService()using a shared future (_waitForLspServiceFuture) so concurrent callers await the same LSP registration check instead of issuing duplicate DTD requests.lspServiceAvailable(whether an analysis server has begun registeringLspservices on DTD) from_allLspServicesRegistered(whether all required widget preview LSP methods inkRequiredLspServicesare registered), and updates_waitForLspServiceHelper()to listen for theinitializedevent on theLspstream before checkinggetRegisteredServices()._callLspService()and removes thekMethodNotFoundpolling loop now that_waitForLspService()ensures all required LSP methods are registered.RpcErrorCodes.kStreamAlreadySubscribedonstreamListencalls inWidgetPreviewDtdServices(safeStreamListen).WidgetPreviewDtdServices.waitForAnalysis()(invokingLsp.dart/workspace/analysis/completeover DTD), unifiesLspPreviewDetector.waitForAnalysis()so widget preview waits for analysis completion on both tool-managed and pre-existing analysis servers, and moves the startupwaitForAnalysis()call inside the DTD error-handlingtry / catchblock inWidgetPreviewStartCommand.Lsp.initialized), and waiting for analysis over DTD.Fixes #192424
Pre-launch Checklist
///).