[ 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:
@@ -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';
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -139,5 +139,4 @@ DEFINE_NATIVE_ENTRY(VMService_CancelStream, 0, 1) {
|
||||
#endif
|
||||
return Object::null();
|
||||
}
|
||||
|
||||
} // namespace dart
|
||||
|
||||
+20
-4
@@ -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") {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 == "") {
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user