[dds/dap] Ensure DAP always sends resume on PausePostRequest

The change at https://dart-review.googlesource.com/c/sdk/+/410760 to fix a race incorrectly assumed that once we had handled startup for a thread, we would never need to send an automatic resume again. However this was not the case - after a hot reload, we need to resume the thread even though we had technically already handled startup.

This is essentially a partial revert of 84e6ed0784, with a test to verify we trigger readyToResume on PausePostRequest.

Change-Id: I1e171043f04f38dd6f52e1692d9257d34e5248be
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/416580
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
Reviewed-by: Derek Xu <derekx@google.com>
This commit is contained in:
Danny Tuppeny
2025-03-19 07:32:19 -07:00
committed by Commit Queue
parent 5c1b9086c0
commit 29c350dfe9
3 changed files with 69 additions and 2 deletions
+6 -1
View File
@@ -645,7 +645,12 @@ class IsolateManager {
// after a hot restart.
if (eventKind == vm.EventKind.kPausePostRequest) {
await _configureIsolate(thread);
await handleThreadStartup(thread, sendStoppedOnEntry: false);
// We always want to resume here regardless of whether startupHandled was
// already `true` (because that might be from before the reload).
// Therefore set the flag and resume always.
thread.startupHandled = true;
await readyToResumeThread(thread.threadId);
} else if (eventKind == vm.EventKind.kPauseStart) {
handleThreadStartup(thread, sendStoppedOnEntry: true);
} else {
@@ -144,5 +144,27 @@ main() {
expect(receivedUri, 'scheme://file/path.dart');
expect(resolved, Uri.file('/returned/file/path.dart'));
});
test('sends resume after kPausePostRequest', () async {
// Trigger initial startup.
await adapter.isolateManager.handleEvent(Event(
isolate: adapter.mockService.isolate1,
kind: EventKind.kIsolateRunnable));
isolateManager.threads[0].startupHandled = true;
// Clear any previously called methods.
adapter.mockService.calledMethods.clear();
// Now simulate a hot reload causing the isolate to pause again.
await adapter.isolateManager.handleEvent(Event(
isolate: adapter.mockService.isolate1,
kind: EventKind.kPausePostRequest));
// Ensure we called readyToResume.
expect(adapter.mockService.calledMethods.map((cm) => cm.method),
contains('readyToResume'));
// Expect it was resumed.
});
});
}
+41 -1
View File
@@ -5,7 +5,7 @@
import 'dart:async';
import 'package:dap/dap.dart' as dap;
import 'package:dds/dap.dart';
import 'package:dds/dap.dart' hide Response;
import 'package:dds/dds.dart';
import 'package:dds/dds_launcher.dart';
import 'package:dds/src/dap/adapters/dart_cli_adapter.dart';
@@ -168,6 +168,10 @@ class MockVmService implements VmService {
/// tests.
final List<String> requests = [];
/// A list of methods that were called on the Mock VM Service.
List<({String method, String? isolateId, Map<String, dynamic>? args})>
calledMethods = [];
@override
Future<Success> setLibraryDebuggable(
String isolateId,
@@ -213,6 +217,30 @@ class MockVmService implements VmService {
);
}
@override
Future<Response> callMethod(
String method, {
String? isolateId,
Map<String, dynamic>? args,
}) async {
calledMethods.add((method: method, isolateId: isolateId, args: args));
// Some DDS methods are implemented as extensions so we can't override
// them in the mock, we need to override callMethod and handle them here
// (and provide their responses) instead.
if (method == 'getDartDevelopmentServiceVersion') {
return Version(major: 2, minor: 0);
} else if (method == 'readyToResume') {
isolateId ??= args != null ? args['isolateId'] : null;
return {isolate1.id, isolate2.id}.contains(isolateId)
? Success()
: throw SentinelException.parse(method, {});
}
throw 'MockVmService does not handle $method ($isolateId, $args)';
}
@override
Future<Success> resume(
String isolateId, {
@@ -224,6 +252,18 @@ class MockVmService implements VmService {
? Success()
: throw SentinelException.parse('resume', {});
}
@override
Future<Success> setIsolatePauseMode(
String isolateId, {
/*ExceptionPauseMode*/ String? exceptionPauseMode,
bool? shouldPauseOnExit,
}) async {
// Do nothing, just pretend.
return {isolate1.id, isolate2.id}.contains(isolateId)
? Success()
: throw SentinelException.parse('setIsolatePauseMode', {});
}
}
class MockDartDevelopmentServiceLauncher