[ Service ] Add support for ID zones to package:dart_runtime_service_vm

Also disables logging by default, which can be enabled by specifying the
`VM_SERVICE_LOGGING` environment variable.

package:vm_service test suite is ~99% passing with this change.
Change-Id: I41578cc8b39afac93589b8792861be34b848f558
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/490921
Reviewed-by: Jessy Yameogo <yjessy@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
This commit is contained in:
Ben Konyi
2026-04-10 12:04:28 -07:00
committed by Commit Queue
parent cb72e886d5
commit d2d28414ef
11 changed files with 217 additions and 21 deletions
@@ -113,7 +113,7 @@ Future<void> main([List<String> args = const []]) async {
}
await DartRuntimeService.initialize(
config: DartRuntimeServiceOptions(
enableLogging: true,
enableLogging: Platform.environment.containsKey('VM_SERVICE_LOGGING'),
port: _port,
disableAuthCodes: _authCodesDisabled,
disableOriginCheck: _originCheckDisabled,
@@ -17,6 +17,7 @@ import 'package:stream_channel/stream_channel.dart';
import 'src/dart_runtime_service_vm_rpcs.dart';
import 'src/native_bindings.dart';
import 'src/vm_clients.dart';
import 'src/vm_dev_fs.dart';
import 'src/vm_expression_evaluator.dart';
import 'src/vm_isolate_manager.dart';
@@ -79,7 +80,7 @@ class DartRuntimeServiceVMBackend
@override
late final VmExpressionEvaluator expressionEvaluator;
final _vmServiceRpcs = DartRuntimeServiceVmRpcs();
late final _vmServiceRpcs = DartRuntimeServiceVmRpcs(backend: this);
/// Adds support for launching and accepting connections from the
/// Dart Development Service.
@@ -103,6 +104,10 @@ class DartRuntimeServiceVMBackend
@override
OptionalHandler get httpHandler => _devFs.handlePutStreamRequest;
@override
VmClientManager clientManagerBuilder() =>
VmClientManager(backend: this, eventStreamMethods: frontend.eventStreams);
@override
Future<void> initialize() async {
_logger.info('Initializing...');
@@ -5,18 +5,34 @@
import 'dart:collection';
import 'package:dart_runtime_service/dart_runtime_service.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc_2;
import 'package:logging/logging.dart';
import 'package:vm_service/vm_service.dart';
import '../dart_runtime_service_vm.dart';
import 'native_bindings.dart';
import 'vm_clients.dart';
/// Implementations of RPCs specific to the VM service that are not handled
/// in runtime/vm/service.cc.
final class DartRuntimeServiceVmRpcs {
DartRuntimeServiceVmRpcs({required this.backend});
final _logger = Logger('$DartRuntimeServiceVmRpcs');
final _nativeBindings = NativeBindings();
final DartRuntimeServiceVMBackend backend;
static const _kGetSupportedProtocols = 'getSupportedProtocols';
static const _kCreateIdZone = 'createIdZone';
static const _kDeleteIdZone = 'deleteIdZone';
static const _kIsolateId = 'isolateId';
static const _kIdZoneId = 'idZoneId';
late final rpcs = UnmodifiableListView<ServiceRpcHandler>([
('getSupportedProtocols', getSupportedProtocols),
(_kGetSupportedProtocols, getSupportedProtocols),
(_kCreateIdZone, createIdZone),
(_kDeleteIdZone, deleteIdZone),
]);
/// Returns the list of protocols implemented by the service.
@@ -42,4 +58,40 @@ final class DartRuntimeServiceVmRpcs {
],
).toJson();
}
/// Creates a new [IdZone] where temporary IDs for instances in the specified
/// isolate may be allocated for [client].
Future<RpcResponse> createIdZone(
json_rpc_2.Parameters parameters,
Client client,
) async {
// The implementation of this RPC is in the VM, but we track which zones
// have been created by individual clients so we can clean them up when the
// clients disconnect.
final result = await backend.sendToRuntime(parameters);
final idZone = IdZone.parse(result);
if (idZone != null) {
final isolateId = parameters[_kIsolateId].asString;
final vmClient = client as VmClient;
vmClient.registerIdZone(isolateId: isolateId, idZone: idZone);
}
return result;
}
/// Destroys an [IdZone] owned by [client].
Future<RpcResponse> deleteIdZone(
json_rpc_2.Parameters parameters,
Client client,
) async {
// The implementation of this RPC is in the VM, but we track which zones
// have been created by individual clients so we can clean them up when the
// clients disconnect.
final result = await backend.sendToRuntime(parameters);
final vmClient = client as VmClient;
vmClient.unregisterIdZone(
isolateId: parameters[_kIsolateId].asString,
idZoneId: parameters[_kIdZoneId].asString,
);
return result;
}
}
@@ -0,0 +1,82 @@
// Copyright (c) 2026, 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 'package:dart_runtime_service/dart_runtime_service.dart';
import 'package:json_rpc_2/json_rpc_2.dart' hide Client;
import 'package:meta/meta.dart';
import 'package:stream_channel/stream_channel.dart';
import 'package:vm_service/vm_service.dart';
import '../dart_runtime_service_vm.dart';
typedef ServiceIDZone = ({IdZone idZone, String isolateId});
/// A [Client] of the VM service.
final class VmClient extends Client<DartRuntimeServiceVMBackend> {
VmClient({
required super.connection,
required super.clients,
required super.eventStreamMethods,
required super.backend,
required super.artificial,
super.name,
});
final _idZones = <ServiceIDZone>{};
@override
@protected
Future<void> cleanup() async {
await _cleanupIdZones();
await super.cleanup();
}
/// Track a newly created [IdZone].
void registerIdZone({required String isolateId, required IdZone idZone}) {
_idZones.add((idZone: idZone, isolateId: isolateId));
}
/// Stop tracking a recently destroyed [IdZone].
void unregisterIdZone({required String isolateId, required String idZoneId}) {
_idZones.removeWhere(
(e) => e.isolateId == isolateId && e.idZone.id == idZoneId,
);
}
Future<void> _cleanupIdZones() async {
await Future.wait([
for (final (:idZone, :isolateId) in _idZones)
backend.sendToRuntime(
Parameters('deleteIdZone', {
'isolateId': isolateId,
'idZoneId': idZone.id!,
}),
),
]);
}
}
/// Manages and tracks clients of the VM service.
final class VmClientManager extends ClientManager<DartRuntimeServiceVMBackend> {
VmClientManager({required super.backend, required super.eventStreamMethods});
@override
VmClient clientBuilder({
required StreamChannel<Object?> connection,
required UnmodifiableClientNamedLookup clients,
required EventStreamMethods eventStreamMethods,
required DartRuntimeServiceVMBackend backend,
required bool artificial,
String? name,
}) {
return VmClient(
connection: connection,
clients: clients,
eventStreamMethods: eventStreamMethods,
backend: backend,
name: name,
artificial: artificial,
);
}
}
@@ -29,8 +29,8 @@ final class VmExpressionEvaluator extends ExpressionEvaluator {
static const kScope = 'scope';
static const kDisableBreakpoints = 'disableBreakpoints';
// TODO(bkonyi): add ID zone support.
// static const kIdZoneId = 'idZoneId';
// ID zone support.
static const kIdZoneId = 'idZoneId';
// `evaluate` specific parameters.
static const kTargetId = 'targetId';
@@ -74,6 +74,9 @@ final class VmExpressionEvaluator extends ExpressionEvaluator {
disableBreakpoints: parameters[kDisableBreakpoints].exists
? parameters[kDisableBreakpoints].asBool
: null,
idZoneId: parameters[kIdZoneId].exists
? parameters[kIdZoneId].asString
: null,
);
}
@@ -90,6 +93,9 @@ final class VmExpressionEvaluator extends ExpressionEvaluator {
disableBreakpoints: parameters[kDisableBreakpoints].exists
? parameters[kDisableBreakpoints].asBool
: null,
idZoneId: parameters[kIdZoneId].exists
? parameters[kIdZoneId].asString
: null,
);
}
@@ -105,6 +111,7 @@ final class VmExpressionEvaluator extends ExpressionEvaluator {
required String? targetId,
required Map<String, String>? scope,
required bool? disableBreakpoints,
required String? idZoneId,
}) async {
final buildScopeResponse = await _buildScope(
isolateId: isolateId,
@@ -124,6 +131,7 @@ final class VmExpressionEvaluator extends ExpressionEvaluator {
targetId: targetId,
scope: scope,
disableBreakpoints: disableBreakpoints,
idZoneId: idZoneId,
kernelBase64: kernelBase64,
);
}
@@ -206,6 +214,7 @@ final class VmExpressionEvaluator extends ExpressionEvaluator {
required int? frameIndex,
required String? targetId,
required bool? disableBreakpoints,
required String? idZoneId,
required String kernelBase64,
}) {
final params = <String, Object?>{
@@ -215,6 +224,7 @@ final class VmExpressionEvaluator extends ExpressionEvaluator {
kFrameIndex: ?frameIndex,
kTargetId: ?targetId,
kDisableBreakpoints: ?disableBreakpoints,
kIdZoneId: ?idZoneId,
kKernelBytes: kernelBase64,
};
return backend.sendToRuntime(
+1
View File
@@ -13,5 +13,6 @@ dependencies:
file: any
json_rpc_2: any
logging: any
meta: any
stream_channel: any
vm_service: any