From 7761d34e80ea3359cdaf68949182aad41a2c9255 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Fri, 4 Apr 2025 13:37:45 -0400 Subject: [PATCH 1/8] bump dwds version to 24.3.9 --- webdev/CHANGELOG.md | 3 ++- webdev/lib/src/version.dart | 2 +- webdev/pubspec.yaml | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/webdev/CHANGELOG.md b/webdev/CHANGELOG.md index 448a206af..55fd184ba 100644 --- a/webdev/CHANGELOG.md +++ b/webdev/CHANGELOG.md @@ -1,7 +1,8 @@ -## 3.7.2-wip +## 3.7.2 - Adds `--offline` flag [#2483](https://github.com/dart-lang/webdev/pull/2483). - Support the `--hostname` flag when the `--tls-cert-key` and `--tls-cert-chain` flags are present [#2588](https://github.com/dart-lang/webdev/pull/2588). +- Update `dwds` constraint to `24.3.9`. ## 3.7.1 diff --git a/webdev/lib/src/version.dart b/webdev/lib/src/version.dart index 7d40e1d7b..2cda305ca 100644 --- a/webdev/lib/src/version.dart +++ b/webdev/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '3.7.2-wip'; +const packageVersion = '3.7.2'; diff --git a/webdev/pubspec.yaml b/webdev/pubspec.yaml index 591ff2218..209121895 100644 --- a/webdev/pubspec.yaml +++ b/webdev/pubspec.yaml @@ -1,6 +1,6 @@ name: webdev # Every time this changes you need to run `dart run build_runner build`. -version: 3.7.2-wip +version: 3.7.2 # We should not depend on a dev SDK before publishing. # publish_to: none description: >- @@ -19,7 +19,7 @@ dependencies: crypto: ^3.0.2 dds: ^4.1.0 # Pin DWDS to avoid dependency conflicts with vm_service: - dwds: 24.3.5 + dwds: 24.3.9 http: ^1.0.0 http_multi_server: ^3.2.0 io: ^1.0.3 From 3c711c2c2f7d9f0e288978b93b0087ec0779a3a8 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 17 Jun 2025 11:17:42 -0400 Subject: [PATCH 2/8] updated dwds constraints to 24.3.11 --- webdev/CHANGELOG.md | 2 +- webdev/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webdev/CHANGELOG.md b/webdev/CHANGELOG.md index 55fd184ba..32f45e047 100644 --- a/webdev/CHANGELOG.md +++ b/webdev/CHANGELOG.md @@ -2,7 +2,7 @@ - Adds `--offline` flag [#2483](https://github.com/dart-lang/webdev/pull/2483). - Support the `--hostname` flag when the `--tls-cert-key` and `--tls-cert-chain` flags are present [#2588](https://github.com/dart-lang/webdev/pull/2588). -- Update `dwds` constraint to `24.3.9`. +- Update `dwds` constraint to `24.3.11`. ## 3.7.1 diff --git a/webdev/pubspec.yaml b/webdev/pubspec.yaml index 12722722b..e1b32ac68 100644 --- a/webdev/pubspec.yaml +++ b/webdev/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: crypto: ^3.0.2 dds: ^4.1.0 # Pin DWDS to avoid dependency conflicts with vm_service: - dwds: 24.3.9 + dwds: 24.3.11 http: ^1.0.0 http_multi_server: ^3.2.0 io: ^1.0.3 From db61954fb953216eeef6bf8e72ec8d3b8298abfe Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 17 Jun 2025 14:18:42 -0400 Subject: [PATCH 3/8] fix duplicate logs --- webdev/lib/src/daemon/app_domain.dart | 70 +++++++++++++++++++-------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/webdev/lib/src/daemon/app_domain.dart b/webdev/lib/src/daemon/app_domain.dart index 66853c19d..39493a1c9 100644 --- a/webdev/lib/src/daemon/app_domain.dart +++ b/webdev/lib/src/daemon/app_domain.dart @@ -25,6 +25,9 @@ class AppDomain extends Domain { final _appStates = {}; + // Prevents duplicate stdout listeners for the same appId + final _activeListeners = {}; + // Mapping from service name to service method. final Map _registeredMethodsForService = {}; @@ -85,24 +88,11 @@ class AppDomain extends Domain { 'deviceId': 'chrome', 'launchMode': 'run' }); - // TODO(grouma) - limit the catch to the appropriate error. - try { - await vmService.streamCancel('Stdout'); - } catch (_) {} - try { - await vmService.streamListen('Stdout'); - } catch (_) {} - try { - vmService.onServiceEvent.listen(_onServiceEvent); - await vmService.streamListen('Service'); - } catch (_) {} + + // Set up VM service listeners (only once per appId to prevent duplicates) // ignore: cancel_subscriptions - final stdOutSub = vmService.onStdoutEvent.listen((log) { - sendEvent('app.log', { - 'appId': appId, - 'log': utf8.decode(base64.decode(log.bytes!)), - }); - }); + final stdOutSub = await _setupVmServiceListeners(appId, vmService); + sendEvent('app.debugPort', { 'appId': appId, 'port': debugConnection.port, @@ -121,8 +111,7 @@ class AppDomain extends Domain { appConnection.runMain(); unawaited(debugConnection.onDone.whenComplete(() { - appState.dispose(); - _appStates.remove(appId); + _cleanupAppConnection(appId, appState); })); } @@ -223,6 +212,44 @@ class AppDomain extends Domain { return true; } + /// Sets up VM service listeners for the given appId if not already active. + /// Returns the stdout subscription if created, null otherwise. + Future?> _setupVmServiceListeners( + String appId, VmService vmService) async { + if (_activeListeners.contains(appId)) { + return null; // Already listening for this appId + } + + _activeListeners.add(appId); + + // TODO(grouma) - limit the catch to the appropriate error. + try { + await vmService.streamCancel('Stdout'); + } catch (_) {} + try { + await vmService.streamListen('Stdout'); + } catch (_) {} + try { + vmService.onServiceEvent.listen(_onServiceEvent); + await vmService.streamListen('Service'); + } catch (_) {} + + // ignore: cancel_subscriptions + return vmService.onStdoutEvent.listen((log) { + sendEvent('app.log', { + 'appId': appId, + 'log': utf8.decode(base64.decode(log.bytes!)), + }); + }); + } + + /// Cleans up an app connection and its associated listeners. + void _cleanupAppConnection(String appId, _AppState appState) { + appState.dispose(); + _appStates.remove(appId); + _activeListeners.remove(appId); + } + @override void dispose() { _isShutdown = true; @@ -230,13 +257,14 @@ class AppDomain extends Domain { state.dispose(); } _appStates.clear(); + _activeListeners.clear(); } } class _AppState { final DebugConnection _debugConnection; final StreamSubscription _resultSub; - final StreamSubscription _stdOutSub; + final StreamSubscription? _stdOutSub; bool _isDisposed = false; @@ -247,7 +275,7 @@ class _AppState { void dispose() { if (_isDisposed) return; _isDisposed = true; - _stdOutSub.cancel(); + _stdOutSub?.cancel(); _resultSub.cancel(); _debugConnection.close(); } From 2ccd2d9d854ed43e473284088383c7a66dda68e2 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 17 Jun 2025 14:19:43 -0400 Subject: [PATCH 4/8] fix duplicate logs --- dwds/CHANGELOG.md | 3 +++ dwds/lib/src/version.dart | 2 +- dwds/pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 418ff21e7..520eaa2c6 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,3 +1,6 @@ +## 24.3.12 +- Fixed duplicate app logs on page refresh by preventing multiple stdout listeners for the same appId. + ## 24.3.11 - Changed DWDS to always inject the client and added `useDwdsWebSocketConnection` flag to control communication protocol: when true uses socket-based implementation, when false uses Chrome-based communication protocol. diff --git a/dwds/lib/src/version.dart b/dwds/lib/src/version.dart index b2c6d5326..2488fd7a1 100644 --- a/dwds/lib/src/version.dart +++ b/dwds/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '24.3.11'; +const packageVersion = '24.3.12'; diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml index ae2f792ab..73bf2f351 100644 --- a/dwds/pubspec.yaml +++ b/dwds/pubspec.yaml @@ -1,6 +1,6 @@ name: dwds # Every time this changes you need to run `dart run build_runner build`. -version: 24.3.11 +version: 24.3.12 description: >- A service that proxies between the Chrome debug protocol and the Dart VM From 418858b5eac388ec1437d09e1b09d10d67cc7b73 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 17 Jun 2025 14:25:01 -0400 Subject: [PATCH 5/8] Revert "fix duplicate logs" This reverts commit 2ccd2d9d854ed43e473284088383c7a66dda68e2. --- dwds/CHANGELOG.md | 3 --- dwds/lib/src/version.dart | 2 +- dwds/pubspec.yaml | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 520eaa2c6..418ff21e7 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,6 +1,3 @@ -## 24.3.12 -- Fixed duplicate app logs on page refresh by preventing multiple stdout listeners for the same appId. - ## 24.3.11 - Changed DWDS to always inject the client and added `useDwdsWebSocketConnection` flag to control communication protocol: when true uses socket-based implementation, when false uses Chrome-based communication protocol. diff --git a/dwds/lib/src/version.dart b/dwds/lib/src/version.dart index 2488fd7a1..b2c6d5326 100644 --- a/dwds/lib/src/version.dart +++ b/dwds/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '24.3.12'; +const packageVersion = '24.3.11'; diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml index 73bf2f351..ae2f792ab 100644 --- a/dwds/pubspec.yaml +++ b/dwds/pubspec.yaml @@ -1,6 +1,6 @@ name: dwds # Every time this changes you need to run `dart run build_runner build`. -version: 24.3.12 +version: 24.3.11 description: >- A service that proxies between the Chrome debug protocol and the Dart VM From 1e99bf0a8f8335cc21ebcecda915bd60e9c00be0 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Tue, 17 Jun 2025 14:29:36 -0400 Subject: [PATCH 6/8] updated changelog --- webdev/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/webdev/CHANGELOG.md b/webdev/CHANGELOG.md index 32f45e047..49ef1f1b9 100644 --- a/webdev/CHANGELOG.md +++ b/webdev/CHANGELOG.md @@ -1,5 +1,6 @@ ## 3.7.2 +- Fixed duplicate app logs on page refresh by preventing multiple stdout listeners for the same appId. - Adds `--offline` flag [#2483](https://github.com/dart-lang/webdev/pull/2483). - Support the `--hostname` flag when the `--tls-cert-key` and `--tls-cert-chain` flags are present [#2588](https://github.com/dart-lang/webdev/pull/2588). - Update `dwds` constraint to `24.3.11`. From ab72ccca7ec3a879d64b2aa30b2da6f9c346fc24 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Fri, 20 Jun 2025 13:09:35 -0400 Subject: [PATCH 7/8] check and resuse active app state for each appId --- webdev/lib/src/daemon/app_domain.dart | 68 +++++++++++++-------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/webdev/lib/src/daemon/app_domain.dart b/webdev/lib/src/daemon/app_domain.dart index 39493a1c9..7fe2ebf1b 100644 --- a/webdev/lib/src/daemon/app_domain.dart +++ b/webdev/lib/src/daemon/app_domain.dart @@ -25,9 +25,6 @@ class AppDomain extends Domain { final _appStates = {}; - // Prevents duplicate stdout listeners for the same appId - final _activeListeners = {}; - // Mapping from service name to service method. final Map _registeredMethodsForService = {}; @@ -66,22 +63,22 @@ class AppDomain extends Domain { Future _handleAppConnections(WebDevServer server) async { final dwds = server.dwds!; + // The connection is established right before `main()` is called. await for (final appConnection in dwds.connectedApps) { + final appId = appConnection.request.appId; + + // Check if we already have an active app state for this appId + if (_appStates.containsKey(appId)) { + // Reuse existing connection, just run main again + appConnection.runMain(); + continue; + } + final debugConnection = await dwds.debugConnection(appConnection); final debugUri = debugConnection.ddsUri ?? debugConnection.uri; final vmService = await vmServiceConnectUri(debugUri); - final appId = appConnection.request.appId; - unawaited(debugConnection.onDone.then((_) { - sendEvent('app.log', { - 'appId': appId, - 'log': 'Lost connection to device.', - }); - sendEvent('app.stop', { - 'appId': appId, - }); - daemon.shutdown(); - })); + sendEvent('app.start', { 'appId': appId, 'directory': Directory.current.path, @@ -89,7 +86,7 @@ class AppDomain extends Domain { 'launchMode': 'run' }); - // Set up VM service listeners (only once per appId to prevent duplicates) + // Set up VM service listeners for this appId // ignore: cancel_subscriptions final stdOutSub = await _setupVmServiceListeners(appId, vmService); @@ -110,7 +107,18 @@ class AppDomain extends Domain { appConnection.runMain(); - unawaited(debugConnection.onDone.whenComplete(() { + // Handle connection termination - send events first, then cleanup + unawaited(debugConnection.onDone.then((_) { + sendEvent('app.log', { + 'appId': appId, + 'log': 'Lost connection to device.', + }); + sendEvent('app.stop', { + 'appId': appId, + }); + daemon.shutdown(); + + // Clean up app resources _cleanupAppConnection(appId, appState); })); } @@ -212,27 +220,19 @@ class AppDomain extends Domain { return true; } - /// Sets up VM service listeners for the given appId if not already active. - /// Returns the stdout subscription if created, null otherwise. - Future?> _setupVmServiceListeners( + /// Sets up VM service listeners for the given appId. + /// Returns the stdout subscription. + Future> _setupVmServiceListeners( String appId, VmService vmService) async { - if (_activeListeners.contains(appId)) { - return null; // Already listening for this appId - } - - _activeListeners.add(appId); - - // TODO(grouma) - limit the catch to the appropriate error. try { - await vmService.streamCancel('Stdout'); + vmService.onServiceEvent.listen(_onServiceEvent); + await vmService.streamListen('Service'); } catch (_) {} + + // Set up stdout listener try { await vmService.streamListen('Stdout'); } catch (_) {} - try { - vmService.onServiceEvent.listen(_onServiceEvent); - await vmService.streamListen('Service'); - } catch (_) {} // ignore: cancel_subscriptions return vmService.onStdoutEvent.listen((log) { @@ -247,7 +247,6 @@ class AppDomain extends Domain { void _cleanupAppConnection(String appId, _AppState appState) { appState.dispose(); _appStates.remove(appId); - _activeListeners.remove(appId); } @override @@ -257,14 +256,13 @@ class AppDomain extends Domain { state.dispose(); } _appStates.clear(); - _activeListeners.clear(); } } class _AppState { final DebugConnection _debugConnection; final StreamSubscription _resultSub; - final StreamSubscription? _stdOutSub; + final StreamSubscription _stdOutSub; bool _isDisposed = false; @@ -275,7 +273,7 @@ class _AppState { void dispose() { if (_isDisposed) return; _isDisposed = true; - _stdOutSub?.cancel(); + _stdOutSub.cancel(); _resultSub.cancel(); _debugConnection.close(); } From 2c2d265be45401c712196d9608a239d40756e1a3 Mon Sep 17 00:00:00 2001 From: Jessy Yameogo Date: Fri, 20 Jun 2025 13:29:09 -0400 Subject: [PATCH 8/8] addressed comments --- webdev/lib/src/daemon/app_domain.dart | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/webdev/lib/src/daemon/app_domain.dart b/webdev/lib/src/daemon/app_domain.dart index 7fe2ebf1b..8d750e50e 100644 --- a/webdev/lib/src/daemon/app_domain.dart +++ b/webdev/lib/src/daemon/app_domain.dart @@ -108,7 +108,7 @@ class AppDomain extends Domain { appConnection.runMain(); // Handle connection termination - send events first, then cleanup - unawaited(debugConnection.onDone.then((_) { + unawaited(debugConnection.onDone.whenComplete(() { sendEvent('app.log', { 'appId': appId, 'log': 'Lost connection to device.', @@ -226,21 +226,22 @@ class AppDomain extends Domain { String appId, VmService vmService) async { try { vmService.onServiceEvent.listen(_onServiceEvent); - await vmService.streamListen('Service'); - } catch (_) {} - - // Set up stdout listener - try { - await vmService.streamListen('Stdout'); + await vmService.streamListen(EventStreams.kService); } catch (_) {} // ignore: cancel_subscriptions - return vmService.onStdoutEvent.listen((log) { + final stdoutSubscription = vmService.onStdoutEvent.listen((log) { sendEvent('app.log', { 'appId': appId, 'log': utf8.decode(base64.decode(log.bytes!)), }); }); + + try { + await vmService.streamListen(EventStreams.kStdout); + } catch (_) {} + + return stdoutSubscription; } /// Cleans up an app connection and its associated listeners.