Skip to content

Commit c0492f1

Browse files
authored
[dwds] Wait for scripts to be parsed on a hot restart and publish DWDS 25.0.0 (#2667)
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 595f876 commit c0492f1

20 files changed

+1210
-1236
lines changed

dwds/CHANGELOG.md

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

33
- Implemented hot restart over websockets with multi window support.
44
- Fix refresh race condition bug by adding an isolate destruction grace period.
55
- Update a call to the `package:shelf_web_socket` `webSocketHandler()` function.
6+
7+
**Breaking changes**
8+
69
- Remove deprecated parameter `injectDebuggingSupportCode` from `Dwds.start()`.
710
- Remove all deprecated fields, getters, and parameters
811
related to the null safety compilation mode. Dart 3 only
912
supports [sound null safety](https://dart.dev/null-safety).
13+
- Rename `FrontendServerDdcLibraryBundleStrategy.hotReloadSourcesUri` to
14+
`reloadedSourcesUri`. The file that the `Uri` points to should now be updated
15+
for both a hot restart and a hot reload.
1016

1117
## 24.4.1
1218

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';
@@ -585,6 +587,9 @@ Future<Map<String, dynamic>> _hotRestart(
585587
// Start listening for isolate create events before issuing a hot
586588
// restart. Only return success after the isolate has fully started.
587589
final stream = chromeProxyService.onEvent('Isolate');
590+
final waitForIsolateStarted = stream.firstWhere(
591+
(event) => event.kind == EventKind.kIsolateStart,
592+
);
588593
try {
589594
// If we should pause isolates on start, then only run main once we get a
590595
// resume event.
@@ -594,11 +599,58 @@ Future<Map<String, dynamic>> _hotRestart(
594599
}
595600
// Generate run id to hot restart all apps loaded into the tab.
596601
final runId = const Uuid().v4().toString();
602+
603+
// When using the DDC library bundle format, we determine the sources that
604+
// were reloaded during a hot restart to then wait until all the sources are
605+
// parsed before finishing hot restart. This is necessary before we can
606+
// recompute any source location metadata in the `ChromeProxyService`.
607+
// TODO(srujzs): We don't do this for the AMD module format, should we? It
608+
// would require adding an extra parameter in the AMD strategy. As we're
609+
// planning to deprecate it, for now, do nothing.
610+
final isDdcLibraryBundle =
611+
globalToolConfiguration.loadStrategy is DdcLibraryBundleStrategy;
612+
final computedReloadedSrcs = Completer<void>();
613+
final reloadedSrcs = <String>{};
614+
if (isDdcLibraryBundle) {
615+
// Injected client should send a request to recreate the isolate after the
616+
// hot restart. The creation of the isolate should in turn wait until all
617+
// scripts are parsed.
618+
chromeProxyService.allowedToCreateIsolate = Completer<void>();
619+
final debugger = await chromeProxyService.debuggerFuture;
620+
late StreamSubscription<String> parsedScriptsSubscription;
621+
parsedScriptsSubscription = debugger.parsedScriptsController.stream
622+
.listen((url) {
623+
computedReloadedSrcs.future.then((_) async {
624+
reloadedSrcs.remove(Uri.parse(url).normalizePath().path);
625+
if (reloadedSrcs.isEmpty) {
626+
chromeProxyService.allowedToCreateIsolate.complete();
627+
await parsedScriptsSubscription.cancel();
628+
}
629+
});
630+
});
631+
}
597632
_chromeLogger.info('Issuing \$dartHotRestartDwds request');
598-
await chromeProxyService.inspector.jsEvaluate(
633+
final remoteObject = await chromeProxyService.inspector.jsEvaluate(
599634
'\$dartHotRestartDwds(\'$runId\', $pauseIsolatesOnStart);',
600635
awaitPromise: true,
636+
returnByValue: true,
601637
);
638+
if (isDdcLibraryBundle) {
639+
final reloadedSrcModuleLibraries =
640+
(remoteObject.value as List).cast<Map>();
641+
for (final srcModuleLibrary in reloadedSrcModuleLibraries) {
642+
final srcModuleLibraryCast = srcModuleLibrary.cast<String, Object>();
643+
reloadedSrcs.add(
644+
Uri.parse(srcModuleLibraryCast['src'] as String).normalizePath().path,
645+
);
646+
}
647+
if (reloadedSrcs.isEmpty) {
648+
chromeProxyService.allowedToCreateIsolate.complete();
649+
}
650+
computedReloadedSrcs.complete();
651+
} else {
652+
assert(remoteObject.value == null);
653+
}
602654
_chromeLogger.info('\$dartHotRestartDwds request complete.');
603655
} on WipError catch (exception) {
604656
final code = exception.error?['code'];
@@ -620,7 +672,7 @@ Future<Map<String, dynamic>> _hotRestart(
620672
};
621673
}
622674
_chromeLogger.info('Waiting for Isolate Start event.');
623-
await stream.firstWhere((event) => event.kind == EventKind.kIsolateStart);
675+
await waitForIsolateStarted;
624676
chromeProxyService.terminatingIsolates = false;
625677

626678
_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)