Fix duplicate connection/logs in Webdev (#2635)
* bump dwds version to 24.3.9
* updated dwds constraints to 24.3.11
* fix duplicate logs
* fix duplicate logs
* Revert "fix duplicate logs"
This reverts commit 2ccd2d9d854ed43e473284088383c7a66dda68e2.
* updated changelog
* check and resuse active app state for each appId
* addressed comments
diff --git a/webdev/CHANGELOG.md b/webdev/CHANGELOG.md
index 448a206..49ef1f1 100644
--- a/webdev/CHANGELOG.md
+++ b/webdev/CHANGELOG.md
@@ -1,7 +1,9 @@
-## 3.7.2-wip
+## 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`.
## 3.7.1
diff --git a/webdev/lib/src/daemon/app_domain.dart b/webdev/lib/src/daemon/app_domain.dart
index 66853c1..8d750e5 100644
--- a/webdev/lib/src/daemon/app_domain.dart
+++ b/webdev/lib/src/daemon/app_domain.dart
@@ -63,46 +63,33 @@
Future<void> _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,
'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 for this appId
// 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,
@@ -120,9 +107,19 @@
appConnection.runMain();
+ // Handle connection termination - send events first, then cleanup
unawaited(debugConnection.onDone.whenComplete(() {
- appState.dispose();
- _appStates.remove(appId);
+ sendEvent('app.log', {
+ 'appId': appId,
+ 'log': 'Lost connection to device.',
+ });
+ sendEvent('app.stop', {
+ 'appId': appId,
+ });
+ daemon.shutdown();
+
+ // Clean up app resources
+ _cleanupAppConnection(appId, appState);
}));
}
@@ -223,6 +220,36 @@
return true;
}
+ /// Sets up VM service listeners for the given appId.
+ /// Returns the stdout subscription.
+ Future<StreamSubscription<Event>> _setupVmServiceListeners(
+ String appId, VmService vmService) async {
+ try {
+ vmService.onServiceEvent.listen(_onServiceEvent);
+ await vmService.streamListen(EventStreams.kService);
+ } catch (_) {}
+
+ // ignore: cancel_subscriptions
+ 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.
+ void _cleanupAppConnection(String appId, _AppState appState) {
+ appState.dispose();
+ _appStates.remove(appId);
+ }
+
@override
void dispose() {
_isShutdown = true;
diff --git a/webdev/lib/src/version.dart b/webdev/lib/src/version.dart
index 7d40e1d..2cda305 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 5e7ea31..d1706c7 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 @@
crypto: ^3.0.2
dds: ^4.1.0
# Pin DWDS to avoid dependency conflicts with vm_service:
- dwds: 24.3.5
+ dwds: 24.3.11
http: ^1.0.0
http_multi_server: ^3.2.0
io: ^1.0.3