Skip to content

Commit a15ad81

Browse files
committed
Optimize skip lists and locations
1 parent 79a6660 commit a15ad81

File tree

13 files changed

+162
-45
lines changed

13 files changed

+162
-45
lines changed

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/location.dart

Lines changed: 28 additions & 6 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,12 +152,33 @@ class Locations {
151152

152153
Modules get modules => _modules;
153154

154-
void initialize(String entrypoint) {
155-
_sourceToTokenPosTable.clear();
156-
_sourceToLocation.clear();
157-
_locationMemoizer.clear();
158-
_moduleToLocations.clear();
159-
_entrypoint = entrypoint;
155+
Future<void> initialize(
156+
String entrypoint, [
157+
InvalidatedModuleReport? invalidatedModuleReport,
158+
]) async {
159+
// If we know that only certain modules are deleted or added, we can only
160+
// invalidate those.
161+
if (invalidatedModuleReport != null) {
162+
for (final module in invalidatedModuleReport.deletedModules.union(
163+
invalidatedModuleReport.reloadedModules,
164+
)) {
165+
_locationMemoizer.remove(module);
166+
_moduleToLocations.remove(module);
167+
final sources = await _modules.sourcesForModule(module);
168+
if (sources != null) {
169+
for (final serverPath in sources) {
170+
_sourceToTokenPosTable.remove(serverPath);
171+
_sourceToLocation.remove(serverPath);
172+
}
173+
}
174+
}
175+
} else {
176+
_sourceToTokenPosTable.clear();
177+
_locationMemoizer.clear();
178+
_sourceToLocation.clear();
179+
_moduleToLocations.clear();
180+
_entrypoint = entrypoint;
181+
}
160182
}
161183

162184
/// Returns all [Location] data for a provided Dart source.

dwds/lib/src/debugging/metadata/provider.dart

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,9 @@ class MetadataProvider {
215215
await _metadataMemoizer.runOnce(() => _processMetadata(false));
216216
}
217217

218-
Future<void> reinitializeAfterReload(Set<String> reloadedModules) async {
218+
Future<InvalidatedModuleReport> reinitializeAfterReload(
219+
Map<String, List> reloadedModulesToLibraries,
220+
) async {
219221
final modules = (await _processMetadata(true))!;
220222
final invalidatedLibraries = <String>{};
221223
void invalidateLibrary(String libraryImportUri) {
@@ -227,10 +229,9 @@ class MetadataProvider {
227229
}
228230

229231
final deletedModules = <String>{};
230-
// final invalidatedModules = <String>{};
231232
for (final module in _moduleToLibraries.keys) {
232233
final deletedModule = !modules.containsKey(module);
233-
final invalidatedModule = reloadedModules.contains(module);
234+
final invalidatedModule = reloadedModulesToLibraries.containsKey(module);
234235
assert(!(deletedModule && invalidatedModule));
235236
// If the module was either deleted or reloaded, invalidate all previous
236237
// information both about the module and its libraries.
@@ -242,14 +243,27 @@ class MetadataProvider {
242243
}
243244
if (deletedModule) deletedModules.add(module);
244245
}
245-
for (final module in reloadedModules) {
246+
final reloadedModules = <String>{};
247+
final reloadedLibraries = <String>{};
248+
for (final module in reloadedModulesToLibraries.keys) {
249+
reloadedModules.add(module);
250+
reloadedLibraries.addAll(
251+
reloadedModulesToLibraries[module]!.cast<String>(),
252+
);
246253
_addMetadata(modules[module]!);
247254
}
248255
// The libraries that were removed from the program or those that we
249256
// invalidated but were never added again.
250-
// final deletedLibraries = invalidatedLibraries.where(
251-
// (library) => !_libraries.contains(library),
252-
// );
257+
final deletedLibraries =
258+
invalidatedLibraries
259+
.where((library) => !_libraries.contains(library))
260+
.toSet();
261+
return InvalidatedModuleReport(
262+
deletedModules: deletedModules,
263+
deletedLibraries: deletedLibraries,
264+
reloadedModules: reloadedModules,
265+
reloadedLibraries: reloadedLibraries,
266+
);
253267
}
254268

255269
void _addMetadata(ModuleMetadata metadata) {
@@ -303,3 +317,18 @@ class AbsoluteImportUriException implements Exception {
303317
@override
304318
String toString() => "AbsoluteImportUriError: '$importUri'";
305319
}
320+
321+
class InvalidatedModuleReport {
322+
final Set<String> deletedModules;
323+
final Set<String> deletedLibraries;
324+
// The union of invalidated and new modules.
325+
final Set<String> reloadedModules;
326+
// The union of invalidated and new libraries.
327+
final Set<String> reloadedLibraries;
328+
InvalidatedModuleReport({
329+
required this.deletedModules,
330+
required this.deletedLibraries,
331+
required this.reloadedModules,
332+
required this.reloadedLibraries,
333+
});
334+
}

dwds/lib/src/debugging/modules.dart

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:async/async.dart';
66
import 'package:dwds/src/config/tool_configuration.dart';
77
import 'package:dwds/src/debugging/debugger.dart';
8+
import 'package:dwds/src/debugging/metadata/provider.dart';
89
import 'package:dwds/src/utilities/dart_uri.dart';
910
import 'package:logging/logging.dart';
1011

@@ -16,6 +17,9 @@ class Modules {
1617
// The Dart server path to containing module.
1718
final _sourceToModule = <String, String>{};
1819

20+
// Module to Dart server paths.
21+
final _moduleToSources = <String, Set<String>>{};
22+
1923
// The Dart server path to library import uri
2024
final _sourceToLibrary = <String, Uri>{};
2125
var _moduleMemoizer = AsyncMemoizer<void>();
@@ -31,9 +35,13 @@ class Modules {
3135
/// Intended to be called multiple times throughout the development workflow,
3236
/// e.g. after a hot-reload.
3337
void initialize(String entrypoint) {
34-
// We only clear the source to module mapping as script IDs may persist
35-
// across hot reloads.
38+
// TODO(srujzs): Can we do better and only invalidate the sources/modules
39+
// that were deleted/reloaded? This would require removing the
40+
// deleted/reloaded libraries/sources/modules from the following maps and
41+
// then only processing that set in `_initializeMapping`. It's doable, but
42+
// these calculations are also not that expensive.
3643
_sourceToModule.clear();
44+
_moduleToSources.clear();
3745
_sourceToLibrary.clear();
3846
_libraryToModule.clear();
3947
_moduleMemoizer = AsyncMemoizer();
@@ -46,6 +54,12 @@ class Modules {
4654
return _sourceToModule[serverPath];
4755
}
4856

57+
/// Returns the Dart server paths for the provided module.
58+
Future<Set<String>?> sourcesForModule(String module) async {
59+
await _moduleMemoizer.runOnce(_initializeMapping);
60+
return _moduleToSources[module];
61+
}
62+
4963
/// Returns the containing library importUri for the provided Dart server path.
5064
Future<Uri?> libraryForSource(String serverPath) async {
5165
await _moduleMemoizer.runOnce(_initializeMapping);
@@ -69,11 +83,15 @@ class Modules {
6983
) async {
7084
final serverPath = await globalToolConfiguration.loadStrategy
7185
.serverPathForModule(entrypoint, module);
86+
// TODO(srujzs): We should wait until all scripts are parsed before
87+
// accessing.
7288
return chromePathToRuntimeScriptId[serverPath];
7389
}
7490

75-
/// Initializes [_sourceToModule] and [_sourceToLibrary].
76-
Future<void> _initializeMapping() async {
91+
/// Initializes [_sourceToModule], [_moduleToSources], and [_sourceToLibrary].
92+
Future<void> _initializeMapping([
93+
InvalidatedModuleReport? invalidatedModuleReport,
94+
]) async {
7795
final provider = globalToolConfiguration.loadStrategy.metadataProviderFor(
7896
_entrypoint,
7997
);
@@ -92,6 +110,7 @@ class Modules {
92110
final module = scriptToModule[library]!;
93111

94112
_sourceToModule[libraryServerPath] = module;
113+
_moduleToSources.putIfAbsent(module, () => {}).add(libraryServerPath);
95114
_sourceToLibrary[libraryServerPath] = Uri.parse(library);
96115
_libraryToModule[library] = module;
97116

@@ -102,6 +121,7 @@ class Modules {
102121
: DartUri(script, _root).serverPath;
103122

104123
_sourceToModule[scriptServerPath] = module;
124+
_moduleToSources[module]!.add(scriptServerPath);
105125
_sourceToLibrary[scriptServerPath] = Uri.parse(library);
106126
}
107127
} else {

dwds/lib/src/debugging/skip_list.dart

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,59 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:dwds/src/config/tool_configuration.dart';
56
import 'package:dwds/src/debugging/location.dart';
7+
import 'package:dwds/src/debugging/metadata/provider.dart';
8+
import 'package:dwds/src/utilities/dart_uri.dart';
69

710
const maxValue = 2147483647;
811

912
class SkipLists {
1013
// Map of script ID to scriptList.
1114
final _idToList = <String, List<Map<String, dynamic>>>{};
15+
// Map of url to script ID.
16+
final _urlToId = <String, String>{};
17+
final String _root;
1218

13-
void initialize() => _idToList.clear();
19+
SkipLists(this._root);
20+
21+
Future<void> initialize([
22+
String? entrypoint,
23+
InvalidatedModuleReport? invalidatedModuleReport,
24+
]) async {
25+
if (invalidatedModuleReport != null) {
26+
assert(entrypoint != null);
27+
final invalidatedModules = invalidatedModuleReport.deletedModules.union(
28+
invalidatedModuleReport.reloadedModules,
29+
);
30+
for (final url in _urlToId.keys) {
31+
if (url.isEmpty) continue;
32+
33+
final dartUri = DartUri(url, _root);
34+
final serverPath = dartUri.serverPath;
35+
final module = await globalToolConfiguration.loadStrategy
36+
.moduleForServerPath(entrypoint!, serverPath);
37+
if (invalidatedModules.contains(module)) {
38+
_idToList.remove(_urlToId[url]!);
39+
}
40+
}
41+
} else {
42+
_idToList.clear();
43+
}
44+
}
1445

1546
/// Returns a skipList as defined by the Chrome DevTools Protocol.
1647
///
1748
/// A `skipList` is an array of `LocationRange`s see:
1849
/// https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#method-stepInto
1950
///
2051
/// Can return a cached value.
21-
List<Map<String, dynamic>> compute(String scriptId, Set<Location> locations) {
52+
List<Map<String, dynamic>> compute(
53+
String scriptId,
54+
String url,
55+
Set<Location> locations,
56+
) {
57+
_urlToId[url] = scriptId;
2258
if (_idToList.containsKey(scriptId)) return _idToList[scriptId]!;
2359

2460
final sortedLocations =

dwds/lib/src/loaders/strategy.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,12 @@ abstract class LoadStrategy {
183183
return Future.value();
184184
}
185185

186-
Future<void> reinitializeEntrypointAfterReload(
186+
Future<InvalidatedModuleReport> reinitializeEntrypointAfterReload(
187187
String entrypoint,
188-
Set<String> modules,
188+
Map<String, List> reloadedModulesToLibraries,
189189
) {
190190
final provider = _providers[entrypoint]!;
191-
return provider.reinitializeAfterReload(modules);
191+
return provider.reinitializeAfterReload(reloadedModulesToLibraries);
192192
}
193193
}
194194

dwds/lib/src/services/chrome_proxy_service.dart

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import 'package:dwds/src/debugging/execution_context.dart';
1717
import 'package:dwds/src/debugging/inspector.dart';
1818
import 'package:dwds/src/debugging/instance.dart';
1919
import 'package:dwds/src/debugging/location.dart';
20+
import 'package:dwds/src/debugging/metadata/provider.dart';
2021
import 'package:dwds/src/debugging/modules.dart';
2122
import 'package:dwds/src/debugging/remote_debugger.dart';
2223
import 'package:dwds/src/debugging/skip_list.dart';
@@ -196,7 +197,7 @@ class ChromeProxyService implements VmServiceInterface {
196197

197198
final modules = Modules(root);
198199
final locations = Locations(assetReader, modules, root);
199-
final skipLists = SkipLists();
200+
final skipLists = SkipLists(root);
200201
final service = ChromeProxyService._(
201202
vm,
202203
root,
@@ -245,18 +246,20 @@ class ChromeProxyService implements VmServiceInterface {
245246
Map<String, List> reloadedModules,
246247
) async {
247248
final entrypoint = inspector.appConnection.request.entrypointPath;
248-
final modules = reloadedModules.keys.toSet();
249-
await globalToolConfiguration.loadStrategy
250-
.reinitializeEntrypointAfterReload(entrypoint, modules);
251-
_initializeEntrypoint(entrypoint);
249+
final invalidatedModuleReport = await globalToolConfiguration.loadStrategy
250+
.reinitializeEntrypointAfterReload(entrypoint, reloadedModules);
251+
await _initializeEntrypoint(entrypoint, invalidatedModuleReport);
252252
await inspector.initialize();
253253
}
254254

255255
/// Initializes metadata in [Locations], [Modules], and [ExpressionCompiler].
256-
void _initializeEntrypoint(String entrypoint) {
257-
_locations.initialize(entrypoint);
256+
Future<void> _initializeEntrypoint(
257+
String entrypoint, [
258+
InvalidatedModuleReport? invalidatedModuleReport,
259+
]) async {
258260
_modules.initialize(entrypoint);
259-
_skipLists.initialize();
261+
await _locations.initialize(entrypoint, invalidatedModuleReport);
262+
await _skipLists.initialize(entrypoint, invalidatedModuleReport);
260263
// We do not need to wait for compiler dependencies to be updated as the
261264
// [ExpressionEvaluator] is robust to evaluation requests during updates.
262265
safeUnawaited(_updateCompilerDependencies(entrypoint));

dwds/test/debugger_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ void main() async {
8888
FakeModules(),
8989
root,
9090
);
91-
locations.initialize('fake_entrypoint');
92-
skipLists = SkipLists();
91+
await locations.initialize('fake_entrypoint');
92+
skipLists = SkipLists(root);
9393
debugger = await Debugger.create(
9494
webkitDebugger,
9595
(_, __) {},

dwds/test/expression_evaluator_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ void main() async {
5454
final root = 'fakeRoot';
5555
final entry = 'fake_entrypoint';
5656
final locations = Locations(assetReader, modules, root);
57-
locations.initialize(entry);
57+
await locations.initialize(entry);
5858

59-
final skipLists = SkipLists();
59+
final skipLists = SkipLists(root);
6060
final debugger = await Debugger.create(
6161
webkitDebugger,
6262
(_, e) => debugEventController.sink.add(e),

dwds/test/fixtures/context.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ class TestContext {
412412
'remote-debugging-port=$debugPort',
413413
if (enableDebugExtension)
414414
'--load-extension=debug_extension/prod_build',
415-
if (headless) '--headless',
415+
// if (headless) '--headless',
416416
],
417417
},
418418
});

0 commit comments

Comments
 (0)