Skip to content

Commit 1416bb0

Browse files
committed
[dwds] Wait for scripts to be parsed on a hot restart and publish DWDS 25.0.0
Like #2640, we should wait until scripts are parsed before continuing a hot restart. Otherwise, metadata can be stale and breakpoints may be placed in the wrong files. For now, we only do this in the DDC library bundle format as we don't have a way to fetch the changed libraries in the AMD format. - hotReloadSourcesUri is repurposed to be reloadedSourcesUri, which is a breaking change. This file is now used for both hot restart and hot reload to detail the changed files across either. - Injected client is changed to return the mapping within this uri when a hot reload is executed. - Completer is added to ChromeProxyService to wait until all scripts are parsed before recreating the isolate, which will reinitialize all metadata. - Fix a preexisting race condition where we don't listen for a kIsolateStart event early enough. This should help fix the Windows flakes for hot_restart_breakpoints_test.dart.
1 parent 94c172c commit 1416bb0

19 files changed

+1263
-987
lines changed

dwds/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
## 24.4.2-wip
1+
## 25.0.0
22

33
- Update a call to the `package:shelf_web_socket` `webSocketHandler()` function.
44

5+
**Breaking changes**
6+
7+
- Rename `FrontendServerDdcLibraryBundleStrategy.hotReloadSourcesUri` to
8+
`reloadedSourcesUri`. The file that the `Uri` points to should now be updated
9+
for both a hot restart and a hot reload.
10+
511
## 24.4.1
612

713
- Implemented a WebSocket-based communication protocol that provides essential developer tooling (hot reload, service extensions) when Chrome debugger access is unavailable. - [#2605](https://github.com/dart-lang/webdev/issues/2605)

dwds/lib/src/dwds_vm_client.dart

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import 'dart:async';
66
import 'dart:convert';
77

8+
import 'package:dwds/src/config/tool_configuration.dart';
89
import 'package:dwds/src/events.dart';
10+
import 'package:dwds/src/loaders/ddc_library_bundle.dart';
911
import 'package:dwds/src/services/chrome_debug_exception.dart';
1012
import 'package:dwds/src/services/chrome_proxy_service.dart';
1113
import 'package:dwds/src/services/debug_service.dart';
@@ -566,6 +568,9 @@ Future<Map<String, dynamic>> _hotRestart(
566568
// Start listening for isolate create events before issuing a hot
567569
// restart. Only return success after the isolate has fully started.
568570
final stream = chromeProxyService.onEvent('Isolate');
571+
final waitForIsolateStarted = stream.firstWhere(
572+
(event) => event.kind == EventKind.kIsolateStart,
573+
);
569574
try {
570575
// If we should pause isolates on start, then only run main once we get a
571576
// resume event.
@@ -575,11 +580,58 @@ Future<Map<String, dynamic>> _hotRestart(
575580
}
576581
// Generate run id to hot restart all apps loaded into the tab.
577582
final runId = const Uuid().v4().toString();
583+
584+
// When using the DDC library bundle format, we determine the sources that
585+
// were reloaded during a hot restart to then wait until all the sources are
586+
// parsed before finishing hot restart. This is necessary before we can
587+
// recompute any metadata.
588+
// TODO(srujzs): We don't do this for the AMD module format, should we? It
589+
// would require adding an extra parameter in the AMD strategy. As we're
590+
// planning to deprecate it, for now, do nothing.
591+
final isDdcLibraryBundle =
592+
globalToolConfiguration.loadStrategy is DdcLibraryBundleStrategy;
593+
final computedReloadedSrcs = Completer<void>();
594+
final reloadedSrcs = <String>{};
595+
if (isDdcLibraryBundle) {
596+
// Injected client should send a request to recreate the isolate after the
597+
// hot restart. The creation of the isolate should in turn wait until all
598+
// scripts are parsed.
599+
chromeProxyService.allowedToCreateIsolate = Completer<void>();
600+
final debugger = await chromeProxyService.debuggerFuture;
601+
late StreamSubscription<String> parsedScriptsSubscription;
602+
parsedScriptsSubscription = debugger.parsedScriptsController.stream
603+
.listen((url) {
604+
computedReloadedSrcs.future.then((_) async {
605+
reloadedSrcs.remove(Uri.parse(url).normalizePath().path);
606+
if (reloadedSrcs.isEmpty) {
607+
chromeProxyService.allowedToCreateIsolate.complete();
608+
await parsedScriptsSubscription.cancel();
609+
}
610+
});
611+
});
612+
}
578613
_chromeLogger.info('Issuing \$dartHotRestartDwds request');
579-
await chromeProxyService.inspector.jsEvaluate(
614+
final remoteObject = await chromeProxyService.inspector.jsEvaluate(
580615
'\$dartHotRestartDwds(\'$runId\', $pauseIsolatesOnStart);',
581616
awaitPromise: true,
617+
returnByValue: true,
582618
);
619+
if (isDdcLibraryBundle) {
620+
final reloadedSrcModuleLibraries =
621+
(remoteObject.value as List).cast<Map>();
622+
for (final srcModuleLibrary in reloadedSrcModuleLibraries) {
623+
final srcModuleLibraryCast = srcModuleLibrary.cast<String, Object>();
624+
reloadedSrcs.add(
625+
Uri.parse(srcModuleLibraryCast['src'] as String).normalizePath().path,
626+
);
627+
}
628+
if (reloadedSrcs.isEmpty) {
629+
chromeProxyService.allowedToCreateIsolate.complete();
630+
}
631+
computedReloadedSrcs.complete();
632+
} else {
633+
assert(remoteObject.value == null);
634+
}
583635
_chromeLogger.info('\$dartHotRestartDwds request complete.');
584636
} on WipError catch (exception) {
585637
final code = exception.error?['code'];
@@ -601,7 +653,7 @@ Future<Map<String, dynamic>> _hotRestart(
601653
};
602654
}
603655
_chromeLogger.info('Waiting for Isolate Start event.');
604-
await stream.firstWhere((event) => event.kind == EventKind.kIsolateStart);
656+
await waitForIsolateStarted;
605657
chromeProxyService.terminatingIsolates = false;
606658

607659
_chromeLogger.info('Successful hot restart');

dwds/lib/src/handlers/injector.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ Future<String> _injectedClientSnippet(
204204
'window.\$dartEmitDebugEvents = ${debugSettings.emitDebugEvents};\n'
205205
'window.\$isInternalBuild = ${appMetadata.isInternalBuild};\n'
206206
'window.\$isFlutterApp = ${buildSettings.isFlutterApp};\n'
207-
'${loadStrategy is DdcLibraryBundleStrategy ? 'window.\$hotReloadSourcesPath = "${loadStrategy.hotReloadSourcesUri.toString()}";\n' : ''}'
207+
'${loadStrategy is DdcLibraryBundleStrategy ? 'window.\$reloadedSourcesPath = "${loadStrategy.reloadedSourcesUri.toString()}";\n' : ''}'
208208
'${loadStrategy.loadClientSnippet(_clientScript)}';
209209

210210
if (extensionUri != null) {

0 commit comments

Comments
 (0)