Skip to content

Commit b61423d

Browse files
authored
Invalidate caches after a hot reload (#2641)
Many of the caches we computed on the original load need to be invalidated. Previously, we pessimistically tore down the caches and reinitialized them. With this PR, a ModifiedModuleReport is computed that allows us to only invalidate and recompute information for modules/libraries that were either deleted or reloaded. Specifically, the caches in MetadataProvider, Modules, Locations, SkipLists, AppInspector, and LibraryHelper are now specially invalidated and recomputed. Removes an existing option to use the module URI instead of the module name in the MetadataProvider. This option is not relevant until we can unify this change across the ecosystem. Fixes #2628.
1 parent f9f8e13 commit b61423d

27 files changed

+839
-259
lines changed

dwds/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## 24.4.0-wip
1+
## 24.4.0
22

33
- Added support for breakpoint registering on a hot reload with the DDC library bundle format using PausePostRequests.
4+
- `FrontendServerDdcLibraryBundleStrategy.hotReloadSourceUri` is now expected to also provide the reloaded modules.
45

56
## 24.3.11
67

dwds/lib/src/debugging/debugger.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ class Debugger extends Domain {
594594
params: {
595595
'skipList': _skipLists.compute(
596596
scriptId,
597+
url,
597598
await _locations.locationsForUrl(url),
598599
),
599600
},

dwds/lib/src/debugging/inspector.dart

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:dwds/src/debugging/execution_context.dart';
1313
import 'package:dwds/src/debugging/instance.dart';
1414
import 'package:dwds/src/debugging/libraries.dart';
1515
import 'package:dwds/src/debugging/location.dart';
16+
import 'package:dwds/src/debugging/metadata/provider.dart';
1617
import 'package:dwds/src/debugging/remote_debugger.dart';
1718
import 'package:dwds/src/loaders/ddc_library_bundle.dart';
1819
import 'package:dwds/src/readers/asset_reader.dart';
@@ -34,7 +35,9 @@ import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
3435
class AppInspector implements AppInspectorInterface {
3536
var _scriptCacheMemoizer = AsyncMemoizer<List<ScriptRef>>();
3637

37-
Future<List<ScriptRef>> get scriptRefs => _populateScriptCaches();
38+
Future<List<ScriptRef>> getScriptRefs({
39+
ModifiedModuleReport? modifiedModuleReport,
40+
}) => _populateScriptCaches(modifiedModuleReport: modifiedModuleReport);
3841

3942
final _logger = Logger('AppInspector');
4043

@@ -103,24 +106,35 @@ class AppInspector implements AppInspectorInterface {
103106

104107
/// Reset all caches and recompute any mappings.
105108
///
106-
/// Should be called across hot reloads.
107-
Future<void> initialize() async {
109+
/// Should be called across hot reloads with a valid [ModifiedModuleReport].
110+
Future<void> initialize({ModifiedModuleReport? modifiedModuleReport}) async {
108111
_scriptCacheMemoizer = AsyncMemoizer<List<ScriptRef>>();
109-
_scriptRefsById.clear();
110-
_serverPathToScriptRef.clear();
111-
_scriptIdToLibraryId.clear();
112-
_libraryIdToScriptRefs.clear();
113112

114-
_libraryHelper = LibraryHelper(this);
113+
// TODO(srujzs): We can invalidate these in a smarter way instead of
114+
// reinitializing when doing a hot reload, but these helpers recompute info
115+
// on demand later and therefore are not in the critical path.
115116
_classHelper = ClassHelper(this);
116117
_instanceHelper = InstanceHelper(this);
117118

119+
if (modifiedModuleReport != null) {
120+
// Invalidate `_libraryHelper` as we use it populate any script caches.
121+
_libraryHelper.initialize(modifiedModuleReport: modifiedModuleReport);
122+
} else {
123+
_libraryHelper = LibraryHelper(this)..initialize();
124+
_scriptRefsById.clear();
125+
_serverPathToScriptRef.clear();
126+
_scriptIdToLibraryId.clear();
127+
_libraryIdToScriptRefs.clear();
128+
}
129+
118130
final libraries = await _libraryHelper.libraryRefs;
119131
isolate.rootLib = await _libraryHelper.rootLib;
120132
isolate.libraries?.clear();
121133
isolate.libraries?.addAll(libraries);
122134

123-
final scripts = await scriptRefs;
135+
final scripts = await getScriptRefs(
136+
modifiedModuleReport: modifiedModuleReport,
137+
);
124138

125139
await DartUri.initialize();
126140
DartUri.recordAbsoluteUris(libraries.map((lib) => lib.uri).nonNulls);
@@ -583,7 +597,7 @@ class AppInspector implements AppInspectorInterface {
583597
/// All the scripts in the isolate.
584598
@override
585599
Future<ScriptList> getScripts() async {
586-
return ScriptList(scripts: await scriptRefs);
600+
return ScriptList(scripts: await getScriptRefs());
587601
}
588602

589603
/// Calls the Chrome Runtime.getProperties API for the object with [objectId].
@@ -714,19 +728,50 @@ class AppInspector implements AppInspectorInterface {
714728
///
715729
/// This will get repopulated on restarts and reloads.
716730
///
731+
/// If [modifiedModuleReport] is provided, only invalidates and
732+
/// recalculates caches for the modified libraries.
733+
///
717734
/// Returns the list of scripts refs cached.
718-
Future<List<ScriptRef>> _populateScriptCaches() {
735+
Future<List<ScriptRef>> _populateScriptCaches({
736+
ModifiedModuleReport? modifiedModuleReport,
737+
}) {
719738
return _scriptCacheMemoizer.runOnce(() async {
720739
final scripts =
721740
await globalToolConfiguration.loadStrategy
722741
.metadataProviderFor(appConnection.request.entrypointPath)
723742
.scripts;
743+
if (modifiedModuleReport != null) {
744+
// Invalidate any script caches that were computed for the now invalid
745+
// libraries. They will get repopulated below.
746+
for (final libraryUri in modifiedModuleReport.modifiedLibraries) {
747+
final libraryRef = await _libraryHelper.libraryRefFor(libraryUri);
748+
final libraryId = libraryRef?.id;
749+
// If this was not a pre-existing library, nothing to invalidate.
750+
if (libraryId == null) continue;
751+
final scriptRefs = _libraryIdToScriptRefs.remove(libraryId);
752+
if (scriptRefs == null) continue;
753+
for (final scriptRef in scriptRefs) {
754+
final scriptId = scriptRef.id;
755+
final scriptUri = scriptRef.uri;
756+
if (scriptId != null && scriptUri != null) {
757+
_scriptRefsById.remove(scriptId);
758+
_scriptIdToLibraryId.remove(scriptId);
759+
_serverPathToScriptRef.remove(
760+
DartUri(scriptUri, _root).serverPath,
761+
);
762+
}
763+
}
764+
}
765+
}
724766
// For all the non-dart: libraries, find their parts and create scriptRefs
725767
// for them.
726768
final userLibraries = _userLibraryUris(
727769
isolate.libraries ?? <LibraryRef>[],
728770
);
729771
for (final uri in userLibraries) {
772+
if (modifiedModuleReport?.modifiedLibraries.contains(uri) == false) {
773+
continue;
774+
}
730775
final parts = scripts[uri];
731776
final scriptRefs = [
732777
ScriptRef(uri: uri, id: createId()),

dwds/lib/src/debugging/libraries.dart

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:collection/collection.dart';
66
import 'package:dwds/src/config/tool_configuration.dart';
77
import 'package:dwds/src/debugging/metadata/class.dart';
8+
import 'package:dwds/src/debugging/metadata/provider.dart';
89
import 'package:dwds/src/services/chrome_debug_exception.dart';
910
import 'package:dwds/src/utilities/domain.dart';
1011
import 'package:logging/logging.dart';
@@ -27,6 +28,29 @@ class LibraryHelper extends Domain {
2728
inspector = appInspector;
2829
}
2930

31+
/// Initialize any caches.
32+
///
33+
/// If [modifiedModuleReport] is not null, invalidates only modified libraries
34+
/// from the cache and recomputes values for any eager caches.
35+
void initialize({ModifiedModuleReport? modifiedModuleReport}) {
36+
_rootLib = null;
37+
if (modifiedModuleReport != null) {
38+
for (final library in modifiedModuleReport.modifiedLibraries) {
39+
// These will later be initialized by `libraryFor` if needed.
40+
_librariesById.remove(library);
41+
_libraryRefsById.remove(library);
42+
}
43+
for (final library in modifiedModuleReport.reloadedLibraries) {
44+
// These need to be recomputed here as `libraryRefs` only checks if this
45+
// map is empty before returning.
46+
_libraryRefsById[library] = _createLibraryRef(library);
47+
}
48+
return;
49+
}
50+
_librariesById.clear();
51+
_libraryRefsById.clear();
52+
}
53+
3054
Future<LibraryRef> get rootLib async {
3155
if (_rootLib != null) return _rootLib!;
3256
final libraries = await libraryRefs;
@@ -51,21 +75,21 @@ class LibraryHelper extends Domain {
5175
return _rootLib!;
5276
}
5377

78+
LibraryRef _createLibraryRef(String library) =>
79+
LibraryRef(id: library, name: library, uri: library);
80+
5481
/// Returns all libraryRefs in the app.
5582
///
56-
/// Note this can return a cached result.
83+
/// Note this can return a cached result that can be selectively reinitialized
84+
/// using [initialize].
5785
Future<List<LibraryRef>> get libraryRefs async {
5886
if (_libraryRefsById.isNotEmpty) return _libraryRefsById.values.toList();
5987
final libraries =
6088
await globalToolConfiguration.loadStrategy
6189
.metadataProviderFor(inspector.appConnection.request.entrypointPath)
6290
.libraries;
6391
for (final library in libraries) {
64-
_libraryRefsById[library] = LibraryRef(
65-
id: library,
66-
name: library,
67-
uri: library,
68-
);
92+
_libraryRefsById[library] = _createLibraryRef(library);
6993
}
7094
return _libraryRefsById.values.toList();
7195
}

dwds/lib/src/debugging/location.dart

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:async/async.dart';
66
import 'package:dwds/src/config/tool_configuration.dart';
7+
import 'package:dwds/src/debugging/metadata/provider.dart';
78
import 'package:dwds/src/debugging/modules.dart';
89
import 'package:dwds/src/readers/asset_reader.dart';
910
import 'package:dwds/src/utilities/dart_uri.dart';
@@ -151,11 +152,34 @@ class Locations {
151152

152153
Modules get modules => _modules;
153154

154-
void initialize(String entrypoint) {
155-
_sourceToTokenPosTable.clear();
156-
_sourceToLocation.clear();
155+
/// Initialize any caches.
156+
///
157+
/// If [modifiedModuleReport] is not null, only invalidates the caches for the
158+
/// modified modules instead.
159+
Future<void> initialize(
160+
String entrypoint, {
161+
ModifiedModuleReport? modifiedModuleReport,
162+
}) async {
163+
// If we know that only certain modules are deleted or added, we can only
164+
// invalidate those.
165+
if (modifiedModuleReport != null) {
166+
for (final module in modifiedModuleReport.modifiedModules) {
167+
_locationMemoizer.remove(module);
168+
_moduleToLocations.remove(module);
169+
final sources = await _modules.sourcesForModule(module);
170+
if (sources != null) {
171+
for (final serverPath in sources) {
172+
_sourceToTokenPosTable.remove(serverPath);
173+
_sourceToLocation.remove(serverPath);
174+
}
175+
}
176+
}
177+
return;
178+
}
157179
_locationMemoizer.clear();
158180
_moduleToLocations.clear();
181+
_sourceToTokenPosTable.clear();
182+
_sourceToLocation.clear();
159183
_entrypoint = entrypoint;
160184
}
161185

0 commit comments

Comments
 (0)