Skip to content

Commit e95af5f

Browse files
authored
Wait until all scripts are parsed before recomputing metadata on a hot reload (#2650)
Fixes #2640 On a hot reload, we wait until all the scripts are downloaded, but we don't wait until all of them are parsed and a script ID is created for them to refer to. This then results in incorrect metadata being computed. Instead, return the srcs that are being loaded during a hot reload, use a controller to determine when scripts are parsed, and only compute metadata once we have parsed all the reloaded scripts. In order to compare the parsed scripts' URLs with the reloaded scripts' URLs, we now require full URLs in the hot reload sources metadata. This is already true in Flutter tools, so this just canonicalizes that and modifies the tests to do the same. To be consistent, hot restart also provides the full URL in the DDC library bundle format and the bootstrap is modified to reflect that.
1 parent 4dbddc8 commit e95af5f

17 files changed

+251
-147
lines changed

dwds/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## 24.4.1-wip
22

3+
- Fixed an issue where we didn't wait until all scripts were parsed before
4+
recomputing metadata on a hot reload.
5+
36
## 24.4.0
47

58
- Added support for breakpoint registering on a hot reload with the DDC library bundle format using PausePostRequests.

dwds/lib/src/debugging/debugger.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ class Debugger extends Domain {
5656

5757
int _frameErrorCount = 0;
5858

59+
final StreamController<String> parsedScriptsController =
60+
StreamController.broadcast();
61+
5962
Debugger._(
6063
this._remoteDebugger,
6164
this._streamNotify,
@@ -489,6 +492,9 @@ class Debugger extends Domain {
489492
final scriptPath = _pathForChromeScript(e.script.url);
490493
if (scriptPath != null) {
491494
chromePathToRuntimeScriptId[scriptPath] = e.script.scriptId;
495+
if (parsedScriptsController.hasListener) {
496+
parsedScriptsController.sink.add(e.script.url);
497+
}
492498
}
493499
}
494500

dwds/lib/src/debugging/modules.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,6 @@ class Modules {
9898
) async {
9999
final serverPath = await globalToolConfiguration.loadStrategy
100100
.serverPathForModule(entrypoint, module);
101-
// TODO(srujzs): We should wait until all scripts are parsed before
102-
// accessing after a hot reload. See
103-
// https://github.com/dart-lang/webdev/issues/2640.
104101
return chromePathToRuntimeScriptId[serverPath];
105102
}
106103

dwds/lib/src/injected/client.js

Lines changed: 11 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dwds/lib/src/loaders/ddc_library_bundle.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,16 @@ class DdcLibraryBundleStrategy extends LoadStrategy {
110110
/// ```json
111111
/// [
112112
/// {
113-
/// "src": "<file_name>",
113+
/// "src": "<base_uri>/<file_name>",
114+
/// "module": "<module_name>",
114115
/// "libraries": ["<lib1>", "<lib2>"],
115116
/// },
116117
/// ]
117118
/// ```
118119
///
119120
/// `src`: A string that corresponds to the file path containing a DDC library
120121
/// bundle.
122+
/// `module`: The name of the library bundle in `src`.
121123
/// `libraries`: An array of strings containing the libraries that were
122124
/// compiled in `src`.
123125
///

dwds/lib/src/services/chrome_proxy_service.dart

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,15 +1221,42 @@ class ChromeProxyService implements VmServiceInterface {
12211221
Future<void> _performClientSideHotReload(String isolateId) async {
12221222
_logger.info('Attempting a hot reload');
12231223

1224+
final debugger = await debuggerFuture;
1225+
final reloadedSrcs = <String>{};
1226+
final computedReloadedSrcs = Completer<void>();
1227+
final parsedAllReloadedSrcs = Completer<void>();
1228+
// Wait until all the reloaded scripts are parsed before we reinitialize
1229+
// metadata below.
1230+
final parsedScriptsSubscription = debugger.parsedScriptsController.stream
1231+
.listen((url) {
1232+
computedReloadedSrcs.future.then((_) {
1233+
reloadedSrcs.remove(Uri.parse(url).normalizePath().path);
1234+
if (reloadedSrcs.isEmpty) {
1235+
parsedAllReloadedSrcs.complete();
1236+
}
1237+
});
1238+
});
1239+
12241240
// Initiate a hot reload.
12251241
_logger.info('Issuing \$dartHotReloadStartDwds request');
12261242
final remoteObject = await inspector.jsEvaluate(
12271243
'\$dartHotReloadStartDwds();',
12281244
awaitPromise: true,
12291245
returnByValue: true,
12301246
);
1231-
final reloadedModulesToLibraries =
1232-
(remoteObject.value as Map).cast<String, List>();
1247+
final reloadedSrcModuleLibraries = (remoteObject.value as List).cast<Map>();
1248+
final reloadedModulesToLibraries = <String, List<String>>{};
1249+
for (final srcModuleLibrary in reloadedSrcModuleLibraries) {
1250+
final srcModuleLibraryCast = srcModuleLibrary.cast<String, Object>();
1251+
reloadedSrcs.add(
1252+
Uri.parse(srcModuleLibraryCast['src'] as String).normalizePath().path,
1253+
);
1254+
reloadedModulesToLibraries[srcModuleLibraryCast['module'] as String] =
1255+
(srcModuleLibraryCast['libraries'] as List).cast<String>();
1256+
}
1257+
computedReloadedSrcs.complete();
1258+
if (reloadedSrcs.isNotEmpty) await parsedAllReloadedSrcs.future;
1259+
await parsedScriptsSubscription.cancel();
12331260

12341261
if (!pauseIsolatesOnStart) {
12351262
// Finish hot reload immediately.

dwds/test/common/chrome_proxy_service_common.dart

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2225,27 +2225,6 @@ void runTests({
22252225
);
22262226
});
22272227

2228-
test('reloadSources', () async {
2229-
final service = context.service;
2230-
final vm = await service.getVM();
2231-
final isolateId = vm.isolates!.first.id!;
2232-
final report = await service.reloadSources(isolateId);
2233-
// TODO(srujzs): This naturally fails regardless of the module format
2234-
// because we didn't set up prerequisite state (the reload scripts). We
2235-
// should create new tests for hot reload within DWDS and remove this
2236-
// test.
2237-
expect(report.success, false);
2238-
// Make sure that a notice was provided in the expected format.
2239-
final notices = report.json?['notices'];
2240-
expect(notices, isNotNull);
2241-
expect(notices is List, isTrue);
2242-
final noticeList = (notices as List).cast<Map>();
2243-
expect(noticeList, isNotEmpty);
2244-
final message = noticeList[0]['message'];
2245-
expect(message, isNotNull);
2246-
expect(message is String && message.isNotEmpty, isTrue);
2247-
});
2248-
22492228
test('setIsolatePauseMode', () async {
22502229
final service = context.service;
22512230
final vm = await service.getVM();

dwds/test/fixtures/context.dart

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -339,11 +339,9 @@ class TestContext {
339339
_hostname = appMetadata.hostname;
340340
await webRunner.run(
341341
frontendServerFileSystem,
342-
_hostname,
343-
assetServerPort,
344-
filePathToServe,
345-
initialCompile: true,
346-
fullRestart: false,
342+
hostname: _hostname,
343+
port: assetServerPort,
344+
index: filePathToServe,
347345
);
348346

349347
if (testSettings.enableExpressionEvaluation) {
@@ -433,6 +431,10 @@ class TestContext {
433431
final appConnectionCompleter = Completer();
434432
final connection = ChromeConnection('localhost', debugPort);
435433

434+
// TODO(srujzs): In the case of the frontend server, it doesn't make sense
435+
// that we initialize a new HTTP server instead of reusing the one in
436+
// `TestAssetServer`. We should instead use that one to align with Flutter
437+
// tools.
436438
_testServer = await TestServer.start(
437439
debugSettings: debugSettings.copyWith(
438440
expressionCompiler: expressionCompiler,
@@ -590,13 +592,9 @@ class TestContext {
590592
}
591593

592594
Future<void> recompile({required bool fullRestart}) async {
593-
await webRunner.run(
594-
frontendServerFileSystem,
595-
_hostname,
596-
await findUnusedPort(),
597-
webCompatiblePath([project.directoryToServe, project.filePathToServe]),
598-
initialCompile: false,
595+
await webRunner.rerun(
599596
fullRestart: fullRestart,
597+
fileServerUri: Uri.parse('http://${testServer.host}:${testServer.port}'),
600598
);
601599
return;
602600
}

0 commit comments

Comments
 (0)