Skip to content

Commit ffa619e

Browse files
author
Anna Gringauze
authored
Initialize new isolate after restart (#1305)
* Initiaze new isolate after restart - reset initialization completer on `destroyIsolate` so correct signal is set after `createIsolate` - make all supported VM API wait for isolate initizaliation before proceeding. Towards:flutter/flutter#74903 * Update changelog * Fix evaluate completing before update dependencies * Actually fix expression evaluation before updateDependencies
1 parent 658f692 commit ffa619e

File tree

3 files changed

+115
-20
lines changed

3 files changed

+115
-20
lines changed

dwds/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
- Fix incorrect `rootLib` returned by `ChromeProxyService`.
66
- Fix not working breakpoints in library part files.
77
- Fix data race in calculating locations for a module.
8+
- Fix uninitialized isolate after hot restart.
9+
- Fix intermittent failure caused by evaluation not waiting for dependencies
10+
to be updated.
811

912
## 10.0.1
1013

dwds/lib/src/services/chrome_proxy_service.dart

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,14 @@ class ChromeProxyService implements VmServiceInterface {
5252
/// are dynamic and roughly map to chrome tabs.
5353
final VM _vm;
5454

55-
final _initializedCompleter = Completer<void>();
56-
55+
/// Signals when isolate is intialized.
56+
Completer<void> _initializedCompleter = Completer<void>();
5757
Future<void> get isInitialized => _initializedCompleter.future;
5858

59+
/// Signals when expression compiler is ready to evaluate.
60+
Completer<void> _compilerCompleter = Completer<void>();
61+
Future<void> get isCompilerInitialized => _compilerCompleter.future;
62+
5963
/// The root URI at which we're serving.
6064
final String uri;
6165

@@ -165,10 +169,10 @@ class ChromeProxyService implements VmServiceInterface {
165169
_skipLists.initialize();
166170
// We do not need to wait for compiler dependencies to be udpated as the
167171
// [ExpressionEvaluator] is robust to evaluation requests during updates.
168-
unawaited(updateCompilerDependencies(entrypoint));
172+
unawaited(_updateCompilerDependencies(entrypoint));
169173
}
170174

171-
Future<void> updateCompilerDependencies(String entrypoint) async {
175+
Future<void> _updateCompilerDependencies(String entrypoint) async {
172176
var metadataProvider = globalLoadStrategy.metadataProviderFor(entrypoint);
173177
var moduleFormat = globalLoadStrategy.moduleFormat;
174178
var soundNullSafety = await metadataProvider.soundNullSafety;
@@ -183,6 +187,8 @@ class ChromeProxyService implements VmServiceInterface {
183187
await globalLoadStrategy.moduleInfoForEntrypoint(entrypoint);
184188
var stopwatch = Stopwatch()..start();
185189
await _compiler.updateDependencies(dependencies);
190+
// Expression evaluation is ready after dependencies are updated.
191+
if (!_compilerCompleter.isCompleted) _compilerCompleter.complete();
186192
emitEvent(DwdsEvent('COMPILER_UPDATE_DEPENDENCIES', {
187193
'entrypoint': entrypoint,
188194
'elapsedMilliseconds': stopwatch.elapsedMilliseconds,
@@ -273,6 +279,8 @@ class ChromeProxyService implements VmServiceInterface {
273279
void destroyIsolate() {
274280
var isolate = _inspector?.isolate;
275281
if (isolate == null) return;
282+
_initializedCompleter = Completer<void>();
283+
_compilerCompleter = Completer<void>();
276284
_streamNotify(
277285
'Isolate',
278286
Event(
@@ -299,9 +307,11 @@ class ChromeProxyService implements VmServiceInterface {
299307

300308
@override
301309
Future<Breakpoint> addBreakpoint(String isolateId, String scriptId, int line,
302-
{int column}) async =>
303-
(await _debugger)
304-
.addBreakpoint(isolateId, scriptId, line, column: column);
310+
{int column}) async {
311+
await isInitialized;
312+
return (await _debugger)
313+
.addBreakpoint(isolateId, scriptId, line, column: column);
314+
}
305315

306316
@override
307317
Future<Breakpoint> addBreakpointAtEntry(String isolateId, String functionId) {
@@ -312,6 +322,7 @@ class ChromeProxyService implements VmServiceInterface {
312322
Future<Breakpoint> addBreakpointWithScriptUri(
313323
String isolateId, String scriptUri, int line,
314324
{int column}) async {
325+
await isInitialized;
315326
var dartUri = DartUri(scriptUri, uri);
316327
var ref = await _inspector.scriptRefFor(dartUri.serverPath);
317328
return (await _debugger)
@@ -321,6 +332,7 @@ class ChromeProxyService implements VmServiceInterface {
321332
@override
322333
Future<Response> callServiceExtension(String method,
323334
{String isolateId, Map args}) async {
335+
await isInitialized;
324336
// Validate the isolate id is correct, _getIsolate throws if not.
325337
if (isolateId != null) _getIsolate(isolateId);
326338
args ??= <String, String>{};
@@ -413,7 +425,9 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension(
413425
dynamic error;
414426

415427
try {
428+
await isInitialized;
416429
if (_expressionEvaluator != null) {
430+
await isCompilerInitialized;
417431
_validateIsolateId(isolateId);
418432

419433
var library = await _inspector?.getLibrary(isolateId, targetId);
@@ -453,7 +467,9 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension(
453467
dynamic error;
454468

455469
try {
470+
await isInitialized;
456471
if (_expressionEvaluator != null) {
472+
await isCompilerInitialized;
457473
_validateIsolateId(isolateId);
458474

459475
var result = await _getEvaluationResult(
@@ -516,25 +532,38 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension(
516532
}
517533

518534
@override
519-
Future<Isolate> getIsolate(String isolateId) async => _getIsolate(isolateId);
535+
Future<Isolate> getIsolate(String isolateId) async {
536+
await isInitialized;
537+
return _getIsolate(isolateId);
538+
}
520539

521540
@override
522-
Future<MemoryUsage> getMemoryUsage(String isolateId) {
541+
Future<MemoryUsage> getMemoryUsage(String isolateId) async {
542+
await isInitialized;
523543
return _inspector.getMemoryUsage(isolateId);
524544
}
525545

526546
@override
527547
Future<Obj> getObject(String isolateId, String objectId,
528-
{int offset, int count}) =>
529-
_inspector?.getObject(isolateId, objectId, offset: offset, count: count);
548+
{int offset, int count}) async {
549+
await isInitialized;
550+
return _inspector?.getObject(isolateId, objectId,
551+
offset: offset, count: count);
552+
}
530553

531554
@override
532-
Future<ScriptList> getScripts(String isolateId) =>
533-
_inspector?.getScripts(isolateId);
555+
Future<ScriptList> getScripts(String isolateId) async {
556+
await isInitialized;
557+
return _inspector?.getScripts(isolateId);
558+
}
534559

535560
@override
536561
Future<SourceReport> getSourceReport(String isolateId, List<String> reports,
537-
{String scriptId, int tokenPos, int endTokenPos, bool forceCompile}) {
562+
{String scriptId,
563+
int tokenPos,
564+
int endTokenPos,
565+
bool forceCompile}) async {
566+
await isInitialized;
538567
return _inspector?.getSourceReport(isolateId, reports,
539568
scriptId: scriptId,
540569
tokenPos: tokenPos,
@@ -548,8 +577,10 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension(
548577
///
549578
/// The returned stack will contain up to [limit] frames if provided.
550579
@override
551-
Future<Stack> getStack(String isolateId, {int limit}) async =>
552-
(await _debugger).getStack(isolateId, limit: limit);
580+
Future<Stack> getStack(String isolateId, {int limit}) async {
581+
await isInitialized;
582+
return (await _debugger).getStack(isolateId, limit: limit);
583+
}
553584

554585
@override
555586
Future<VM> getVM() async {
@@ -577,6 +608,7 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension(
577608
Future<Response> invoke(
578609
String isolateId, String targetId, String selector, List argumentIds,
579610
{bool disableBreakpoints}) async {
611+
await isInitialized;
580612
// TODO(798) - respect disableBreakpoints.
581613
var remote =
582614
await _inspector?.invoke(isolateId, targetId, selector, argumentIds);
@@ -633,7 +665,10 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension(
633665
}
634666

635667
@override
636-
Future<Success> pause(String isolateId) async => (await _debugger).pause();
668+
Future<Success> pause(String isolateId) async {
669+
await isInitialized;
670+
return (await _debugger).pause();
671+
}
637672

638673
@override
639674
Future<Success> registerService(String service, String alias) async {
@@ -653,6 +688,7 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension(
653688
@override
654689
Future<Success> removeBreakpoint(
655690
String isolateId, String breakpointId) async {
691+
await isInitialized;
656692
_disabledBreakpoints
657693
.removeWhere((breakpoint) => breakpoint.id == breakpointId);
658694
return (await _debugger).removeBreakpoint(isolateId, breakpointId);
@@ -664,6 +700,7 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension(
664700
if (_inspector == null) throw StateError('No running isolate.');
665701
if (_inspector.appConnection.isStarted) {
666702
var stopwatch = Stopwatch()..start();
703+
await isInitialized;
667704
var result = await (await _debugger)
668705
.resume(isolateId, step: step, frameIndex: frameIndex);
669706
emitEvent(DwdsEvent('RESUME', {
@@ -679,6 +716,7 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension(
679716

680717
@override
681718
Future<Success> setExceptionPauseMode(String isolateId, String mode) async {
719+
await isInitialized;
682720
return (await _debugger).setExceptionPauseMode(isolateId, mode);
683721
}
684722

@@ -695,6 +733,7 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension(
695733

696734
@override
697735
Future<Success> setName(String isolateId, String name) async {
736+
await isInitialized;
698737
var isolate = _getIsolate(isolateId);
699738
isolate.name = name;
700739
return Success();

dwds/test/reload_test.dart

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ void main() {
9090

9191
group('Injected client', () {
9292
setUp(() async {
93-
await context.setUp();
93+
await context.setUp(enableExpressionEvaluation: true);
9494
});
9595

9696
tearDown(() async {
@@ -204,13 +204,66 @@ void main() {
204204
expect(source.contains('Hello World!'), isTrue);
205205
expect(source.contains('Gary is awesome!'), isTrue);
206206

207-
// Should not be paused.
208207
vm = await client.getVM();
209208
isolateId = vm.isolates.first.id;
210209
var isolate = await client.getIsolate(isolateId);
211-
expect(isolate.pauseEvent.kind, EventKind.kResume);
210+
212211
// Previous breakpoint should still exist.
213212
expect(isolate.breakpoints.isNotEmpty, isTrue);
213+
var bp = isolate.breakpoints.first;
214+
215+
// Should pause eventually.
216+
await stream
217+
.firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
218+
219+
expect(await client.removeBreakpoint(isolate.id, bp.id), isA<Success>());
220+
expect(await client.resume(isolate.id), isA<Success>());
221+
});
222+
223+
test('can evaluate expressions after hot restart ', () async {
224+
var client = context.debugConnection.vmService;
225+
var vm = await client.getVM();
226+
var isolateId = vm.isolates.first.id;
227+
await client.streamListen('Debug');
228+
var stream = client.onEvent('Debug');
229+
var scriptList = await client.getScripts(isolateId);
230+
var main = scriptList.scripts
231+
.firstWhere((script) => script.uri.contains('main.dart'));
232+
var bpLine =
233+
await context.findBreakpointLine('printCount', isolateId, main);
234+
await client.addBreakpoint(isolateId, main.id, bpLine);
235+
await stream
236+
.firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
237+
238+
await client.callServiceExtension('hotRestart');
239+
240+
vm = await client.getVM();
241+
isolateId = vm.isolates.first.id;
242+
var isolate = await client.getIsolate(isolateId);
243+
var library = isolate.rootLib.uri;
244+
var bp = isolate.breakpoints.first;
245+
246+
// Should pause eventually.
247+
var event = await stream
248+
.firstWhere((event) => event.kind == EventKind.kPauseBreakpoint);
249+
250+
// Expression evaluation while paused on a breakpoint should work.
251+
var result = await client.evaluateInFrame(
252+
isolate.id, event.topFrame.index, 'count');
253+
expect(
254+
result,
255+
isA<InstanceRef>().having((instance) => instance.valueAsString,
256+
'valueAsString', greaterThanOrEqualTo('0')));
257+
258+
await client.removeBreakpoint(isolateId, bp.id);
259+
await client.resume(isolateId);
260+
261+
// Expression evaluation while running should work.
262+
result = await client.evaluate(isolateId, library, 'true');
263+
expect(
264+
result,
265+
isA<InstanceRef>().having(
266+
(instance) => instance.valueAsString, 'valueAsString', 'true'));
214267
});
215268
});
216269

0 commit comments

Comments
 (0)