[VM/Service] Delete all the Service ID zones created by a client when it disconnects
TEST=pkg/vm_service/test/id_zone_deletion_on_client_disconnect_with_dds_test.dart,
pkg/vm_service/test/id_zone_deletion_on_client_disconnect_without_dds_test.dart,
CI
Issue: https://github.com/dart-lang/sdk/issues/55869
CoreLibraryReviewExempt: This CL does not include any core library API
changes, only VM Service implementation changes in
sdk/lib/vmservice/client.dart and
sdk/lib/vmservice/running_isolates.dart.
Change-Id: I32a72ade89b858519c61b5124adbeafb835c3d61
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381520
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Derek Xu <derekx@google.com>
diff --git a/pkg/dds/lib/src/client.dart b/pkg/dds/lib/src/client.dart
index 3a4acca..207f61e 100644
--- a/pkg/dds/lib/src/client.dart
+++ b/pkg/dds/lib/src/client.dart
@@ -67,7 +67,16 @@
///
/// Returned future completes when the peer is closed.
Future<void> listen() => _clientPeer.listen().then(
- (_) => dds.streamManager.clientDisconnect(this),
+ (_) async {
+ dds.streamManager.clientDisconnect(this);
+
+ for (final pair in createdServiceIdZones) {
+ await _vmServicePeer.sendRequest('deleteIdZone', {
+ 'isolateId': pair.isolateId,
+ 'idZoneId': pair.serviceIdZoneId,
+ });
+ }
+ },
);
/// Close the connection to the client.
@@ -260,6 +269,21 @@
},
);
+ _clientPeer.registerMethod('createIdZone',
+ (json_rpc.Parameters parameters) async {
+ final response = await _vmServicePeer.sendRequest(
+ parameters.method,
+ parameters.value,
+ ) as Map<String, dynamic>;
+ if (response.containsKey('id')) {
+ createdServiceIdZones.add((
+ serviceIdZoneId: response['id']!,
+ isolateId: parameters['isolateId'].asString,
+ ));
+ }
+ return response;
+ });
+
_clientPeer.registerMethod(
'getCachedCpuSamples',
dds.isolateManager.getCachedCpuSamples,
@@ -372,6 +396,12 @@
final DartDevelopmentServiceImpl dds;
final StreamChannel connection;
final Map<String, String> services = {};
+
+ /// Pairs of 1) the ID of a Service ID zone created by this client and 2) the
+ /// ID of the isolate in which that zone was created.
+ final List<({String serviceIdZoneId, String isolateId})>
+ createdServiceIdZones = [];
+
final Set<String> profilerUserTagFilters = {};
final json_rpc.Peer _vmServicePeer;
late json_rpc.Peer _clientPeer;
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 3135893..5cf29a4 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -148,6 +148,8 @@
vm_service/test/get_stack_limit_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
vm_service/test/get_stack_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
vm_service/test/get_stack_test: SkipByDesign # Debugger is disabled in AOT mode.
+vm_service/test/id_zone_deletion_on_client_disconnect_with_dds_test: SkipByDesign # Debugger is disabled in AOT mode.
+vm_service/test/id_zone_deletion_on_client_disconnect_without_dds_test: SkipByDesign # Debugger is disabled in AOT mode.
vm_service/test/id_zones_test: SkipByDesign # Debugger is disabled in AOT mode.
vm_service/test/implicit_getter_setter_test: SkipByDesign # Debugger is disabled in AOT mode.
vm_service/test/invoke_test: SkipByDesign # Debugger is disabled in AOT mode.
diff --git a/pkg/vm_service/test/id_zone_deletion_on_client_disconnect_common.dart b/pkg/vm_service/test/id_zone_deletion_on_client_disconnect_common.dart
new file mode 100644
index 0000000..0c873f3
--- /dev/null
+++ b/pkg/vm_service/test/id_zone_deletion_on_client_disconnect_common.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:developer';
+
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+import 'package:vm_service/vm_service_io.dart';
+
+import 'common/service_test_common.dart';
+
+// AUTOGENERATED START
+//
+// Update these constants by running:
+//
+// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
+//
+const LINE_A = 25;
+// AUTOGENERATED END
+
+void testeeMain() {
+ // ignore: unused_local_variable
+ final abcString = 'abc';
+ debugger(); // LINE_A
+}
+
+final idZoneDeletionOnDisconnectTests = <IsolateTest>[
+ hasStoppedAtBreakpoint,
+ stoppedAtLine(LINE_A),
+ (VmService client1, IsolateRef isolateRef) async {
+ final isolateId = isolateRef.id!;
+
+ final idZone1 = await client1.createIdZone(
+ isolateId,
+ IdZoneBackingBufferKind.kRing,
+ IdAssignmentPolicy.kAlwaysAllocate,
+ );
+
+ // Confirm that [idZone1] can be used.
+ await client1.evaluateInFrame(
+ isolateId,
+ 0,
+ 'abcString',
+ idZoneId: idZone1.id,
+ );
+
+ final client2 = await vmServiceConnectUri(client1.wsUri!);
+ final idZone2 = await client2.createIdZone(
+ isolateId,
+ IdZoneBackingBufferKind.kRing,
+ IdAssignmentPolicy.kAlwaysAllocate,
+ );
+
+ // Confirm that [idZone2] can be used.
+ await client2.evaluateInFrame(
+ isolateId,
+ 0,
+ 'abcString',
+ idZoneId: idZone2.id,
+ );
+
+ // Disposing of [client2] should delete [idZone2];
+ await client2.dispose();
+
+ // Confirm that [idZone2] can be no longer be used.
+ try {
+ await client1.evaluateInFrame(
+ isolateId,
+ 0,
+ 'abcString',
+ idZoneId: idZone2.id,
+ );
+ fail('successfully used an ID zone that should have been deleted');
+ } on RPCError catch (e) {
+ expect(e.code, RPCErrorKind.kInvalidParams.code);
+ }
+
+ // Confirm that [idZone1] can still be used.
+ await client1.evaluateInFrame(
+ isolateId,
+ 0,
+ 'abcString',
+ idZoneId: idZone1.id,
+ );
+ },
+];
diff --git a/pkg/vm_service/test/id_zone_deletion_on_client_disconnect_with_dds_test.dart b/pkg/vm_service/test/id_zone_deletion_on_client_disconnect_with_dds_test.dart
new file mode 100644
index 0000000..c74591a
--- /dev/null
+++ b/pkg/vm_service/test/id_zone_deletion_on_client_disconnect_with_dds_test.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'common/test_helper.dart';
+import 'id_zone_deletion_on_client_disconnect_common.dart';
+
+Future<void> main([args = const <String>[]]) => runIsolateTests(
+ args,
+ idZoneDeletionOnDisconnectTests,
+ 'id_zone_deletion_on_client_disconnect_without_dds_test.dart',
+ testeeConcurrent: testeeMain,
+ );
diff --git a/pkg/vm_service/test/id_zone_deletion_on_client_disconnect_without_dds_test.dart b/pkg/vm_service/test/id_zone_deletion_on_client_disconnect_without_dds_test.dart
new file mode 100644
index 0000000..06c006f
--- /dev/null
+++ b/pkg/vm_service/test/id_zone_deletion_on_client_disconnect_without_dds_test.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'common/test_helper.dart';
+import 'id_zone_deletion_on_client_disconnect_common.dart';
+
+Future<void> main([args = const <String>[]]) => runIsolateTests(
+ args,
+ idZoneDeletionOnDisconnectTests,
+ 'id_zone_deletion_on_client_disconnect_without_dds_test.dart',
+ extraArgs: ['--no-dds'],
+ testeeConcurrent: testeeMain,
+ );
diff --git a/sdk/lib/vmservice/client.dart b/sdk/lib/vmservice/client.dart
index 7ce9c51..3726fb9 100644
--- a/sdk/lib/vmservice/client.dart
+++ b/sdk/lib/vmservice/client.dart
@@ -23,6 +23,11 @@
/// The set of streams the client is subscribed to.
final streams = <String>{};
+ /// Pairs of 1) the ID of a Service ID zone created by this client and 2) the
+ /// ID of the isolate in which that zone was created.
+ final List<({String serviceIdZoneId, String isolateId})>
+ createdServiceIdZones = [];
+
/// The set of user tags that the client wants to receive CPU samples for.
final profilerUserTagFilters = <String>{};
diff --git a/sdk/lib/vmservice/vmservice.dart b/sdk/lib/vmservice/vmservice.dart
index 9fc218f..65b37db 100644
--- a/sdk/lib/vmservice/vmservice.dart
+++ b/sdk/lib/vmservice/vmservice.dart
@@ -285,6 +285,17 @@
_vmCancelStream(streamId);
}
}
+ for (final pair in client.createdServiceIdZones) {
+ runningIsolates.routeRequest(
+ this,
+ Message._fromJsonRpcRequest(client, {
+ 'method': 'deleteIdZone',
+ 'params': {
+ 'isolateId': pair.isolateId,
+ 'idZoneId': pair.serviceIdZoneId,
+ }
+ }));
+ }
_cleanupUnusedUserTagSubscriptions();
for (final service in client.services.keys) {
_eventMessageHandler(
@@ -725,6 +736,20 @@
return encodeSuccess(message);
}
+ void _recordInformationAboutCreatedServiceIdZone(
+ Client client,
+ Map<String, dynamic> decodedResponse,
+ String isolateId,
+ ) {
+ if (decodedResponse.containsKey('result')) {
+ client.createdServiceIdZones.add((
+ serviceIdZoneId:
+ (decodedResponse['result'] as Map<String, dynamic>)['id']!,
+ isolateId: isolateId,
+ ));
+ }
+ }
+
Future<Response?> routeRequest(VMService _, Message message) async {
final response = await _routeRequestImpl(message);
if (response == null) {
@@ -769,7 +794,16 @@
return await _handleService(message);
}
if (message.params['isolateId'] != null) {
- return await runningIsolates.routeRequest(this, message);
+ final Response response =
+ await runningIsolates.routeRequest(this, message);
+ if (message.method == 'createIdZone') {
+ _recordInformationAboutCreatedServiceIdZone(
+ message.client!,
+ response.decodeJson(),
+ message.params['isolateId'],
+ );
+ }
+ return response;
}
return await message.sendToVM();
} catch (e, st) {