From 0b68c62fc56184327508e89ea4a17a2ed43ef3c6 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 30 Apr 2026 17:04:19 -0700 Subject: [PATCH] [Service] Add support for resident frontend server to package:dart_runtime_service_vm TEST=Existing, ran locally. Change-Id: Idc1f35eb3d4cf0c7251a64b02b801e8110cc323b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499000 Reviewed-by: Nicholas Shahan Commit-Queue: Ben Konyi --- .../bin/vm_service_entrypoint.dart | 18 +- .../lib/dart_runtime_service_vm.dart | 3 + .../lib/src/dart_runtime_service_vm_rpcs.dart | 69 ++++++++ .../lib/src/vm_expression_evaluator.dart | 61 ++++++- .../lib/src/vm_isolate_manager.dart | 2 +- pkg/dart_runtime_service_vm/pubspec.yaml | 1 + pkg/dds/test/common/test_helper.dart | 10 +- .../lib/resident_frontend_server_utils.dart | 162 +++++++++++++++++- ...esolution_after_reloading_test_common.dart | 92 +++++----- pkg/vm_service/test/common/test_helper.dart | 10 +- runtime/bin/dartdev_options.cc | 8 + runtime/bin/dartdev_options.h | 8 + runtime/vm/service.cc | 2 - 13 files changed, 375 insertions(+), 71 deletions(-) diff --git a/pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart b/pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart index f5bfaf500f2..fb30cdf2add 100644 --- a/pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart +++ b/pkg/dart_runtime_service_vm/bin/vm_service_entrypoint.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:isolate'; +import 'dart:vmservice_io' show getResidentCompilerInfoFileConsideringArgsImpl; import 'package:dart_runtime_service/dart_runtime_service.dart'; import 'package:dart_runtime_service_vm/dart_runtime_service_vm.dart'; @@ -90,19 +91,23 @@ bool _waitForDdsToAdvertiseService = false; @entrypoint bool _printDtd = false; -// ignore: unused_element File? _residentCompilerInfoFile; @entrypoint +/// Sets the resident compiler info file, which is used to configure the +/// service to utilize a resident compiler. +/// +/// 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 [residentCompilerInfoFilePathArgumentFromCli]. If neither +/// option was supplied, the argument to this parameter should be null. // 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 + _residentCompilerInfoFile = getResidentCompilerInfoFileConsideringArgsImpl( + residentCompilerInfoFilePathArgumentFromCli, + ); } @pragma('vm:entry-point', 'get') @@ -131,6 +136,7 @@ Future main([List args = const []]) async { host: _ddsIP, port: _ddsPort, ), + residentCompilerInfoFile: _residentCompilerInfoFile, ), ); } diff --git a/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart b/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart index 4c5e4200287..124090ebdb2 100644 --- a/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart +++ b/pkg/dart_runtime_service_vm/lib/dart_runtime_service_vm.dart @@ -33,6 +33,7 @@ class DartRuntimeServiceVMBackend required super.frontend, required this.signalWatch, required Stream runningIsolatesStream, + required this.residentCompilerInfoFile, required this._ddsManager, }) : isolateManager = VmIsolateManager( runningIsolatesStream: runningIsolatesStream, @@ -82,6 +83,8 @@ class DartRuntimeServiceVMBackend late final _vmServiceRpcs = DartRuntimeServiceVmRpcs(backend: this); + final File? residentCompilerInfoFile; + /// Adds support for launching and accepting connections from the /// Dart Development Service. final DartDevelopmentServiceManager _ddsManager; diff --git a/pkg/dart_runtime_service_vm/lib/src/dart_runtime_service_vm_rpcs.dart b/pkg/dart_runtime_service_vm/lib/src/dart_runtime_service_vm_rpcs.dart index 04c1e4f321f..20c3fa3976c 100644 --- a/pkg/dart_runtime_service_vm/lib/src/dart_runtime_service_vm_rpcs.dart +++ b/pkg/dart_runtime_service_vm/lib/src/dart_runtime_service_vm_rpcs.dart @@ -5,6 +5,9 @@ import 'dart:collection'; import 'package:dart_runtime_service/dart_runtime_service.dart'; +import 'package:file/local.dart'; +import 'package:frontend_server/resident_frontend_server_utils.dart' + as frontend_server; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc_2; import 'package:logging/logging.dart'; import 'package:vm_service/vm_service.dart'; @@ -26,16 +29,22 @@ final class DartRuntimeServiceVmRpcs { static const _kCreateIdZone = 'createIdZone'; static const _kDeleteIdZone = 'deleteIdZone'; static const _kStreamCpuSamplesWithUserTag = 'streamCpuSamplesWithUserTag'; + static const _kReloadSources = 'reloadSources'; + static const _kReloadKernel = '_reloadKernel'; static const _kIsolateId = 'isolateId'; static const _kIdZoneId = 'idZoneId'; static const _kUserTags = 'userTags'; + static const _kRootLibUri = 'rootLibUri'; + static const _kForce = 'force'; + static const _kKernelFilePath = 'kernelFilePath'; late final rpcs = UnmodifiableListView([ (_kGetSupportedProtocols, getSupportedProtocols), (_kCreateIdZone, createIdZone), (_kDeleteIdZone, deleteIdZone), (_kStreamCpuSamplesWithUserTag, streamCpuSamplesWithUserTag), + (_kReloadSources, reloadSources), ]); /// Returns the list of protocols implemented by the service. @@ -108,4 +117,64 @@ final class DartRuntimeServiceVmRpcs { parameters[_kUserTags].asList; return Success().toJson(); } + + /// Performs a hot reload of the sources of all isolates in the same isolate + /// group as the isolate specified by `isolateId`. + Future reloadSources(json_rpc_2.Parameters parameters) async { + final isolateId = parameters[_kIsolateId].asString; + final residentCompilerInfoFile = backend.residentCompilerInfoFile; + if (residentCompilerInfoFile == null || + !residentCompilerInfoFile.existsSync()) { + _logger.info( + 'Resident compiler not configured: $residentCompilerInfoFile.', + ); + return backend.sendToRuntime(parameters); + } + _logger.info( + 'Resident compiler is configured and will be used to compile ' + 'sources to kernel before reloading.', + ); + + var rootLibUri = parameters[_kRootLibUri].exists + ? parameters[_kRootLibUri].asString + : null; + if (rootLibUri == null) { + final result = Isolate.parse( + await backend.isolateManager.sendToIsolate( + method: 'getIsolate', + params: {_kIsolateId: isolateId}, + ), + ); + rootLibUri = result!.rootLib!.uri!; + } + + final tempDir = const LocalFileSystem().systemTempDirectory + .createTempSync(); + try { + final outputDill = tempDir.childFile('for_hot_reload.dill'); + try { + await frontend_server.invokeCompile( + executable: Uri.parse(rootLibUri).toFilePath(), + outputDill: outputDill.path, + serverInfoFile: residentCompilerInfoFile, + ); + } on frontend_server.CompileException catch (e) { + _logger.warning('Kernel compilation request failed: $e'); + RpcException.internalError.throwExceptionWithDetails( + details: e.message, + ); + } + + return await backend.isolateManager.sendToIsolate( + method: _kReloadKernel, + params: { + _kIsolateId: isolateId, + _kKernelFilePath: outputDill.uri.toFilePath(), + _kForce: parameters[_kForce].asBoolOr(false), + }, + ); + } finally { + tempDir.deleteSync(recursive: true); + } + } } diff --git a/pkg/dart_runtime_service_vm/lib/src/vm_expression_evaluator.dart b/pkg/dart_runtime_service_vm/lib/src/vm_expression_evaluator.dart index d2d3131bc6a..de9721f9267 100644 --- a/pkg/dart_runtime_service_vm/lib/src/vm_expression_evaluator.dart +++ b/pkg/dart_runtime_service_vm/lib/src/vm_expression_evaluator.dart @@ -5,6 +5,8 @@ import 'dart:async'; import 'package:dart_runtime_service/dart_runtime_service.dart'; +import 'package:frontend_server/resident_frontend_server_utils.dart' + as frontend_server; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; import '../dart_runtime_service_vm.dart'; @@ -162,8 +164,7 @@ final class VmExpressionEvaluator extends ExpressionEvaluator { String expression, ExpressionEvaluationScope scope, ) async { - final compileParams = { - kIsolateId: isolateId, + final commonParams = { kExpression: expression, kDefinitions: scope[kParamNames], kDefinitionTypes: scope[kParamTypes], @@ -171,12 +172,16 @@ final class VmExpressionEvaluator extends ExpressionEvaluator { kTypeBounds: scope[kTypeParamsBounds], kTypeDefaults: scope[kTypeParamsDefaults], kLibraryUri: scope[kLibraryUri], - kTokenPos: scope[kTokenPos], kIsStatic: scope[kIsStatic], - kKlass: ?scope[kKlass], kMethod: ?scope[kMethod], kScriptUri: ?scope[kScriptUri], }; + final compileParams = { + kIsolateId: isolateId, + kTokenPos: scope[kTokenPos], + kKlass: ?scope[kKlass], + ...commonParams, + }; final externalClient = clients.findFirstClientThatHandlesService( kExternalCompileExpressionRpc, @@ -192,6 +197,12 @@ final class VmExpressionEvaluator extends ExpressionEvaluator { method: kExternalCompileExpressionRpc, parameters: compileParams, ); + } else if (backend.residentCompilerInfoFile?.existsSync() ?? false) { + logger.info('Using resident frontend server for compilation.'); + result = await _compileExpressionWithResidentFrontendServer( + commonParams: commonParams, + scope: scope, + ); } else { result = await backend.sendToRuntime( json_rpc.Parameters(kInternalCompileExpressionRpc, compileParams), @@ -207,6 +218,48 @@ final class VmExpressionEvaluator extends ExpressionEvaluator { } } + Future _compileExpressionWithResidentFrontendServer({ + required Map commonParams, + required Map scope, + }) async { + final { + kExpression: expression as String, + kDefinitions: definitions as List, + kDefinitionTypes: definitionTypes as List, + kTypeDefinitions: typeDefinitions as List, + kTypeBounds: typeBounds as List, + kTypeDefaults: typeDefaults as List, + kLibraryUri: libraryUri as String, + kIsStatic: isStatic as bool, + } = commonParams; + + final method = commonParams[kMethod] as String?; + final scriptUri = commonParams[kScriptUri] as String?; + + try { + final result = await frontend_server.invokeCompileExpression( + expression: expression, + definitions: definitions.cast(), + definitionTypes: definitionTypes.cast(), + typeDefinitions: typeDefinitions.cast(), + typeBounds: typeBounds.cast(), + typeDefaults: typeDefaults.cast(), + libraryUri: libraryUri, + klass: scope[kKlass] as String?, + method: method, + offset: scope[kTokenPos] as int, + scriptUri: scriptUri, + isStatic: isStatic, + serverInfoFile: backend.residentCompilerInfoFile!, + ); + return {kKernelBytes: result.kernelBytes}; + } on frontend_server.CompileException catch (e) { + RpcException.expressionCompilationError.throwExceptionWithDetails( + details: e.message, + ); + } + } + Future _evaluateCompiledExpression({ required String isolateId, required String expression, diff --git a/pkg/dart_runtime_service_vm/lib/src/vm_isolate_manager.dart b/pkg/dart_runtime_service_vm/lib/src/vm_isolate_manager.dart index 094c944c57b..264adc4afb7 100644 --- a/pkg/dart_runtime_service_vm/lib/src/vm_isolate_manager.dart +++ b/pkg/dart_runtime_service_vm/lib/src/vm_isolate_manager.dart @@ -73,7 +73,7 @@ final class VmIsolateManager extends IsolateManager { /// Reports that an isolate is shutting down based on a message over the /// service's control port. void onIsolateShutdownMessage({required int id}) { - _logger.info('Isolate startup message received for isolate ID $id'); + _logger.info('Received isolate shutdown message for isolate $id'); isolateExited(id: id); } diff --git a/pkg/dart_runtime_service_vm/pubspec.yaml b/pkg/dart_runtime_service_vm/pubspec.yaml index b09b942eb58..ef3fc97b176 100644 --- a/pkg/dart_runtime_service_vm/pubspec.yaml +++ b/pkg/dart_runtime_service_vm/pubspec.yaml @@ -11,6 +11,7 @@ resolution: workspace dependencies: dart_runtime_service: any file: any + frontend_server: any json_rpc_2: any logging: any meta: any diff --git a/pkg/dds/test/common/test_helper.dart b/pkg/dds/test/common/test_helper.dart index 51c1fc9885a..4d9a677f367 100644 --- a/pkg/dds/test/common/test_helper.dart +++ b/pkg/dds/test/common/test_helper.dart @@ -325,10 +325,12 @@ class _ServiceTesteeLauncher { } } -void setupAddresses(Uri /*!*/ serverAddress) { - serviceWebsocketAddress = - 'ws://${serverAddress.authority}${serverAddress.path}ws'; - serviceHttpAddress = 'http://${serverAddress.authority}${serverAddress.path}'; +void setupAddresses(Uri serverAddress) { + serviceWebsocketAddress = serverAddress.replace( + scheme: 'ws', + pathSegments: [...serverAddress.pathSegments, 'ws'], + ).toString(); + serviceHttpAddress = serverAddress.replace(scheme: 'http').toString(); } class _ServiceTesterRunner { diff --git a/pkg/frontend_server/lib/resident_frontend_server_utils.dart b/pkg/frontend_server/lib/resident_frontend_server_utils.dart index 3bafef87420..610b30ff141 100644 --- a/pkg/frontend_server/lib/resident_frontend_server_utils.dart +++ b/pkg/frontend_server/lib/resident_frontend_server_utils.dart @@ -3,7 +3,8 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:convert' show jsonDecode, jsonEncode; -import 'dart:io' show Directory, File, InternetAddress, Socket; +import 'dart:io' + show Directory, File, FileSystemException, InternetAddress, Socket; import 'package:path/path.dart' as path; @@ -93,12 +94,12 @@ CachedDillAndCompilerOptionsPaths computeCachedDillAndCompilerOptionsPaths( /// with [serverInfoFile], and returns the compiler's JSON response. /// /// Throws a [FileSystemException] if [serverInfoFile] cannot be accessed. -Future> sendAndReceiveResponse( +Future> sendAndReceiveResponse( String request, File serverInfoFile, ) async { Socket? client; - Map jsonResponse; + Map jsonResponse; final ResidentCompilerInfo residentCompilerInfo = ResidentCompilerInfo.fromFile(serverInfoFile); @@ -109,9 +110,9 @@ Future> sendAndReceiveResponse( ); client.write(request); final String data = new String.fromCharCodes(await client.first); - jsonResponse = jsonDecode(data); + jsonResponse = (jsonDecode(data) as Map); } catch (e) { - jsonResponse = { + jsonResponse = { 'success': false, 'errorMessage': e.toString(), }; @@ -129,12 +130,159 @@ Future invokeReplaceCachedDill({ required String replacementDillPath, required File serverInfoFile, }) async { - final Map response = await sendAndReceiveResponse( + final Map response = await sendAndReceiveResponse( jsonEncode({ 'command': 'replaceCachedDill', 'replacementDillPath': replacementDillPath, }), serverInfoFile, ); - return response['success']; + return response['success'] == true; +} + +/// The result of a successful compilation request sent to a resident +/// frontend compiler. +final class CompileResult { + /// The absolute path to the kernel file produced by the compiler. + final String outputDill; + + /// The number of errors produced by the compiler. + final int errorCount; + + /// The output lines produced by the compiler, if any. + final List compilerOutputLines; + + CompileResult({ + required this.outputDill, + required this.errorCount, + this.compilerOutputLines = const [], + }); +} + +/// The result of a successful expression compilation request sent to a +/// resident frontend compiler. +final class CompileExpressionResult { + /// The base64 encoded kernel bytes produced by the compiler. + final String kernelBytes; + + /// The number of errors produced by the compiler. + final int errorCount; + + /// The output lines produced by the compiler, if any. + final List compilerOutputLines; + + CompileExpressionResult({ + required this.kernelBytes, + required this.errorCount, + this.compilerOutputLines = const [], + }); +} + +/// The exception thrown when a compilation request to the resident frontend +/// compiler fails. +final class CompileException implements Exception { + /// The error message from the compiler. + final String message; + + CompileException(this.message); + + @override + String toString() => 'CompileException: $message'; +} + +/// Sends a 'compile' request to the resident frontend compiler associated with +/// [serverInfoFile], and returns a [CompileResult] on success. +/// +/// Throws a [CompileException] if compilation fails. +/// Throws a [FileSystemException] if [serverInfoFile] cannot be accessed. +Future invokeCompile({ + required String executable, + required String outputDill, + required File serverInfoFile, +}) async { + final Map response = await sendAndReceiveResponse( + jsonEncode({ + 'command': 'compile', + 'executable': executable, + 'output-dill': outputDill, + 'useCachedCompilerOptionsAsBase': true, + }), + serverInfoFile, + ); + + if (response['success'] != true) { + final String errorMessage = switch (response) { + {'errorMessage': final String errorMessage} => errorMessage, + {'compilerOutputLines': final List lines} => lines.join('\n'), + _ => 'Unknown error: $response', + }; + throw new CompileException(errorMessage); + } + + return new CompileResult( + outputDill: response['output-dill'] as String, + errorCount: response['errorCount'] as int, + compilerOutputLines: + (response['compilerOutputLines'] as List?)?.cast() ?? + const [], + ); +} + +/// Sends a 'compileExpression' request to the resident frontend compiler +/// associated with [serverInfoFile], and returns a [CompileExpressionResult] +/// on success. +/// +/// Throws a [CompileException] if compilation fails. +/// Throws a [FileSystemException] if [serverInfoFile] cannot be accessed. +Future invokeCompileExpression({ + required String expression, + required List definitions, + required List definitionTypes, + required List typeDefinitions, + required List typeBounds, + required List typeDefaults, + required String libraryUri, + required String? klass, + required String? method, + required int offset, + required String? scriptUri, + required bool isStatic, + required File serverInfoFile, +}) async { + final Map response = await sendAndReceiveResponse( + jsonEncode({ + 'command': 'compileExpression', + 'expression': expression, + 'definitions': definitions, + 'definitionTypes': definitionTypes, + 'typeDefinitions': typeDefinitions, + 'typeBounds': typeBounds, + 'typeDefaults': typeDefaults, + 'libraryUri': libraryUri, + if (klass != null) 'class': klass, + if (method != null) 'method': method, + 'offset': offset, + if (scriptUri != null) 'scriptUri': scriptUri, + 'isStatic': isStatic, + 'useCachedCompilerOptionsAsBase': true, + }), + serverInfoFile, + ); + + if (response['success'] != true) { + final String errorMessage = switch (response) { + {'errorMessage': final String errorMessage} => errorMessage, + {'compilerOutputLines': final List lines} => lines.join('\n'), + _ => 'Unknown error: $response', + }; + throw new CompileException(errorMessage); + } + + return new CompileExpressionResult( + kernelBytes: response['kernelBytes'] as String, + errorCount: response['errorCount'] as int, + compilerOutputLines: + (response['compilerOutputLines'] as List?)?.cast() ?? + const [], + ); } diff --git a/pkg/vm_service/test/breakpoint_resolution_after_reloading_test_common.dart b/pkg/vm_service/test/breakpoint_resolution_after_reloading_test_common.dart index 54323280b78..06932130f9d 100644 --- a/pkg/vm_service/test/breakpoint_resolution_after_reloading_test_common.dart +++ b/pkg/vm_service/test/breakpoint_resolution_after_reloading_test_common.dart @@ -2,6 +2,7 @@ // 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:developer' show debugger; import 'dart:io' show Directory, File; import 'dart:isolate' as i; @@ -23,13 +24,17 @@ const LINE_A = 77; const _v0Contents = ''' import 'dart:developer'; +import 'dart:isolate'; -void f() {} +void f() { + print('V0.a'); +} void main() { + print('READY'); + // Keep the isolate alive. + RawReceivePort(); debugger(); - f(); - f(); } '''; @@ -44,10 +49,7 @@ void f() { })(); } -void main() { - f(); - f(); -} +void main() {} '''; const _v2Contents = ''' @@ -56,24 +58,22 @@ import 'dart:developer'; void f() { (() { print('v2.a'); - print('v2.b'); + (() { + print('v2.b'); + })(); })(); } -void main() { - f(); - f(); -} +void main() {} '''; Future testeeMain() async { - // Spawn the child isolate. final tempDir = Directory.systemTemp.createTempSync(); try { final rootLib = File(join(tempDir.path, 'main.dart')); rootLib.writeAsStringSync(_v0Contents); - await i.Isolate.spawnUri(rootLib.uri, [], null); + await i.Isolate.spawnUri(rootLib.uri, [], null, debugName: 'Test Main'); debugger(); // LINE_A tempDir.deleteSync(recursive: true); } catch (_) { @@ -83,8 +83,6 @@ Future testeeMain() async { } final breakpointResolutionAfterReloadingTests = [ - // Ensure that the main isolate has stopped at the [debugger] statement at the - // end of [testeeMain]. hasStoppedAtBreakpoint, stoppedAtLine(LINE_A), (VmService service, IsolateRef isolateRef) async { @@ -100,13 +98,16 @@ final breakpointResolutionAfterReloadingTests = [ // Find the spawned isolate. final vm = await service.getVM(); final isolates = vm.isolates!; - expect(isolates.length, 2); - final spawnedIsolateRef = isolates.firstWhere( - (i) => i != isolateRef, - ); + final spawnedIsolateRef = + isolates.firstWhere((i) => i.name == 'Test Main'); final spawnedIsolateId = spawnedIsolateRef.id!; - // Load [v1Contents] into the spawned isolate. + // Wait for spawned isolate to hit its debugger() + await hasStoppedAtBreakpoint(service, spawnedIsolateRef); + // Resume it so it finishes main() and sits idle. + await resumeIsolate(service, spawnedIsolateRef); + + // --- STEP 1: RELOAD V1 --- spawnedIsolateRootLib.writeAsStringSync(_v1Contents); await service.reloadSources( spawnedIsolateId, @@ -121,16 +122,23 @@ final breakpointResolutionAfterReloadingTests = [ ) as Library; String scriptId = rootLib.scripts![0].id!; - // Add a breakpoint at `print('v1');`. - await service.addBreakpoint(spawnedIsolateId, scriptId, 6); + // Add a breakpoint at `print('v1');` (line 6 of v1). + final Breakpoint bpt1 = + await service.addBreakpoint(spawnedIsolateId, scriptId, 6); + + // Trigger f() in the reloaded code. + // This ensures a fresh entry into the reloaded f(). + print('Invoking f() V1'); + unawaited(service.invoke(spawnedIsolateId, rootLib.id!, 'f', [])); - // Resuming the spawned isolate should let it run until it gets paused at - // the breakpoint at `print('v1');`. - await resumeIsolate(service, spawnedIsolateRef); await hasStoppedAtBreakpoint(service, spawnedIsolateRef); await stoppedAtLine(6)(service, spawnedIsolateRef); - // Load [v2Contents] into the spawned isolate. + // Remove the breakpoint before moving to V2. + await service.removeBreakpoint(spawnedIsolateId, bpt1.id!); + await resumeIsolate(service, spawnedIsolateRef); + + // --- STEP 2: RELOAD V2 --- spawnedIsolateRootLib.writeAsStringSync(_v2Contents); await service.reloadSources( spawnedIsolateId, @@ -145,27 +153,25 @@ final breakpointResolutionAfterReloadingTests = [ ) as Library; scriptId = rootLib.scripts![0].id!; - // Add a breakpoint at `print('v2.a');`. - await service.addBreakpoint(spawnedIsolateId, scriptId, 5); + // Add a breakpoint at `print('v2.a');` (line 5 of v2). + final Breakpoint bpt2 = + await service.addBreakpoint(spawnedIsolateId, scriptId, 5); + + print('Invoking f() V2'); + unawaited(service.invoke(spawnedIsolateId, rootLib.id!, 'f', [])); - // Resuming the spawned isolate should let it run until it gets paused at - // the breakpoint at `print('v2.a');`. - await resumeIsolate(service, spawnedIsolateRef); await hasStoppedAtBreakpoint(service, spawnedIsolateRef); await stoppedAtLine(5)(service, spawnedIsolateRef); - // Add a breakpoint at `print('v2.b');`. - final breakpoint3 = - await service.addBreakpoint(spawnedIsolateId, scriptId, 6); - expect(breakpoint3.breakpointNumber, 3); - - // We previously had a bug that would have made the breakpoint resolution - // code get confused by the old closure that was defined in [v1Contents]. - // We prevent a reintroduction of that bug by ensuring that the newly set - // breakpoint has been resolved immediately. - expect(breakpoint3.resolved, true); - + // Remove the breakpoint. + await service.removeBreakpoint(spawnedIsolateId, bpt2.id!); await resumeIsolate(service, spawnedIsolateRef); + + // Add a breakpoint at `print('v2.b');` (line 6 of v2). + final bpt = await service.addBreakpoint(spawnedIsolateId, scriptId, 6); + expect(bpt.resolved, true); + + // No need for final resume as the isolate is already running and idle. tempDir.deleteSync(recursive: true); } catch (_) { tempDir.deleteSync(recursive: true); diff --git a/pkg/vm_service/test/common/test_helper.dart b/pkg/vm_service/test/common/test_helper.dart index a2a1fad1885..1e08094106d 100644 --- a/pkg/vm_service/test/common/test_helper.dart +++ b/pkg/vm_service/test/common/test_helper.dart @@ -284,10 +284,12 @@ class _ServiceTesteeLauncher { } } -void setupAddresses(Uri /*!*/ serverAddress) { - serviceWebsocketAddress = - 'ws://${serverAddress.authority}${serverAddress.path}ws'; - serviceHttpAddress = 'http://${serverAddress.authority}${serverAddress.path}'; +void setupAddresses(Uri serverAddress) { + serviceWebsocketAddress = serverAddress.replace( + scheme: 'ws', + pathSegments: [...serverAddress.pathSegments, 'ws'], + ).toString(); + serviceHttpAddress = serverAddress.replace(scheme: 'http').toString(); } class _ServiceTesterRunner { diff --git a/runtime/bin/dartdev_options.cc b/runtime/bin/dartdev_options.cc index 1e3a60da8e7..fbc7d62fb3a 100644 --- a/runtime/bin/dartdev_options.cc +++ b/runtime/bin/dartdev_options.cc @@ -220,6 +220,11 @@ bool Options::ParseDartDevArguments(int argc, skipVmOption = true; } else if (IsOption(argv[i], "enable-experiment")) { dart_options->AddArgument(argv[i]); + } else if (IsOption(argv[i], "resident")) { + resident_ = true; + } else if (IsOption(argv[i], "resident-compiler-info-file")) { + resident_compiler_info_file_path_ = OptionProcessor::ProcessOption( + argv[i], "--resident-compiler-info-file"); } } if (!skipVmOption) { @@ -346,6 +351,9 @@ void Options::PrintUsage() { } // clang-format on +bool Options::resident_ = false; +const char* Options::resident_compiler_info_file_path_ = nullptr; + dart::SimpleHashMap* Options::environment_ = nullptr; bool Options::ProcessEnvironmentOption(const char* arg, CommandLineOptions* vm_options) { diff --git a/runtime/bin/dartdev_options.h b/runtime/bin/dartdev_options.h index 253a9e8ba71..1e7fd72453d 100644 --- a/runtime/bin/dartdev_options.h +++ b/runtime/bin/dartdev_options.h @@ -76,6 +76,11 @@ class Options { SHORT_BOOL_OPTIONS_LIST(SHORT_BOOL_OPTION_GETTER) #undef SHORT_BOOL_OPTION_GETTER + static bool resident() { return resident_; } + static const char* resident_compiler_info_file_path() { + return resident_compiler_info_file_path_; + } + // Callbacks have to be public. #define CB_OPTIONS_DECL(callback) \ static bool callback(const char* arg, CommandLineOptions* vm_options); @@ -113,6 +118,9 @@ class Options { SHORT_BOOL_OPTIONS_LIST(SHORT_BOOL_OPTION_DECL) #undef SHORT_BOOL_OPTION_DECL + static bool resident_; + static const char* resident_compiler_info_file_path_; + static dart::SimpleHashMap* environment_; static char** env_argv_; diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index 19bcd48fda9..594c421beb5 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -4010,8 +4010,6 @@ static void ReloadKernel(Thread* thread, JSONStream* js) { isolate_group->ReloadKernel(js, force_reload, kernel_buffer, kernel_buffer_size); - free(kernel_buffer); - Service::CheckForPause(isolate, js); #endif // defined(DART_PRECOMPILED_RUNTIME) }