[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 <nshahan@google.com> Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
521b102563
commit
0b68c62fc5
@@ -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<void> main([List<String> args = const []]) async {
|
||||
host: _ddsIP,
|
||||
port: _ddsPort,
|
||||
),
|
||||
residentCompilerInfoFile: _residentCompilerInfoFile,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class DartRuntimeServiceVMBackend
|
||||
required super.frontend,
|
||||
required this.signalWatch,
|
||||
required Stream<VmRunningIsolate> 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;
|
||||
|
||||
@@ -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<ServiceRpcHandler>([
|
||||
(_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<RpcResponse> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = <String, Object?>{
|
||||
kIsolateId: isolateId,
|
||||
final commonParams = <String, Object?>{
|
||||
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 = <String, Object?>{
|
||||
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<RpcResponse> _compileExpressionWithResidentFrontendServer({
|
||||
required Map<String, Object?> commonParams,
|
||||
required Map<String, Object?> scope,
|
||||
}) async {
|
||||
final {
|
||||
kExpression: expression as String,
|
||||
kDefinitions: definitions as List<Object?>,
|
||||
kDefinitionTypes: definitionTypes as List<Object?>,
|
||||
kTypeDefinitions: typeDefinitions as List<Object?>,
|
||||
kTypeBounds: typeBounds as List<Object?>,
|
||||
kTypeDefaults: typeDefaults as List<Object?>,
|
||||
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<String>(),
|
||||
definitionTypes: definitionTypes.cast<String>(),
|
||||
typeDefinitions: typeDefinitions.cast<String>(),
|
||||
typeBounds: typeBounds.cast<String>(),
|
||||
typeDefaults: typeDefaults.cast<String>(),
|
||||
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<RpcResponse> _evaluateCompiledExpression({
|
||||
required String isolateId,
|
||||
required String expression,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ resolution: workspace
|
||||
dependencies:
|
||||
dart_runtime_service: any
|
||||
file: any
|
||||
frontend_server: any
|
||||
json_rpc_2: any
|
||||
logging: any
|
||||
meta: any
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Map<String, dynamic>> sendAndReceiveResponse(
|
||||
Future<Map<String, Object?>> sendAndReceiveResponse(
|
||||
String request,
|
||||
File serverInfoFile,
|
||||
) async {
|
||||
Socket? client;
|
||||
Map<String, dynamic> jsonResponse;
|
||||
Map<String, Object?> jsonResponse;
|
||||
final ResidentCompilerInfo residentCompilerInfo =
|
||||
ResidentCompilerInfo.fromFile(serverInfoFile);
|
||||
|
||||
@@ -109,9 +110,9 @@ Future<Map<String, dynamic>> sendAndReceiveResponse(
|
||||
);
|
||||
client.write(request);
|
||||
final String data = new String.fromCharCodes(await client.first);
|
||||
jsonResponse = jsonDecode(data);
|
||||
jsonResponse = (jsonDecode(data) as Map<String, Object?>);
|
||||
} catch (e) {
|
||||
jsonResponse = <String, dynamic>{
|
||||
jsonResponse = <String, Object?>{
|
||||
'success': false,
|
||||
'errorMessage': e.toString(),
|
||||
};
|
||||
@@ -129,12 +130,159 @@ Future<bool> invokeReplaceCachedDill({
|
||||
required String replacementDillPath,
|
||||
required File serverInfoFile,
|
||||
}) async {
|
||||
final Map<String, dynamic> response = await sendAndReceiveResponse(
|
||||
final Map<String, Object?> 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<String> 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<String> 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<CompileResult> invokeCompile({
|
||||
required String executable,
|
||||
required String outputDill,
|
||||
required File serverInfoFile,
|
||||
}) async {
|
||||
final Map<String, Object?> 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<Object?> 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<Object?>?)?.cast<String>() ??
|
||||
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<CompileExpressionResult> invokeCompileExpression({
|
||||
required String expression,
|
||||
required List<String> definitions,
|
||||
required List<String> definitionTypes,
|
||||
required List<String> typeDefinitions,
|
||||
required List<String> typeBounds,
|
||||
required List<String> 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<String, Object?> 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<Object?> 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<Object?>?)?.cast<String>() ??
|
||||
const [],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<void> 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<void> testeeMain() async {
|
||||
}
|
||||
|
||||
final breakpointResolutionAfterReloadingTests = <IsolateTest>[
|
||||
// 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 = <IsolateTest>[
|
||||
// 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 = <IsolateTest>[
|
||||
) 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 = <IsolateTest>[
|
||||
) 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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user