[ Service ] Fix dart:developer service extensions, add getSupportedProtocols

Brings package:vm_service test suite pass rate up to ~93%

Change-Id: I108a24e1acf37eb69c9de25a2f7334eb4c56dbe6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/487600
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Ben Konyi
2026-03-16 11:14:58 -07:00
committed by Commit Queue
parent e3ca6d824b
commit 0e9d801353
8 changed files with 120 additions and 31 deletions
@@ -6,6 +6,11 @@ export 'src/clients.dart';
export 'src/dart_runtime_service.dart';
export 'src/dart_runtime_service_backend.dart';
export 'src/dart_runtime_service_options.dart';
export 'src/dart_runtime_service_rpcs.dart'
show
RpcHandlerWithNoParameters,
RpcHandlerWithParameters,
ServiceRpcHandler;
export 'src/event_streams.dart';
export 'src/exceptions.dart';
export 'src/expression_evaluator.dart';
@@ -76,10 +76,11 @@ base class Client {
@mustCallSuper
void registerRpcHandlers() {
_internalRpcs.registerRpcsWithPeer(_clientPeer);
backend.registerRpcs(_clientPeer);
_internalRpcs.registerServiceExtensionForwarder(_clientPeer);
backend.registerFallbacks(_clientPeer);
_internalRpcs
..addBackendRpcs(backend: backend)
..registerRpcsWithPeer(_clientPeer)
..registerServiceExtensionForwarder(_clientPeer)
..registerBackendFallbacks(_clientPeer);
}
/// Attempts to register a [service] to be provided by this client.
@@ -2,10 +2,12 @@
// 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:json_rpc_2/json_rpc_2.dart' as json_rpc;
import 'dart:collection';
import 'package:meta/meta.dart';
import 'dart_runtime_service.dart';
import 'dart_runtime_service_rpcs.dart';
import 'event_streams.dart';
import 'expression_evaluator.dart';
import 'isolate_manager.dart';
@@ -72,14 +74,14 @@ abstract class DartRuntimeServiceBackend<IM extends IsolateManager> {
/// more clients listening to [streamId].
void onStreamCancel({required String streamId}) {}
/// Invoked by the [DartRuntimeService] to register handlers for the RPCs
/// provided by the backend.
void registerRpcs(json_rpc.Peer clientPeer);
/// RPCs to be registered with the [DartRuntimeService].
UnmodifiableListView<ServiceRpcHandler> get rpcs =>
UnmodifiableListView(const []);
/// Invoked by the [DartRuntimeService] to register fallback handlers
/// provided by the backend.
/// Fallbacks to be registered with the [DartRuntimeService].
///
/// Backend fallbacks are executed after incoming RPC requests fail to match
/// any registered RPCs or service extensions provided by other clients.
void registerFallbacks(json_rpc.Peer clientPeer);
UnmodifiableListView<RpcHandlerWithParameters> get fallbacks =>
UnmodifiableListView(const []);
}
@@ -11,6 +11,7 @@ import 'package:vm_service/vm_service.dart';
import 'clients.dart';
import 'dart_runtime_service.dart';
import 'dart_runtime_service_backend.dart';
import 'event_streams.dart';
import 'expression_evaluator.dart';
import 'rpc_exceptions.dart';
@@ -20,6 +21,8 @@ typedef RpcHandlerWithNoParameters = FutureOr<RpcResponse> Function();
typedef RpcHandlerWithParameters =
FutureOr<RpcResponse> Function(json_rpc.Parameters);
typedef ServiceRpcHandler = (String, Function?);
/// Manages requests made to platform-agnostic RPCs provided by
/// [DartRuntimeService] by a single [Client].
final class DartRuntimeServiceRpcs {
@@ -49,7 +52,7 @@ final class DartRuntimeServiceRpcs {
// Parameters for streamListen
static const _kStreamId = 'streamId';
late final _commonRpcs = <(String, Function?)>[
late final _commonRpcs = <ServiceRpcHandler>[
('getClientName', getClientName),
('registerService', registerService),
('setClientName', setClientName),
@@ -59,9 +62,22 @@ final class DartRuntimeServiceRpcs {
('evaluateInFrame', expressionEvaluator?.evaluateInFrame),
];
/// Registers the set of platform-agnostic RPCs for use by [client].
final _backendRpcs = <ServiceRpcHandler>[];
final _backendFallbacks = <RpcHandlerWithParameters>[];
void addBackendRpcs({required DartRuntimeServiceBackend backend}) {
_backendRpcs.addAll(backend.rpcs);
_backendFallbacks.addAll(backend.fallbacks);
}
void addBackendFallbacks({
required List<RpcHandlerWithParameters> fallbacks,
}) => _backendFallbacks.addAll(fallbacks);
/// Registers the set of platform-agnostic and backend RPCs for use by
/// [client].
void registerRpcsWithPeer(json_rpc.Peer clientPeer) {
for (final (method, callback) in _commonRpcs) {
for (final (method, callback) in [..._commonRpcs, ..._backendRpcs]) {
if (callback == null) continue;
if (callback is! RpcHandlerWithNoParameters &&
callback is! RpcHandlerWithParameters) {
@@ -92,6 +108,12 @@ final class DartRuntimeServiceRpcs {
clientPeer.registerFallback(serviceExtensionForwarderFallback);
}
void registerBackendFallbacks(json_rpc.Peer clientPeer) {
for (final fallback in _backendFallbacks) {
clientPeer.registerFallback(fallback);
}
}
/// Attempts to [parse] [parameters] into an instance of [T].
///
/// If [parameters] can't be parsed, a [json_rpc.RpcException] is thrown
@@ -2,8 +2,9 @@
// 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:collection';
import 'package:dart_runtime_service/dart_runtime_service.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
import 'package:test/fake.dart';
/// Fake implementation of [DartRuntimeServiceBackend] that throws when
@@ -32,10 +33,12 @@ base class FakeDartRuntimeServiceBackend extends Fake
}) async {}
@override
void registerRpcs(json_rpc.Peer clientPeer) {}
UnmodifiableListView<ServiceRpcHandler> get rpcs =>
UnmodifiableListView(const []);
@override
void registerFallbacks(json_rpc.Peer clientPeer) {}
UnmodifiableListView<RpcHandlerWithParameters> get fallbacks =>
UnmodifiableListView(const []);
@override
DartRuntimeService get frontend => throw UnimplementedError();
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
@@ -13,6 +14,7 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
import 'package:logging/logging.dart';
import 'package:stream_channel/stream_channel.dart';
import 'src/dart_runtime_service_vm_rpcs.dart';
import 'src/native_bindings.dart';
import 'src/vm_expression_evaluator.dart';
import 'src/vm_isolate_manager.dart';
@@ -73,6 +75,20 @@ class DartRuntimeServiceVMBackend
@override
late final VmExpressionEvaluator expressionEvaluator;
final _vmServiceRpcs = DartRuntimeServiceVmRpcs();
@override
UnmodifiableListView<ServiceRpcHandler> get rpcs =>
UnmodifiableListView(_vmServiceRpcs.rpcs);
@override
UnmodifiableListView<RpcHandlerWithParameters>
get fallbacks => UnmodifiableListView([
// If the registered Dart RPC handlers can't handle a request, forward it
// it to the native VM service implementation for processing.
sendToRuntime,
]);
@override
Future<void> initialize() async {
_logger.info('Initializing...');
@@ -148,18 +164,6 @@ class DartRuntimeServiceVMBackend
_nativeBindings.streamCancel(streamId: streamId);
}
@override
void registerRpcs(json_rpc.Peer clientPeer) {
// The VM service handles its service requests in service.cc.
}
@override
void registerFallbacks(json_rpc.Peer clientPeer) {
// If the registered Dart RPC handlers can't handle a request, forward it
// it to the native VM service implementation for processing.
clientPeer.registerFallback(sendToRuntime);
}
/// Sends service requests to the Dart VM runtime for processing.
Future<RpcResponse> sendToRuntime(json_rpc.Parameters request) async {
final method = request.method;
@@ -0,0 +1,46 @@
// 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 'dart:collection';
import 'package:dart_runtime_service/dart_runtime_service.dart';
import 'package:logging/logging.dart';
import 'package:vm_service/vm_service.dart';
import 'native_bindings.dart';
/// Implementations of RPCs specific to the VM service that are not handled
/// in runtime/vm/service.cc.
final class DartRuntimeServiceVmRpcs {
final _logger = Logger('$DartRuntimeServiceVmRpcs');
final _nativeBindings = NativeBindings();
late final rpcs = UnmodifiableListView<ServiceRpcHandler>([
('getSupportedProtocols', getSupportedProtocols),
]);
/// Returns the list of protocols implemented by the service.
///
/// VM service middleware like DDS should intercept this RPC and add their
/// own information to the response.
Future<RpcResponse> getSupportedProtocols() async {
final version = Version.parse(
await _nativeBindings.sendToVM(method: 'getVersion', params: const {}),
);
if (version == null) {
_logger.warning('Unable to retrieve version for getSupportedProtocols.');
RpcException.internalError.throwException();
}
return ProtocolList(
protocols: [
Protocol(
protocolName: 'VM Service',
major: version.major,
minor: version.minor,
),
],
).toJson();
}
}
@@ -133,10 +133,16 @@ class NativeBindings {
const kData = 'data';
final Object? converted;
if (value case [final Uint8List utf8String]) {
if (value case final String string) {
converted = json.decode(string);
} else if (value case [final Uint8List utf8String]) {
converted = jsonUtf8Decoder.decode(utf8String);
} else {
RpcException.internalError.throwException();
RpcException.internalError.throwException(
data: {
'details': 'Unknown response type (${value.runtimeType}: $value)',
},
);
}
if (converted case {kResult: final Map<String, Object?> result}) {
return result;