[ Service ] Initial implementation of VM Service based on package:dart_runtime_service

This change includes an initial implementation of the new VM service
implementation based on `package:dart_runtime_service`, along with the
necessary plumbing to start it in place of the legacy VM service
implementation.

The entrypoint for the new VM service implementation is located in
dart_runtime_service_vm/bin/vm_service_entrypoint.dart, which is
compiled into AppJIT and AOT snapshots when the
`--include-experimental-vm-service` flag is provided to `build.py`. To run
the VM with the new VM service implementation, the
`--experimental-vm-service` flag must be provided.

Currently, the experimental VM service implementation supports:

  - User specified ports
  - Authentication code flags
  - Enabling the HTTP server via SIGQUIT
  - Some service protocol RPCs that don't require an isolate ID (e.g.,
    `getVM`)

See go/dart-runtime-services-unification for more details.

TEST=Manual

CoreLibraryReviewExempt: dart:_vmservice is private
Change-Id: I4a58cd1fa0a386313baa3d5c5345720231279123
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/484820
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Alexander Thomas <athom@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Ben Konyi
2026-03-10 13:27:01 -07:00
committed by Commit Queue
parent 4a4658e014
commit f7a049dfb4
34 changed files with 776 additions and 80 deletions
+2
View File
@@ -61,6 +61,7 @@ group("runtime") {
deps += [
"runtime/bin:dartaotruntime",
"runtime/bin:dartaotruntime_product",
"utils/dart_runtime_service_vm:dart_runtime_service_vm_aot_snapshot",
"utils/dartdev:dartdev_aot_snapshot",
"utils/dds:dds_aot",
"utils/dtd:dtd_aot",
@@ -68,6 +69,7 @@ group("runtime") {
]
} else {
deps += [
"utils/dart_runtime_service_vm",
"utils/dds",
"utils/dtd",
"utils/kernel-service:frontend_server",
@@ -6,3 +6,4 @@ export 'src/dart_runtime_service.dart';
export 'src/dart_runtime_service_backend.dart';
export 'src/dart_runtime_service_options.dart';
export 'src/exceptions.dart';
export 'src/rpc_exceptions.dart';
+10 -2
View File
@@ -7,6 +7,8 @@ import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:stream_channel/stream_channel.dart';
import 'dart_runtime_service.dart';
import 'dart_runtime_service_backend.dart';
import 'dart_runtime_service_rpcs.dart';
import 'event_streams.dart';
import 'rpc_exceptions.dart';
@@ -22,6 +24,7 @@ base class Client {
required StreamChannel<String> connection,
required UnmodifiableNamedLookup<Client> clients,
required EventStreamMethods eventStreamMethods,
required this.backend,
}) {
_clientPeer = json_rpc.Peer(connection, strictProtocolChecks: false);
_internalRpcs = DartRuntimeServiceRpcs(
@@ -35,6 +38,7 @@ base class Client {
late json_rpc.Peer _clientPeer;
late final DartRuntimeServiceRpcs _internalRpcs;
final DartRuntimeServiceBackend backend;
/// The logger to be used when handling requests from this client.
Logger get logger => Logger('Client ($name)');
@@ -66,6 +70,9 @@ base class Client {
@mustCallSuper
void registerRpcHandlers() {
_internalRpcs.registerRpcsWithPeer(_clientPeer);
backend.registerRpcs(_clientPeer);
_internalRpcs.registerServiceExtensionForwarder(_clientPeer);
backend.registerFallbacks(_clientPeer);
}
/// Attempts to register a [service] to be provided by this client.
@@ -159,10 +166,10 @@ base class Client {
///
/// Call [addClient] when a client connects to your service.
base class ClientManager {
ClientManager({required this.eventStreamMethods});
ClientManager({required this.backend, required this.eventStreamMethods});
static const _kServicePrologue = 's';
final DartRuntimeServiceBackend backend;
final EventStreamMethods eventStreamMethods;
/// The set of [Client]s currently connected to the service.
@@ -183,6 +190,7 @@ base class ClientManager {
connection: connection,
clients: UnmodifiableNamedLookup(clients),
eventStreamMethods: eventStreamMethods,
backend: backend,
);
final namespace = clients.add(client);
client.initialize(namespace: namespace).then((_) {
@@ -18,6 +18,8 @@ import 'exceptions.dart';
import 'handlers.dart';
import 'utils.dart';
typedef RpcResponse = Map<String, Object?>;
class DartRuntimeService {
DartRuntimeService._({required this.config, required this.backend})
: authCode = config.disableAuthCodes ? null : generateSecret() {
@@ -26,12 +28,12 @@ class DartRuntimeService {
}
}
static Future<DartRuntimeService> start({
static Future<DartRuntimeService> initialize({
required DartRuntimeServiceOptions config,
required DartRuntimeServiceBackend backend,
}) async {
final service = DartRuntimeService._(config: config, backend: backend);
await service._startService();
await service._initialize();
return service;
}
@@ -40,10 +42,31 @@ class DartRuntimeService {
final DartRuntimeServiceBackend backend;
/// The ws:// URI pointing to this [DartRuntimeService]'s server.
Uri get uri => _uri!;
///
/// Throws [DartRuntimeServiceServerNotRunning] if the HTTP server is not
/// active.
Uri get uri {
if (_server == null) {
throw const DartRuntimeServiceServerNotRunning();
}
return _uri!;
}
Uri? _uri;
/// The http:// URI pointing to this [DartRuntimeService]'s server.
///
/// Throws [DartRuntimeServiceServerNotRunning] if the HTTP server is not
/// active.
Uri get httpUri => uri.replace(scheme: 'http');
/// The sse:// URI pointing to this [DartRuntimeService]'s server.
///
/// Throws [StateError] if [DartRuntimeServiceOptions.sseHandlerPath] is not
/// set.
///
/// Throws [DartRuntimeServiceServerNotRunning] if the HTTP server is not
/// active.
Uri get sseUri {
if (config.sseHandlerPath == null) {
throw StateError('SSE handler path not configured.');
@@ -63,6 +86,7 @@ class DartRuntimeService {
@visibleForTesting
late final ClientManager clientManager = ClientManager(
backend: backend,
eventStreamMethods: eventStreamManager,
);
@@ -73,15 +97,42 @@ class DartRuntimeService {
HttpServer? _server;
/// Initializes the service's state without starting the web server.
Future<void> _initialize() async {
await backend.initialize();
if (config.autoStart) {
_logger.info('Autostart enabled. Starting server.');
await _startServer();
}
await backend.onServiceReady(this);
}
/// Shuts down the service and cleans up backend state.
Future<void> shutdown() async {
await _server?.close(force: true);
await clientManager.shutdown();
await backend.clearState();
await backend.shutdown();
await _shutdownServer();
await clientManager.shutdown();
Logger.root.clearListeners();
}
Future<void> _startService() async {
Future<void> toggleServer() async {
// TODO(bkonyi): verify there's no race conditions
if (_server != null) {
await _shutdownServer();
} else {
await _startServer();
}
}
Future<void> _startServer() async {
if (_server != null) {
_logger.warning(
"Attempted to start the HTTP server, but it's already running.",
);
throw const DartRuntimeServiceServerAlreadyRunning();
}
// TODO(bkonyi): support IPv6
final host = InternetAddress.loopbackIPv4.host;
@@ -120,11 +171,28 @@ class DartRuntimeService {
port: server.port,
path: authCode != null ? '/$authCode' : '',
);
await backend.onServerStarted(httpUri: httpUri, wsUri: uri);
_logger.info(
'Dart Runtime Service started successfully and is listening at $uri.',
'Dart Runtime Service HTTP server started successfully and is listening '
'at $uri.',
);
}
Future<void> _shutdownServer() async {
final server = _server;
if (server == null) {
_logger.warning(
"Attempting to shut down the HTTP server, but it's not "
'running.',
);
throw const DartRuntimeServiceServerNotRunning();
}
_logger.info('Dart Runtime Service HTTP server is shutting down.');
_server = null;
_uri = null;
await server.close();
}
shelf.Handler _handlers() {
_logger.info('Building Shelf handlers.');
var pipeline = const shelf.Pipeline();
@@ -2,10 +2,50 @@
// 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_runtime_service.dart';
/// A backend implementation of a service used to inject non-common
/// functionality into a [DartRuntimeService].
abstract class DartRuntimeServiceBackend {
/// Invoked by the [DartRuntimeService] when the service is initializing,
/// before the service's HTTP server is started.
///
/// The backend should not expect for this to be invoked more than once.
Future<void> initialize();
/// Invoked by the [DartRuntimeService] once it has completely finished
/// initializing.
///
/// The backend should not expect for this to be invoked more than once.
Future<void> onServiceReady(DartRuntimeService service);
/// Invoked by the [DartRuntimeService] when the service is shutting down,
/// allowing for the backend to clean up its state.
///
/// The backend should not expect to be reinitialized after shutting down.
Future<void> shutdown();
/// Invoked by the [DartRuntimeService] when the service is no longer
/// available, either due to the HTTP server being disabled or the service
/// shutting down.
///
/// This is always invoked immediately before [shutdown].
Future<void> clearState();
/// Invoked by the [DartRuntimeService] when the service's HTTP server has
/// started.
Future<void> onServerStarted({required Uri httpUri, required Uri wsUri});
/// Invoked by the [DartRuntimeService] to register handlers for the RPCs
/// provided by the backend.
void registerRpcs(json_rpc.Peer clientPeer);
/// Invoked by the [DartRuntimeService] to register fallback handlers
/// provided by the backend.
///
/// 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);
}
@@ -11,6 +11,7 @@ class DartRuntimeServiceOptions {
this.port = 0,
this.disableAuthCodes = false,
this.sseHandlerPath,
this.autoStart = true,
});
/// If true, enables log output for the service.
@@ -33,17 +34,22 @@ class DartRuntimeServiceOptions {
/// Defaults to null.
final String? sseHandlerPath;
/// If true, the HTTP server will be started on initialization.
final bool autoStart;
DartRuntimeServiceOptions copyWith({
bool? enableLogging,
int? port,
bool? disableAuthCodes,
String? sseHandlerPath,
bool? autoStart,
}) {
return DartRuntimeServiceOptions(
enableLogging: enableLogging ?? this.enableLogging,
port: port ?? this.port,
disableAuthCodes: disableAuthCodes ?? this.disableAuthCodes,
sseHandlerPath: sseHandlerPath ?? this.sseHandlerPath,
autoStart: autoStart ?? this.autoStart,
);
}
}
@@ -15,7 +15,6 @@ import 'event_streams.dart';
import 'rpc_exceptions.dart';
import 'utils.dart';
typedef RpcResponse = Map<String, Object?>;
typedef RpcHandlerWithNoParameters = FutureOr<RpcResponse> Function();
typedef RpcHandlerWithParameters =
FutureOr<RpcResponse> Function(json_rpc.Parameters);
@@ -80,7 +79,9 @@ final class DartRuntimeServiceRpcs {
}
});
}
}
void registerServiceExtensionForwarder(json_rpc.Peer clientPeer) {
clientPeer.registerFallback(serviceExtensionForwarderFallback);
}
@@ -20,3 +20,19 @@ final class DartRuntimeServiceFailedToStartException
const DartRuntimeServiceFailedToStartException({required String message})
: super(message: 'Failed to start: $message');
}
/// Thrown when the [DartRuntimeService] attempts to start the server when it's
/// already active.
final class DartRuntimeServiceServerAlreadyRunning
extends DartRuntimeServiceException {
const DartRuntimeServiceServerAlreadyRunning()
: super(message: 'The HTTP server is already running.');
}
/// Thrown when the [DartRuntimeService] attempts to shutdown the server when
/// it's not active.
final class DartRuntimeServiceServerNotRunning
extends DartRuntimeServiceException {
const DartRuntimeServiceServerNotRunning()
: super(message: 'The HTTP server is not running.');
}
@@ -8,8 +8,9 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
enum RpcException {
// These error codes must be kept in sync with those in vm/json_stream.h and
// vmservice.dart.
serverError(code: SERVER_ERROR, message: 'Server error'),
methodNotFound(code: METHOD_NOT_FOUND, message: 'Method not found'),
serverError(code: SERVER_ERROR, message: 'Server error.'),
methodNotFound(code: METHOD_NOT_FOUND, message: 'Method not found.'),
internalError(code: INTERNAL_ERROR, message: 'Internal error.'),
connectionDisposed(code: -32010, message: 'Service connection disposed.'),
featureDisabled(code: 100, message: 'Feature is disabled.'),
streamAlreadySubscribed(code: 103, message: 'Stream already subscribed.'),
@@ -3,6 +3,7 @@
// 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' as json_rpc;
import 'package:test/fake.dart';
/// Fake implementation of [DartRuntimeServiceBackend] that throws when
@@ -12,6 +13,27 @@ import 'package:test/fake.dart';
/// a backend implementation.
base class FakeDartRuntimeServiceBackend extends Fake
implements DartRuntimeServiceBackend {
@override
Future<void> initialize() async {}
@override
Future<void> onServiceReady(DartRuntimeService service) async {}
@override
Future<void> shutdown() async {}
@override
Future<void> clearState() async {}
@override
Future<void> onServerStarted({
required Uri httpUri,
required Uri wsUri,
}) async {}
@override
void registerRpcs(json_rpc.Peer clientPeer) {}
@override
void registerFallbacks(json_rpc.Peer clientPeer) {}
}
@@ -22,7 +22,7 @@ Future<DartRuntimeService> createDartRuntimeServiceForTest({
DartRuntimeService? service;
addTearDown(() async => await service?.shutdown());
service = await DartRuntimeService.start(
service = await DartRuntimeService.initialize(
config: config,
backend: FakeDartRuntimeServiceBackend(),
);
+1
View File
@@ -0,0 +1 @@
file:/tools/OWNERS_DEV_INFRA
@@ -0,0 +1 @@
include: ../dart_runtime_service/analysis_options.yaml
@@ -0,0 +1,113 @@
// 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:async';
import 'dart:io';
import 'package:dart_runtime_service/dart_runtime_service.dart';
import 'package:dart_runtime_service_vm/dart_runtime_service_vm.dart';
// ignore: unreachable_from_main
const entrypoint = pragma(
'vm:entry-point',
!bool.fromEnvironment('dart.vm.product'),
);
// The TCP IP that DDS listens on.
@entrypoint
// ignore: unused_element
String _ddsIP = '';
// The TCP port that DDS listens on.
@entrypoint
// ignore: unused_element
int _ddsPort = 0;
// The TCP port that the HTTP server listens on.
@entrypoint
int _port = 0;
// The TCP IP that the HTTP server listens on.
@entrypoint
// ignore: unused_element
String _ip = '';
// Should the HTTP server auto start?
@entrypoint
bool _autoStart = false;
// Should the HTTP server require an auth code?
@entrypoint
bool _authCodesDisabled = false;
// Should the HTTP server run in devmode?
@entrypoint
// ignore: unused_element
bool _originCheckDisabled = false;
// Location of file to output VM service connection info.
@entrypoint
// ignore: unused_element
String? _serviceInfoFilename;
@entrypoint
// ignore: unused_element
bool _isWindows = false;
@entrypoint
// ignore: unused_element
bool _isFuchsia = false;
@entrypoint
Stream<ProcessSignal> Function(ProcessSignal signal)? _signalWatch;
@entrypoint
// ignore: unused_element
StreamSubscription<ProcessSignal>? _signalSubscription;
@entrypoint
// ignore: unused_element
bool _serveDevtools = true;
@entrypoint
// ignore: unused_element
bool _enableServicePortFallback = false;
@entrypoint
// ignore: unused_element
bool _waitForDdsToAdvertiseService = false;
@entrypoint
// ignore: unused_element
bool _printDtd = false;
// ignore: unused_element
File? _residentCompilerInfoFile;
@entrypoint
// ignore: unused_element
void _populateResidentCompilerInfoFile(
/// If either `--resident-compiler-info-file` or `--resident-server-info-file`
/// was supplied on the command line, the CLI argument should be forwarded as
/// the argument to this parameter. If neither option was supplied, the
/// argument to this parameter should be null.
String? residentCompilerInfoFilePathArgumentFromCli,
) {
// TODO(bkonyi): implement
}
Future<void> main([List<String> args = const []]) async {
if (args case ['--help']) {
return;
}
await DartRuntimeService.initialize(
config: DartRuntimeServiceOptions(
enableLogging: true,
port: _port,
disableAuthCodes: _authCodesDisabled,
autoStart: _autoStart,
),
backend: DartRuntimeServiceVMBackend(signalWatch: _signalWatch!),
);
}
@@ -0,0 +1,93 @@
// 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:async';
import 'dart:io';
import 'package:dart_runtime_service/dart_runtime_service.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
import 'package:logging/logging.dart';
import 'src/native_bindings.dart';
class DartRuntimeServiceVMBackend extends DartRuntimeServiceBackend {
/// The backend implementation for the Dart VM Service.
///
/// [signalWatch] is the internal implementation of [ProcessSignal.watch],
/// which bypasses checks that prevent [ProcessSignal.sigquit] from being
/// watched.
DartRuntimeServiceVMBackend({required this.signalWatch});
/// The internal implementation of [ProcessSignal.watch].
final Stream<ProcessSignal> Function(ProcessSignal signal) signalWatch;
final _nativeBindings = NativeBindings();
final _logger = Logger('VM Backend');
StreamSubscription<ProcessSignal>? _sigquitSubscription;
@override
Future<void> initialize() async {
_logger.info('Initializing...');
_nativeBindings.onStart();
_logger.info('Initialized!');
}
@override
Future<void> onServiceReady(DartRuntimeService service) async {
// SIGQUIT isn't supported on Fuchsia or Windows.
if (Platform.isFuchsia || Platform.isWindows) {
return;
}
_sigquitSubscription = signalWatch(ProcessSignal.sigquit).listen((_) {
_logger.info('SIGQUIT received. Toggling VM Service HTTP server.');
service.toggleServer();
});
}
@override
Future<void> onServerStarted({
required Uri httpUri,
required Uri wsUri,
}) async {
// TODO(bkonyi): handle DDS connection case.
stdout.writeln('The Dart VM service is listening on $httpUri');
_nativeBindings.onServerAddressChange(httpUri.toString());
}
@override
Future<void> clearState() async {
// Do nothing for now.
}
@override
Future<void> shutdown() async {
await _sigquitSubscription?.cancel();
_nativeBindings.onExit();
}
@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;
final params = request.asMap.cast<String, Object?>();
if (params case {'isolateId': final String _}) {
// TODO(bkonyi): handle isolate requests
RpcException.serverError.throwException();
}
return await _nativeBindings.sendToVM(method: method, params: params);
}
}
@@ -0,0 +1,148 @@
// 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.
// This is a special situation where we're allowed to import dart:_vmservice
// from outside the core libraries to access the native entrypoints.
//
// See VmTarget in package:vm for the exception for this library to access
// dart:_vmservice.
// ignore: uri_does_not_exist
import 'dart:_vmservice' as vm_service_natives;
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:dart_runtime_service/dart_runtime_service.dart';
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
/// Allows for sending messages to the native VM service implementation.
class NativeBindings {
static final jsonUtf8Decoder = json.fuse(utf8);
/// Sends a general RPC to the VM for processing.
///
/// The RPC is not executed in the scope of any particular isolate.
Future<RpcResponse> sendToVM({
required String method,
required Map<String, Object?> params,
}) {
final receivePort = RawReceivePort(null, 'VM Message');
final completer = Completer<RpcResponse>();
receivePort.handler = (Object value) {
receivePort.close();
try {
completer.complete(_toResponse(value: value));
} on json_rpc.RpcException catch (e) {
completer.completeError(e);
}
};
vm_service_natives.sendRootServiceMessage(
_toRequest(responsePort: receivePort, method: method, params: params),
);
return completer.future;
}
/// Notifies the VM that the VM service server has finished initializing.
void onStart() => vm_service_natives.onStart();
/// Notifies the VM that the VM service server has finished exiting.
void onExit() => vm_service_natives.onExit();
/// Notifies the VM that the VM service server address has been updated.
///
/// If [address] is null, the VM will assume the server is not running.
void onServerAddressChange(String? address) =>
vm_service_natives.onServerAddressChange(address);
RpcResponse _toResponse({required Object value}) {
const kResult = 'result';
const kError = 'error';
const kCode = 'code';
const kMessage = 'message';
const kData = 'data';
final Object? converted;
if (value case [final Uint8List utf8String]) {
converted = jsonUtf8Decoder.decode(utf8String);
} else {
RpcException.internalError.throwException();
}
if (converted case {kResult: final Map<String, Object?> result}) {
return result;
} else if (converted case {
kError: {kCode: final int code, kMessage: final String message},
}) {
final data = converted[kData];
throw json_rpc.RpcException(code, message, data: data);
} else {
RpcException.internalError.throwException();
}
}
// Calls toString on all non-String elements of [list]. We do this so all
// elements in the list are strings, making consumption by C++ simpler.
// This has a side effect that boolean literal values like true become 'true'
// and thus indistinguishable from the string literal 'true'.
static void _convertAllToStringInPlace(List<Object?> list) {
for (var i = 0; i < list.length; i++) {
list[i] = list[i].toString();
}
}
List<Object?> _toRequest({
required RawReceivePort responsePort,
required String method,
required Map<String, Object?> params,
}) {
final parametersAreObjects = _methodNeedsObjectParameters(method);
final keys = params.keys.toList(growable: false);
final values = params.values.cast<Object?>().toList(growable: false);
if (!parametersAreObjects) {
_convertAllToStringInPlace(values);
}
// This is the request ID that will be inserted into the JSON response in
// service.cc. package:json_rpc_2 already handles these IDs, so we just
// pass in a placeholder for now until we can update Service::InvokeMethod
// to not expect it.
// TODO(bkonyi): remove request ID from service message.
const kPlaceholderRequestId = -1;
// Keep in sync with Service::InvokeMethod in service.cc.
return List<Object?>.filled(7, null)
..[0] =
0 // Make room for OOB message type.
..[1] = responsePort.sendPort
..[2] = kPlaceholderRequestId
..[3] = method
..[4] = parametersAreObjects
..[5] = keys
..[6] = values;
}
// We currently support two ways of passing parameters from Dart code to C
// code. The original way always converts the parameters to strings before
// passing them over. Our goal is to convert all C handlers to take the
// parameters as Dart objects but until the conversion is complete, we
// maintain the list of supported methods below.
bool _methodNeedsObjectParameters(String method) {
switch (method) {
case '_listDevFS':
case '_listDevFSFiles':
case '_createDevFS':
case '_deleteDevFS':
case '_writeDevFSFile':
case '_writeDevFSFiles':
case '_readDevFSFile':
case '_spawnUri':
case '_reloadKernel':
case '_reloadSources':
case 'reloadSources':
return true;
default:
return false;
}
}
}
+14
View File
@@ -0,0 +1,14 @@
name: dart_runtime_service_vm
# This package is not intended for consumption on pub.dev. DO NOT publish.
publish_to: none
environment:
sdk: ^3.8.0
resolution: workspace
# Use 'any' constraints here; we get our versions from the DEPS file.
dependencies:
dart_runtime_service: any
json_rpc_2: any
logging: any
+3 -1
View File
@@ -442,7 +442,9 @@ class VmTarget extends Target {
importer.path.contains('runtime/tests/vm/dart') ||
importer.path.contains('tests/standalone/io') ||
importer.path.contains('test-lib') ||
importer.path.contains('tests/ffi');
importer.path.contains('tests/ffi') ||
(importer.path == 'dart_runtime_service_vm/src/native_bindings.dart' &&
imported.path == '_vmservice');
@override
Component configureComponent(Component component) {
+1
View File
@@ -39,6 +39,7 @@ workspace:
- pkg/dart_data_home
- pkg/dart_internal
- pkg/dart_runtime_service
- pkg/dart_runtime_service_vm
- pkg/dart_service_protocol_shared
- pkg/dds
- pkg/dds_service_extensions
+4
View File
@@ -245,6 +245,10 @@ config("dart_config") {
defines += [ "DART_DYNAMIC_MODULES" ]
}
if (include_experimental_vm_service) {
defines += [ "EXPERIMENTAL_VM_SERVICE" ]
}
if (is_fuchsia) {
lib_dirs = [ "${fuchsia_arch_root}/lib" ]
+4 -16
View File
@@ -1065,24 +1065,12 @@ void main(int argc, char** argv) {
Loader::InitOnce();
// Setup script_name to point to the dartdev AOT snapshot.
auto dartdev_path = DartDev::ResolvedSnapshotPath();
char* script_name = dartdev_path.get();
if (script_name == nullptr || !CheckForInvalidPath(script_name)) {
Syslog::PrintErr("Unable to find AOT snapshot for dartdev\n");
auto [app_snapshot, script_name] =
Snapshot::TryReadSDKSnapshot("dartdev_aot.dart.snapshot");
if (app_snapshot == nullptr) {
FreeConvertedArgs(argc, argv, argv_converted);
Platform::Exit(kErrorExitCode);
}
AppSnapshot* app_snapshot = Snapshot::TryReadAppSnapshot(
script_name, /*force_load_from_memory*/ false, /*decode_uri*/ false);
if (app_snapshot == nullptr || !app_snapshot->IsAOT()) {
Syslog::PrintErr("%s is not an AOT snapshot\n", script_name);
FreeConvertedArgs(argc, argv, argv_converted);
if (app_snapshot != nullptr) {
delete app_snapshot;
}
Platform::Exit(kErrorExitCode);
}
app_snapshot->SetBuffers(
&ignore_vm_snapshot_data, &ignore_vm_snapshot_instructions,
&app_isolate_snapshot_data, &app_isolate_snapshot_instructions);
@@ -1152,7 +1140,7 @@ void main(int argc, char** argv) {
// - Exit the process due to some command parsing errors
// - Run the Dart script in a JIT mode by execing the JIT runtime
// - Run the Dart AOT snapshot by creating a new Isolate
DartDev::RunDartDev(script_name, &dart_vm_options, &dart_options);
DartDev::RunDartDev(script_name.get(), &dart_vm_options, &dart_options);
// Terminate process exit-code handler.
Process::TerminateExitCodeHandler();
+30 -14
View File
@@ -532,8 +532,6 @@ static Dart_Isolate CreateAndSetupKernelIsolate(const char* script_uri,
#endif // !defined(EXCLUDE_CFE_AND_KERNEL_PLATFORM)
// Returns newly created Service Isolate on success, nullptr on failure.
// For now we only support the service isolate coming up from sources
// which are compiled by the VM parser.
static Dart_Isolate CreateAndSetupServiceIsolate(const char* script_uri,
const char* packages_config,
Dart_IsolateFlags* flags,
@@ -547,30 +545,48 @@ static Dart_Isolate CreateAndSetupServiceIsolate(const char* script_uri,
packages_config, nullptr, false);
ASSERT(flags != nullptr);
const uint8_t* isolate_snapshot_data = nullptr;
const uint8_t* isolate_snapshot_instructions = nullptr;
#if defined(EXPERIMENTAL_VM_SERVICE)
if (Options::experimental_vm_service()) {
VmService::enable_experimental_vm_service = true;
auto [app_snapshot, script_name] = Snapshot::TryReadSDKSnapshot(
#if defined(DART_PRECOMIPLED_RUNTIME)
"dart_runtime_service_vm_aot.dart.snapshot");
#else
"dart_runtime_service_vm.dart.snapshot");
#endif // defined(DART_PRECOMPILED_RUNTIME)
if (app_snapshot == nullptr) {
Platform::Exit(kErrorExitCode);
}
const uint8_t* ignore_vm_snapshot_data;
const uint8_t* ignore_vm_snapshot_instructions;
app_snapshot->SetBuffers(
&ignore_vm_snapshot_data, &ignore_vm_snapshot_instructions,
&isolate_snapshot_data, &isolate_snapshot_instructions);
} else {
#endif // defined(EXPERIMENTAL_VM_SERVICE)
#if defined(DART_PRECOMPILED_RUNTIME)
// AOT: The service isolate is included in any AOT snapshot in non-PRODUCT
// mode - so we launch the vm-service from the main app AOT snapshot.
const uint8_t* isolate_snapshot_data = app_isolate_snapshot_data;
const uint8_t* isolate_snapshot_instructions =
app_isolate_snapshot_instructions;
isolate = Dart_CreateIsolateGroup(
script_uri, DART_VM_SERVICE_ISOLATE_NAME, isolate_snapshot_data,
isolate_snapshot_instructions, flags, isolate_group_data,
/*isolate_data=*/nullptr, error);
isolate_snapshot_data = app_isolate_snapshot_data;
isolate_snapshot_instructions = app_isolate_snapshot_instructions;
#else
// JIT: Service isolate uses the core libraries snapshot.
// Set flag to load and retain the vmservice library.
flags->load_vmservice_library = true;
flags->null_safety = true; // Service isolate runs in sound null safe mode.
const uint8_t* isolate_snapshot_data = core_isolate_snapshot_data;
const uint8_t* isolate_snapshot_instructions =
core_isolate_snapshot_instructions;
isolate_snapshot_data = core_isolate_snapshot_data;
isolate_snapshot_instructions = core_isolate_snapshot_instructions;
#endif // defined(DART_PRECOMPILED_RUNTIME)
#if defined(EXPERIMENTAL_VM_SERVICE)
}
#endif // defined(EXPERIMENTAL_VM_SERVICE)
isolate = Dart_CreateIsolateGroup(
script_uri, DART_VM_SERVICE_ISOLATE_NAME, isolate_snapshot_data,
isolate_snapshot_instructions, flags, isolate_group_data,
/*isolate_data=*/nullptr, error);
#endif // !defined(DART_PRECOMPILED_RUNTIME)
if (isolate == nullptr) {
delete isolate_group_data;
return nullptr;
+2 -1
View File
@@ -62,7 +62,8 @@ namespace bin {
V(profile_microtasks, profile_microtasks) \
/* The purpose of this flag is documented in */ \
/* pkg/dartdev/lib/src/commands/run.dart. */ \
V(resident, resident)
V(resident, resident) \
V(experimental_vm_service, experimental_vm_service)
// Boolean flags that have a short form.
#define SHORT_BOOL_OPTIONS_LIST(V) \
+62
View File
@@ -11,6 +11,7 @@
#include "bin/dfe.h"
#include "bin/elf_loader.h"
#include "bin/error_exit.h"
#include "bin/exe_utils.h"
#include "bin/file.h"
#include "bin/macho_loader.h"
#include "bin/platform.h"
@@ -846,6 +847,67 @@ static void WriteSnapshotFile(const char* filename,
}
#endif
// TODO(bkonyi): dedup
static bool CheckForInvalidPath(const char* path) {
// TODO(zichangguo): "\\?\" is a prefix for paths on Windows.
// Arguments passed are parsed as an URI. "\\?\" causes problems as a part
// of URIs. This is a temporary workaround to prevent VM from crashing.
// Issue: https://github.com/dart-lang/sdk/issues/42779
if (strncmp(path, R"(\\?\)", 4) == 0) {
Syslog::PrintErr(R"(\\?\ prefix is not supported)");
return false;
}
return true;
}
std::pair<AppSnapshot*, CStringUniquePtr> Snapshot::TryReadSDKSnapshot(
const char* snapshot_name) {
auto try_resolve_path = [&](CStringUniquePtr dir_prefix) {
// |dir_prefix| includes the last path separator.
// First assume we're in dart-sdk/bin.
char* snapshot_path =
Utils::SCreate("%ssnapshots/%s", dir_prefix.get(), snapshot_name);
if (File::Exists(nullptr, snapshot_path)) {
return CStringUniquePtr(snapshot_path);
}
free(snapshot_path);
// If we're not in dart-sdk/bin, we might be in one of the $SDK/out*/
// directories, Try to use a snapshot from that directory.
snapshot_path = Utils::SCreate("%s%s", dir_prefix.get(), snapshot_name);
if (File::Exists(nullptr, snapshot_path)) {
return CStringUniquePtr(snapshot_path);
}
free(snapshot_path);
return CStringUniquePtr(nullptr);
};
auto script_path =
try_resolve_path(EXEUtils::GetDirectoryPrefixFromResolvedExeName());
if (script_path == nullptr) {
script_path =
try_resolve_path(EXEUtils::GetDirectoryPrefixFromUnresolvedExeName());
}
if (script_path == nullptr || !CheckForInvalidPath(script_path.get())) {
Syslog::PrintErr("Unable to find snapshot: %s\n", snapshot_name);
return std::make_pair(static_cast<AppSnapshot*>(nullptr),
std::move(script_path));
}
AppSnapshot* app_snapshot =
TryReadAppSnapshot(script_path.get(), /*force_load_from_memory*/ false,
/*decode_uri*/ false);
if (app_snapshot == nullptr) {
Syslog::PrintErr("%s is not a valid snapshot\n", script_path.get());
if (app_snapshot != nullptr) {
delete app_snapshot;
}
return std::make_pair(static_cast<AppSnapshot*>(nullptr),
std::move(script_path));
}
return std::make_pair(app_snapshot, std::move(script_path));
}
static bool WriteInt64(File* file, int64_t size) {
return file->WriteFully(&size, sizeof(size));
}
+4
View File
@@ -5,6 +5,8 @@
#ifndef RUNTIME_BIN_SNAPSHOT_UTILS_H_
#define RUNTIME_BIN_SNAPSHOT_UTILS_H_
#include <utility>
#include "bin/dartutils.h"
#include "platform/globals.h"
@@ -59,6 +61,8 @@ class Snapshot {
static AppSnapshot* TryReadAppSnapshot(const char* script_uri,
bool force_load_from_memory = false,
bool decode_uri = true);
static std::pair<AppSnapshot*, CStringUniquePtr> TryReadSDKSnapshot(
const char* snapshot_name);
static void WriteAppSnapshot(const char* filename,
uint8_t* isolate_data_buffer,
intptr_t isolate_data_size,
+20 -4
View File
@@ -20,6 +20,8 @@ namespace bin {
#if !defined(PRODUCT)
bool VmService::enable_experimental_vm_service = false;
#define RETURN_ERROR_HANDLE(handle) \
if (Dart_IsError(handle)) { \
return handle; \
@@ -33,9 +35,6 @@ namespace bin {
return false; \
}
static constexpr const char* kVMServiceIOLibraryUri = "dart:vmservice_io";
static constexpr const char* DEFAULT_VM_SERVICE_SERVER_IP = "localhost";
void NotifyServerState(Dart_NativeArguments args) {
Dart_EnterScope();
const char* uri_chars;
@@ -66,6 +65,8 @@ struct VmServiceIONativeEntry {
};
static VmServiceIONativeEntry _VmServiceIONativeEntries[] = {
// TODO(bkonyi): these aren't used by any known embedders and can be
// removed.
{"VMServiceIO_NotifyServerState", 1, NotifyServerState},
{"VMServiceIO_Shutdown", 0, Shutdown},
};
@@ -105,7 +106,12 @@ const uint8_t* VmServiceIONativeSymbol(Dart_NativeFunction nf) {
const char* VmService::error_msg_ = nullptr;
char VmService::server_uri_[kServerUriStringBufferSize];
static constexpr const char* kVMServiceIOLibraryUri = "dart:vmservice_io";
void VmService::SetNativeResolver() {
if (enable_experimental_vm_service) {
return;
}
Dart_Handle url = DartUtils::NewString(kVMServiceIOLibraryUri);
Dart_Handle library = Dart_LookupLibrary(url);
if (!Dart_IsError(library)) {
@@ -114,6 +120,8 @@ void VmService::SetNativeResolver() {
}
}
static constexpr const char* DEFAULT_VM_SERVICE_SERVER_IP = "localhost";
bool VmService::Setup(const char* server_ip,
intptr_t server_port,
bool dev_mode_server,
@@ -140,6 +148,7 @@ bool VmService::Setup(const char* server_ip,
/*flag_profile_microtasks=*/false, DartIoSettings{});
SHUTDOWN_ON_ERROR(result);
if (!enable_experimental_vm_service) {
Dart_Handle url = DartUtils::NewString(kVMServiceIOLibraryUri);
Dart_Handle library = Dart_LookupLibrary(url);
SHUTDOWN_ON_ERROR(library);
@@ -148,6 +157,7 @@ bool VmService::Setup(const char* server_ip,
result = Dart_SetNativeResolver(library, VmServiceIONativeResolver,
VmServiceIONativeSymbol);
SHUTDOWN_ON_ERROR(result);
}
// Make runnable.
Dart_ExitScope();
@@ -161,7 +171,7 @@ bool VmService::Setup(const char* server_ip,
Dart_EnterIsolate(isolate);
Dart_EnterScope();
library = Dart_RootLibrary();
Dart_Handle library = Dart_RootLibrary();
SHUTDOWN_ON_ERROR(library);
// Set HTTP server state.
@@ -172,6 +182,12 @@ bool VmService::Setup(const char* server_ip,
// port when the HTTP server is started.
server_port = 0;
}
#if defined(EXPERIMENTAL_VM_SERVICE)
if (enable_experimental_vm_service) {
// TODO(bkonyi): remove once DDS support is added.
wait_for_dds_to_advertise_service = false;
}
#endif
if (wait_for_dds_to_advertise_service) {
result = DartUtils::SetStringField(library, "_ddsIP", server_ip);
SHUTDOWN_ON_ERROR(result);
+6
View File
@@ -64,6 +64,12 @@ class VmService {
// argument to this parameter should be null.
const char* resident_compiler_info_file_path);
// Specifies that the experimental VM service implementation should be used.
// TODO(bkonyi): remove this variable when the experimental service is
// stable. This was only added as a public static to avoid temporarily
// modifying the signature of Setup(...).
static bool enable_experimental_vm_service;
static void SetNativeResolver();
// Error message if startup failed.
-1
View File
@@ -139,5 +139,4 @@ DEFINE_NATIVE_ENTRY(VMService_CancelStream, 0, 1) {
#endif
return Object::null();
}
} // namespace dart
+20 -4
View File
@@ -54,6 +54,8 @@ declare_args() {
# ........dart2bytecode.snapshot (AOT snapshot, for selected targets)
# ........dart2js_aot.dart.snapshot (AOT snapshot)
# ........dart2wasm_product.snapshot (AOT snapshot)
# ........dart_runtime_service_vm_aot.snapshot (AOT snapshot)
# ........dart_runtime_service_vm.snapshot (JIT snapshot)
# ........dartdev_aot.dart.snapshot (AOT snapshot)
# ........dartdevc_aot.dart.snapshot (AOT snapshot)
# ........dds_aot.dart.snapshot (AOT snapshot)
@@ -153,6 +155,13 @@ if (dart_target_arch != "ia32" && dart_target_arch != "x86") {
"dart_mcp_server_aot",
],
]
if (include_experimental_vm_service) {
_platform_sdk_snapshots += [ [
"dart_runtime_service_vm_aot",
"../utils/dart_runtime_service_vm:dart_runtime_service_vm_aot",
"dart_runtime_service_vm_aot",
] ]
}
} else {
_platform_sdk_snapshots += [ [
"dds",
@@ -160,12 +169,20 @@ if (dart_target_arch != "ia32" && dart_target_arch != "x86") {
"dds",
] ]
}
if (dart_snapshot_kind == "app-jit") {
_platform_sdk_snapshots += [ [
"kernel-service",
"../utils/kernel-service:kernel-service_snapshot",
"kernel-service",
] ]
if (include_experimental_vm_service) {
_platform_sdk_snapshots += [ [
"dart_runtime_service_vm",
"../utils/dart_runtime_service_vm:dart_runtime_service_vm",
"dart_runtime_service_vm",
] ]
}
}
# dart2bytecode is an AOT snapshot, which is not supported on the ia32
@@ -398,9 +415,7 @@ if (!using_sanitizer && current_os == "linux" &&
"tsan",
]
if (is_clang) {
sanitizers += [
"msan",
]
sanitizers += [ "msan" ]
}
} else if (current_cpu == "riscv64") {
# Fuchsia Clang is missing the riscv64 MSAN runtime.
@@ -536,7 +551,8 @@ foreach(snapshot, _full_sdk_snapshots) {
# The dartdev, dds and dtd snapshots are output to root_out_dir in order to
# be compatible with the way the dart sdk is distributed internally.
if (snapshot[0] == "dartdev_aot" || snapshot[0] == "dds_aot_product" ||
snapshot[0] == "dart_tooling_daemon_aot_product") {
snapshot[0] == "dart_tooling_daemon_aot_product" ||
snapshot[0] == "dart_runtime_service_vm_aot") {
root = root_out_dir
}
copy("copy_${snapshot[0]}_snapshot") {
+10 -15
View File
@@ -290,7 +290,7 @@ class VMService extends MessageRouter {
clients.remove(client);
for (final streamId in client.streams) {
if (!_isAnyClientSubscribed(streamId)) {
_vmCancelStream(streamId);
vmCancelStream(streamId);
}
}
for (final pair in client.createdServiceIdZones) {
@@ -444,7 +444,7 @@ class VMService extends MessageRouter {
await VMServiceEmbedderHooks.cleanup!();
await clearState();
// Notify the VM that we have exited.
_onExit();
onExit();
}
void messageHandler(message) {
@@ -497,7 +497,7 @@ class VMService extends MessageRouter {
if (instance == null) {
instance = VMService._internal();
VMService._instance = instance;
_onStart();
onStart();
}
return instance;
}
@@ -533,7 +533,7 @@ class VMService extends MessageRouter {
if (!_isAnyClientSubscribed(streamId)) {
final includePrivates = message.params['_includePrivateMembers'] == true;
if (!serviceStreams.contains(streamId) &&
!_vmListenStream(streamId, includePrivates)) {
!vmListenStream(streamId, includePrivates)) {
return encodeRpcError(
message,
kInvalidParams,
@@ -569,7 +569,7 @@ class VMService extends MessageRouter {
client.streams.remove(streamId);
if (!serviceStreams.contains(streamId) &&
!_isAnyClientSubscribed(streamId)) {
_vmCancelStream(streamId);
vmCancelStream(streamId);
}
return encodeSuccess(message);
@@ -807,24 +807,19 @@ void _registerIsolate(int port_id, SendPort sp, String name) =>
/// Notify the VM that the service is running.
@pragma("vm:external-name", "VMService_OnStart")
external void _onStart();
external void onStart();
/// Notify the VM that the service is no longer running.
@pragma("vm:external-name", "VMService_OnExit")
external void _onExit();
/// Notify the VM that the server's address has changed.
void onServerAddressChange(String? address) {
_onServerAddressChange(address);
}
external void onExit();
@pragma("vm:external-name", "VMService_OnServerAddressChange")
external void _onServerAddressChange(String? address);
external void onServerAddressChange(String? address);
/// Subscribe to a service stream.
@pragma("vm:external-name", "VMService_ListenStream")
external bool _vmListenStream(String streamId, bool include_privates);
external bool vmListenStream(String streamId, bool include_privates);
/// Cancel a subscription to a service stream.
@pragma("vm:external-name", "VMService_CancelStream")
external void _vmCancelStream(String streamId);
external void vmCancelStream(String streamId);
+6
View File
@@ -38,6 +38,12 @@ declare_args() {
# can significantly improve iteration time when iteration on changes in
# core libraries.
precompile_tools = false
# When set to `true`, the Dart Runtime Service based implementation of the
# VM service will be included in the build. By default, the legacy VM
# service implementation will be launched by the VM, unless the
# --include-experimental-vm-service VM flag is provided.
include_experimental_vm_service = false
}
if (default_git_folder == "") {
+3
View File
@@ -1045,6 +1045,7 @@
"script": "tools/build.py",
"arguments": [
"--codesigning-identity=-",
"--include-experimental-vm-service",
"runtime"
]
},
@@ -1075,6 +1076,7 @@
"script": "tools/build.py",
"arguments": [
"--codesigning-identity=-",
"--include-experimental-vm-service",
"runtime"
]
},
@@ -1155,6 +1157,7 @@
"script": "tools/build.py",
"arguments": [
"--codesigning-identity=-",
"--include-experimental-vm-service",
"runtime",
"dartaotruntime"
]
+9
View File
@@ -280,6 +280,10 @@ def ToGnArgs(args, mode, arch, target_os, sanitizer, verify_sdk_hash,
if not args.platform_sdk:
gn_args['dart_platform_sdk'] = args.platform_sdk
if args.include_experimental_vm_service:
gn_args[
'include_experimental_vm_service'] = args.include_experimental_vm_service
# We don't support stripping on Windows
if host_os != 'win':
gn_args['dart_stripped_binary'] = 'exe.stripped/dart'
@@ -555,6 +559,11 @@ def AddCommonGnOptionArgs(parser):
help='Sign executables using the given identity.',
default='',
type=str)
parser.add_argument(
'--include-experimental-vm-service',
help='Use the Dart Runtime Service based VM service implementation.',
default=False,
action='store_true')
def AddCommonConfigurationArgs(parser):
+33
View File
@@ -0,0 +1,33 @@
# 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("../../runtime/runtime_args.gni")
import("../aot_snapshot.gni")
import("../application_snapshot.gni")
group("dart_runtime_service_vm_aot") {
public_deps = [ ":dart_runtime_service_vm_aot_snapshot" ]
}
aot_snapshot("dart_runtime_service_vm_aot_snapshot") {
main_dart = "../../pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart"
output = "$root_out_dir/dart_runtime_service_vm_aot.dart.snapshot"
}
group("dart_runtime_service_vm") {
public_deps = [ ":copy_dart_runtime_service_vm_snapshot" ]
}
copy("copy_dart_runtime_service_vm_snapshot") {
visibility = [ ":dart_runtime_service_vm" ]
public_deps = [ ":generate_dart_runtime_service_vm_snapshot" ]
sources = [ "$root_gen_dir/dart_runtime_service_vm.dart.snapshot" ]
outputs = [ "$root_out_dir/dart_runtime_service_vm.dart.snapshot" ]
}
application_snapshot("generate_dart_runtime_service_vm_snapshot") {
main_dart = "../../pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart"
training_args = [ "--help" ]
output = "$root_gen_dir/dart_runtime_service_vm.dart.snapshot"
}