Skip to content

Commit 6961b20

Browse files
author
Anna Gringauze
authored
Fix a crash during execution context detection (#2286)
* Make dwds tolerant to failure of detecting dart execution context id * Update changelog * Addressed CR comments * Cleanup * Cleanup
1 parent e67071c commit 6961b20

File tree

10 files changed

+345
-20
lines changed

10 files changed

+345
-20
lines changed

dwds/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 23.0.0-wip
22
- Restructure `LoadStrategy` to provide build settings. - [#2270](https://github.com/dart-lang/webdev/pull/2270)
33
- Add `FrontendServerLegacyStrategyProvider` and update bootstrap generation logic for `LegacyStrategy` - [#2285](https://github.com/dart-lang/webdev/pull/2285)
4+
- Tolerate failures to detect a dart execution context. - [#2286](https://github.com/dart-lang/webdev/pull/2286)
45

56
## 22.1.0
67
- Update `package:vm_service` constraint to `^13.0.0`. - [#2265](https://github.com/dart-lang/webdev/pull/2265)

dwds/lib/src/connections/debug_connection.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class DebugConnection {
3737
VmService get vmService => _appDebugServices.dwdsVmClient.client;
3838

3939
Future<void> close() => _closed ??= () async {
40-
_appDebugServices.chromeProxyService.remoteDebugger.close();
40+
await _appDebugServices.chromeProxyService.remoteDebugger.close();
4141
await _appDebugServices.close();
4242
_onDoneCompleter.complete();
4343
}();

dwds/lib/src/debugging/execution_context.dart

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import 'package:dwds/src/debugging/remote_debugger.dart';
99
import 'package:logging/logging.dart';
1010

1111
abstract class ExecutionContext {
12-
/// Returns the context ID that contains the running Dart application.
13-
Future<int> get id;
12+
/// Returns the context ID that contains the running Dart application,
13+
/// if available.
14+
Future<int?> get id;
1415
}
1516

1617
/// The execution context in which to do remote evaluations.
@@ -23,12 +24,12 @@ class RemoteDebuggerExecutionContext extends ExecutionContext {
2324
/// Context can be null if an error has occurred and we cannot detect
2425
/// and parse the context ID.
2526
late StreamQueue<int> _contexts;
27+
final _contextController = StreamController<int>();
28+
final _seenContexts = <int>[];
2629

2730
int? _id;
2831

29-
@override
30-
Future<int> get id async {
31-
if (_id != null) return _id!;
32+
Future<int?> _lookUpId() async {
3233
_logger.fine('Looking for Dart execution context...');
3334
const timeoutInMs = 100;
3435
while (await _contexts.hasNext.timeout(
@@ -41,6 +42,7 @@ class RemoteDebuggerExecutionContext extends ExecutionContext {
4142
},
4243
)) {
4344
final context = await _contexts.next;
45+
_seenContexts.add(context);
4446
_logger.fine('Checking context id: $context');
4547
try {
4648
final result = await _remoteDebugger.sendCommand(
@@ -51,24 +53,34 @@ class RemoteDebuggerExecutionContext extends ExecutionContext {
5153
},
5254
);
5355
if (result.result?['result']?['value'] != null) {
54-
_logger.fine('Found valid execution context: $context');
55-
_id = context;
56-
break;
56+
_logger.fine('Found dart execution context: $context');
57+
return context;
5758
}
5859
} catch (_) {
5960
// Errors may be thrown if we attempt to evaluate in a stale a context.
6061
// Ignore and continue.
61-
_logger.fine('Invalid execution context: $context');
62+
_logger.fine('Stale execution context: $context');
63+
_seenContexts.remove(context);
6264
}
6365
}
66+
return null;
67+
}
68+
69+
@override
70+
Future<int?> get id async {
71+
if (_id != null) return _id;
72+
73+
_id = await _lookUpId();
6474
if (_id == null) {
65-
throw StateError('No context with the running Dart application.');
75+
// Add seen contexts back to the queue in case the injected
76+
// client is still loading, so the next call to `id` succeeds.
77+
_seenContexts.forEach(_contextController.add);
78+
_seenContexts.clear();
6679
}
67-
return _id!;
80+
return _id;
6881
}
6982

7083
RemoteDebuggerExecutionContext(this._id, this._remoteDebugger) {
71-
final contextController = StreamController<int>();
7284
_remoteDebugger
7385
.eventStream('Runtime.executionContextsCleared', (e) => e)
7486
.listen((_) => _id = null);
@@ -86,8 +98,8 @@ class RemoteDebuggerExecutionContext extends ExecutionContext {
8698
}
8799
return parsedId;
88100
}).listen((e) {
89-
if (e != null) contextController.add(e);
101+
if (e != null) _contextController.add(e);
90102
});
91-
_contexts = StreamQueue(contextController.stream);
103+
_contexts = StreamQueue(_contextController.stream);
92104
}
93105
}

dwds/lib/src/debugging/remote_debugger.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,5 @@ abstract class RemoteDebugger {
7272

7373
Stream<T> eventStream<T>(String method, WipEventTransformer<T> transformer);
7474

75-
void close();
75+
Future<void> close();
7676
}

dwds/lib/src/debugging/webkit_debugger.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class WebkitDebugger implements RemoteDebugger {
3232
_wipDebugger.sendCommand(command, params: params);
3333

3434
@override
35-
void close() => _closed ??= _wipDebugger.connection.close();
35+
Future<void> close() => _closed ??= _wipDebugger.connection.close();
3636

3737
@override
3838
Future<void> disable() => _wipDebugger.disable();

dwds/lib/src/dwds_vm_client.dart

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,19 @@ void _recordDwdsStats(DwdsStats dwdsStats, String screen) {
213213
}
214214
}
215215

216+
Future<int> tryGetContextId(
217+
ChromeProxyService chromeProxyService, {
218+
int retries = 3,
219+
}) async {
220+
const waitInMs = 50;
221+
for (var retry = 0; retry < retries; retry++) {
222+
final tryId = await chromeProxyService.executionContext.id;
223+
if (tryId != null) return tryId;
224+
await Future.delayed(const Duration(milliseconds: waitInMs));
225+
}
226+
throw StateError('No context with the running Dart application.');
227+
}
228+
216229
Future<Map<String, dynamic>> _hotRestart(
217230
ChromeProxyService chromeProxyService,
218231
VmService client,
@@ -223,7 +236,7 @@ Future<Map<String, dynamic>> _hotRestart(
223236
await _disableBreakpointsAndResume(client, chromeProxyService);
224237
try {
225238
_logger.info('Attempting to get execution context ID.');
226-
await chromeProxyService.executionContext.id;
239+
await tryGetContextId(chromeProxyService);
227240
_logger.info('Got execution context ID.');
228241
} on StateError catch (e) {
229242
// We couldn't find the execution context. `hotRestart` may have been

dwds/lib/src/loaders/legacy.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class LegacyStrategy extends LoadStrategy {
6666
/// packages/path/path -> d348c2a4647e998011fe305f74f22961
6767
///
6868
final Future<Map<String, String>> Function(MetadataProvider metadataProvider)
69+
// ignore: unused_field
6970
_digestsProvider;
7071

7172
/// Returns the module for the corresponding server path.

dwds/lib/src/servers/extension_debugger.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ class ExtensionDebugger implements RemoteDebugger {
192192
int newId() => _completerId++;
193193

194194
@override
195-
void close() => _closed ??= () {
195+
Future<void> close() => _closed ??= () {
196196
_closeController.add({});
197197
return Future.wait([
198198
sseConnection.sink.close(),

0 commit comments

Comments
 (0)