[VM/Service] Use the resident frontend server for hot reload when it's available
TEST=pkg/vm_service/test/reload_sources_with_resident_compiler_test.dart and pkg/vm_service/test/breakpoint_resolution_after_reloading_with_resident_compiler_test.dart CoreLibraryReviewExempt: This CL does not include any core library API changes, only VM Service implementation changes within sdk/lib/vmservice/. Change-Id: Ibc99cd37439ddd8aca97fa7e18a5112cbfc3b4cb Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/401646 Reviewed-by: Ben Konyi <bkonyi@google.com>
This commit is contained in:
@@ -191,6 +191,7 @@ vm_service/test/regress_55559_test: SkipByDesign # Spawns a child process from s
|
||||
vm_service/test/regress_88104_test: SkipByDesign # Debugger is disabled in AOT mode.
|
||||
vm_service/test/reload_sources_rpc_triggers_isolate_reload_event_test: SkipByDesign # Hot reload is disabled in AOT mode.
|
||||
vm_service/test/reload_sources_test: SkipByDesign # Hot reload is disabled in AOT mode.
|
||||
vm_service/test/reload_sources_with_resident_compiler_test: SkipByDesign # Hot reload is disabled in AOT mode.
|
||||
vm_service/test/resume_shutdown_race_test: SkipByDesign # Debugger is disabled in AOT mode.
|
||||
vm_service/test/rewind*: SkipByDesign # Debugger is disabled in AOT mode.
|
||||
vm_service/test/sdk_break_with_mixin_test: SkipByDesign # Debugger is disabled in AOT mode.
|
||||
|
||||
@@ -2,182 +2,12 @@
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:developer' show debugger;
|
||||
import 'dart:io' show Directory, File;
|
||||
import 'dart:isolate' as i;
|
||||
|
||||
import 'package:path/path.dart' show join;
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/service_test_common.dart';
|
||||
import 'breakpoint_resolution_after_reloading_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
// AUTOGENERATED START
|
||||
//
|
||||
// Update these constants by running:
|
||||
//
|
||||
// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
|
||||
//
|
||||
const LINE_A = 78;
|
||||
// AUTOGENERATED END
|
||||
|
||||
const v0Contents = '''
|
||||
import 'dart:developer';
|
||||
|
||||
void f() {}
|
||||
|
||||
void main() {
|
||||
debugger();
|
||||
f();
|
||||
f();
|
||||
}
|
||||
''';
|
||||
|
||||
const v1Contents = '''
|
||||
import 'dart:developer';
|
||||
|
||||
void f() {
|
||||
(() {
|
||||
(() {
|
||||
print('v1');
|
||||
})();
|
||||
})();
|
||||
}
|
||||
|
||||
void main() {
|
||||
f();
|
||||
f();
|
||||
}
|
||||
''';
|
||||
|
||||
const v2Contents = '''
|
||||
import 'dart:developer';
|
||||
|
||||
void f() {
|
||||
(() {
|
||||
print('v2.a');
|
||||
print('v2.b');
|
||||
})();
|
||||
}
|
||||
|
||||
void main() {
|
||||
f();
|
||||
f();
|
||||
}
|
||||
''';
|
||||
|
||||
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);
|
||||
debugger(); // LINE_A
|
||||
tempDir.deleteSync(recursive: true);
|
||||
} catch (_) {
|
||||
tempDir.deleteSync(recursive: true);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
final tests = <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 {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
try {
|
||||
// This test is a regression test against a bug caused by comparing script
|
||||
// URLs instead of script pointers in the debugger. To produce a situation
|
||||
// in which the bug used to occur, this test loads
|
||||
// [spawnedIsolateRootLib], modifies [spawnedIsolateRootLib], and then
|
||||
// reloads [spawnedIsolateRootLib].
|
||||
final spawnedIsolateRootLib = File(join(tempDir.path, 'main.dart'));
|
||||
|
||||
// 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 spawnedIsolateId = spawnedIsolateRef.id!;
|
||||
|
||||
// Load [v1Contents] into the spawned isolate.
|
||||
spawnedIsolateRootLib.writeAsStringSync(v1Contents);
|
||||
await service.reloadSources(
|
||||
spawnedIsolateId,
|
||||
rootLibUri: spawnedIsolateRootLib.uri.toString(),
|
||||
force: true,
|
||||
);
|
||||
|
||||
Isolate spawnedIsolate = await service.getIsolate(spawnedIsolateId);
|
||||
Library rootLib = await service.getObject(
|
||||
spawnedIsolateId,
|
||||
spawnedIsolate.rootLib!.id!,
|
||||
) as Library;
|
||||
String scriptId = rootLib.scripts![0].id!;
|
||||
|
||||
// Add a breakpoint at `print('v1');`.
|
||||
await service.addBreakpoint(spawnedIsolateId, scriptId, 6);
|
||||
|
||||
// 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.
|
||||
spawnedIsolateRootLib.writeAsStringSync(v2Contents);
|
||||
await service.reloadSources(
|
||||
spawnedIsolateId,
|
||||
rootLibUri: spawnedIsolateRootLib.uri.toString(),
|
||||
force: true,
|
||||
);
|
||||
|
||||
spawnedIsolate = await service.getIsolate(spawnedIsolateId);
|
||||
rootLib = await service.getObject(
|
||||
spawnedIsolateId,
|
||||
spawnedIsolate.rootLib!.id!,
|
||||
) as Library;
|
||||
scriptId = rootLib.scripts![0].id!;
|
||||
|
||||
// Add a breakpoint at `print('v2.a');`.
|
||||
await service.addBreakpoint(spawnedIsolateId, scriptId, 5);
|
||||
|
||||
// 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);
|
||||
|
||||
await resumeIsolate(service, spawnedIsolateRef);
|
||||
tempDir.deleteSync(recursive: true);
|
||||
} catch (_) {
|
||||
tempDir.deleteSync(recursive: true);
|
||||
rethrow;
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
breakpointResolutionAfterReloadingTests,
|
||||
'breakpoint_resolution_after_reloading_test.dart',
|
||||
testeeConcurrent: testeeMain,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright (c) 2025, 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:developer' show debugger;
|
||||
import 'dart:io' show Directory, File;
|
||||
import 'dart:isolate' as i;
|
||||
|
||||
import 'package:path/path.dart' show join;
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/service_test_common.dart';
|
||||
|
||||
// AUTOGENERATED START
|
||||
//
|
||||
// Update these constants by running:
|
||||
//
|
||||
// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
|
||||
//
|
||||
const LINE_A = 77;
|
||||
// AUTOGENERATED END
|
||||
|
||||
const _v0Contents = '''
|
||||
import 'dart:developer';
|
||||
|
||||
void f() {}
|
||||
|
||||
void main() {
|
||||
debugger();
|
||||
f();
|
||||
f();
|
||||
}
|
||||
''';
|
||||
|
||||
const _v1Contents = '''
|
||||
import 'dart:developer';
|
||||
|
||||
void f() {
|
||||
(() {
|
||||
(() {
|
||||
print('v1');
|
||||
})();
|
||||
})();
|
||||
}
|
||||
|
||||
void main() {
|
||||
f();
|
||||
f();
|
||||
}
|
||||
''';
|
||||
|
||||
const _v2Contents = '''
|
||||
import 'dart:developer';
|
||||
|
||||
void f() {
|
||||
(() {
|
||||
print('v2.a');
|
||||
print('v2.b');
|
||||
})();
|
||||
}
|
||||
|
||||
void main() {
|
||||
f();
|
||||
f();
|
||||
}
|
||||
''';
|
||||
|
||||
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);
|
||||
debugger(); // LINE_A
|
||||
tempDir.deleteSync(recursive: true);
|
||||
} catch (_) {
|
||||
tempDir.deleteSync(recursive: true);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
try {
|
||||
// This test is a regression test against a bug caused by comparing script
|
||||
// URLs instead of script pointers in the debugger. To produce a situation
|
||||
// in which the bug used to occur, this test loads
|
||||
// [spawnedIsolateRootLib], modifies [spawnedIsolateRootLib], and then
|
||||
// reloads [spawnedIsolateRootLib].
|
||||
final spawnedIsolateRootLib = File(join(tempDir.path, 'main.dart'));
|
||||
|
||||
// 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 spawnedIsolateId = spawnedIsolateRef.id!;
|
||||
|
||||
// Load [v1Contents] into the spawned isolate.
|
||||
spawnedIsolateRootLib.writeAsStringSync(_v1Contents);
|
||||
await service.reloadSources(
|
||||
spawnedIsolateId,
|
||||
rootLibUri: spawnedIsolateRootLib.uri.toString(),
|
||||
force: true,
|
||||
);
|
||||
|
||||
Isolate spawnedIsolate = await service.getIsolate(spawnedIsolateId);
|
||||
Library rootLib = await service.getObject(
|
||||
spawnedIsolateId,
|
||||
spawnedIsolate.rootLib!.id!,
|
||||
) as Library;
|
||||
String scriptId = rootLib.scripts![0].id!;
|
||||
|
||||
// Add a breakpoint at `print('v1');`.
|
||||
await service.addBreakpoint(spawnedIsolateId, scriptId, 6);
|
||||
|
||||
// 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.
|
||||
spawnedIsolateRootLib.writeAsStringSync(_v2Contents);
|
||||
await service.reloadSources(
|
||||
spawnedIsolateId,
|
||||
rootLibUri: spawnedIsolateRootLib.uri.toString(),
|
||||
force: true,
|
||||
);
|
||||
|
||||
spawnedIsolate = await service.getIsolate(spawnedIsolateId);
|
||||
rootLib = await service.getObject(
|
||||
spawnedIsolateId,
|
||||
spawnedIsolate.rootLib!.id!,
|
||||
) as Library;
|
||||
scriptId = rootLib.scripts![0].id!;
|
||||
|
||||
// Add a breakpoint at `print('v2.a');`.
|
||||
await service.addBreakpoint(spawnedIsolateId, scriptId, 5);
|
||||
|
||||
// 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);
|
||||
|
||||
await resumeIsolate(service, spawnedIsolateRef);
|
||||
tempDir.deleteSync(recursive: true);
|
||||
} catch (_) {
|
||||
tempDir.deleteSync(recursive: true);
|
||||
rethrow;
|
||||
}
|
||||
},
|
||||
];
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2025, 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 'breakpoint_resolution_after_reloading_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
breakpointResolutionAfterReloadingTests,
|
||||
'breakpoint_resolution_after_reloading_with_resident_compiler_test.dart',
|
||||
testeeConcurrent: testeeMain,
|
||||
shouldTesteeBeLaunchedWithDartRunResident: true,
|
||||
);
|
||||
@@ -2,71 +2,12 @@
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/service_test_common.dart';
|
||||
import 'common/test_helper.dart';
|
||||
|
||||
// AUTOGENERATED START
|
||||
//
|
||||
// Update these constants by running:
|
||||
//
|
||||
// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
|
||||
//
|
||||
const LINE_A = 23;
|
||||
// AUTOGENERATED END
|
||||
|
||||
void testMain() {
|
||||
debugger(); // LINE_A.
|
||||
print('1');
|
||||
while (true) {}
|
||||
}
|
||||
|
||||
Future<void> isolateIsRunning(VmService service, IsolateRef isolateRef) async {
|
||||
final isolate = await service.getIsolate(isolateRef.id!);
|
||||
final pauseEvent = isolate.pauseEvent;
|
||||
final isPaused = pauseEvent == null
|
||||
? false
|
||||
: isolate.pauseEvent!.kind != EventKind.kResume;
|
||||
final topFrame = pauseEvent?.topFrame;
|
||||
expect(!isPaused && topFrame != null, true);
|
||||
}
|
||||
|
||||
final tests = <IsolateTest>[
|
||||
// Stopped at 'debugger' statement.
|
||||
hasStoppedAtBreakpoint,
|
||||
stoppedAtLine(LINE_A),
|
||||
// Reload sources and request to pause post reload. The pause request will be
|
||||
// ignored because we are already paused at a breakpoint.
|
||||
reloadSources(pause: true),
|
||||
// Ensure that we are still stopped at a breakpoint.
|
||||
hasStoppedAtBreakpoint,
|
||||
stoppedAtLine(LINE_A),
|
||||
// Resume the isolate into the while loop.
|
||||
resumeIsolate,
|
||||
// Verify that it is running.
|
||||
isolateIsRunning,
|
||||
// Reload sources and request to pause post reload. The pause request will
|
||||
// be respected because we are not already paused.
|
||||
reloadSources(pause: true),
|
||||
// Ensure that we are paused post reload request.
|
||||
hasStoppedPostRequest,
|
||||
// Resume the isolate.
|
||||
resumeIsolate,
|
||||
// Verify that it is running.
|
||||
isolateIsRunning,
|
||||
// Reload sources and do not request to pause post reload.
|
||||
reloadSources(),
|
||||
// Verify that it is running.
|
||||
isolateIsRunning,
|
||||
];
|
||||
import 'reload_sources_test_common.dart' show reloadSourcesTests, testeeMain;
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
tests,
|
||||
reloadSourcesTests,
|
||||
'reload_sources_test.dart',
|
||||
testeeConcurrent: testMain,
|
||||
testeeConcurrent: testeeMain,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2025, 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:developer';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
import 'common/service_test_common.dart';
|
||||
|
||||
// AUTOGENERATED START
|
||||
//
|
||||
// Update these constants by running:
|
||||
//
|
||||
// dart pkg/vm_service/test/update_line_numbers.dart <test.dart>
|
||||
//
|
||||
const LINE_A = 22;
|
||||
// AUTOGENERATED END
|
||||
|
||||
void testeeMain() {
|
||||
debugger(); // LINE_A.
|
||||
print('1');
|
||||
while (true) {}
|
||||
}
|
||||
|
||||
Future<void> isolateIsRunning(VmService service, IsolateRef isolateRef) async {
|
||||
final isolate = await service.getIsolate(isolateRef.id!);
|
||||
final pauseEvent = isolate.pauseEvent;
|
||||
final isPaused = pauseEvent == null
|
||||
? false
|
||||
: isolate.pauseEvent!.kind != EventKind.kResume;
|
||||
final topFrame = pauseEvent?.topFrame;
|
||||
expect(!isPaused && topFrame != null, true);
|
||||
}
|
||||
|
||||
final reloadSourcesTests = <IsolateTest>[
|
||||
// Stopped at 'debugger' statement.
|
||||
hasStoppedAtBreakpoint,
|
||||
stoppedAtLine(LINE_A),
|
||||
// Reload sources and request to pause post reload. The pause request will be
|
||||
// ignored because we are already paused at a breakpoint.
|
||||
reloadSources(pause: true),
|
||||
// Ensure that we are still stopped at a breakpoint.
|
||||
hasStoppedAtBreakpoint,
|
||||
stoppedAtLine(LINE_A),
|
||||
// Resume the isolate into the while loop.
|
||||
resumeIsolate,
|
||||
// Verify that it is running.
|
||||
isolateIsRunning,
|
||||
// Reload sources and request to pause post reload. The pause request will
|
||||
// be respected because we are not already paused.
|
||||
reloadSources(pause: true),
|
||||
// Ensure that we are paused post reload request.
|
||||
hasStoppedPostRequest,
|
||||
// Resume the isolate.
|
||||
resumeIsolate,
|
||||
// Verify that it is running.
|
||||
isolateIsRunning,
|
||||
// Reload sources and do not request to pause post reload.
|
||||
reloadSources(),
|
||||
// Verify that it is running.
|
||||
isolateIsRunning,
|
||||
];
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2025, 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 'common/test_helper.dart';
|
||||
import 'reload_sources_test_common.dart' show reloadSourcesTests, testeeMain;
|
||||
|
||||
void main([args = const <String>[]]) => runIsolateTests(
|
||||
args,
|
||||
reloadSourcesTests,
|
||||
'reload_sources_with_resident_compiler_test.dart',
|
||||
testeeConcurrent: testeeMain,
|
||||
shouldTesteeBeLaunchedWithDartRunResident: true,
|
||||
);
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "vm/heap/safepoint.h"
|
||||
#include "vm/isolate.h"
|
||||
#include "vm/json_stream.h"
|
||||
#include "vm/kernel.h"
|
||||
#include "vm/kernel_isolate.h"
|
||||
#include "vm/lockers.h"
|
||||
#include "vm/message.h"
|
||||
@@ -3953,6 +3954,88 @@ static void GetSourceReport(Thread* thread, JSONStream* js) {
|
||||
#endif // !DART_PRECOMPILED_RUNTIME
|
||||
}
|
||||
|
||||
static const MethodParameter* const reload_kernel_params[] = {
|
||||
RUNNABLE_ISOLATE_PARAMETER,
|
||||
new BoolParameter("force", false),
|
||||
new BoolParameter("pause", false),
|
||||
new StringParameter("kernelFilePath", false),
|
||||
nullptr,
|
||||
};
|
||||
|
||||
/// Replaces the program running in the isolate group containing the isolate
|
||||
/// specified by |js->LookupParam("isolateId")| with the program defined by
|
||||
/// |js->LookupParam("kernelFilePath")|.
|
||||
static void ReloadKernel(Thread* thread, JSONStream* js) {
|
||||
#if defined(DART_PRECOMPILED_RUNTIME)
|
||||
js->PrintError(kFeatureDisabled, "Compiler is disabled in AOT mode.");
|
||||
#else
|
||||
if (!js->HasParam("kernelFilePath")) {
|
||||
PrintMissingParamError(js, "kernelFilePath");
|
||||
return;
|
||||
}
|
||||
|
||||
IsolateGroup* isolate_group = thread->isolate_group();
|
||||
if (isolate_group->library_tag_handler() == nullptr) {
|
||||
js->PrintError(kFeatureDisabled,
|
||||
"A library tag handler must be installed.");
|
||||
return;
|
||||
}
|
||||
Isolate* isolate = thread->isolate();
|
||||
if ((isolate->sticky_error() != Error::null()) ||
|
||||
(Thread::Current()->sticky_error() != Error::null())) {
|
||||
js->PrintError(kIsolateReloadBarred,
|
||||
"The specified isolate cannot reload from a kernel file "
|
||||
"anymore because it encountered an unhandled exception. "
|
||||
"Restart the isolate.");
|
||||
return;
|
||||
}
|
||||
if (isolate_group->IsReloading()) {
|
||||
js->PrintError(kIsolateIsReloading,
|
||||
"The specified isolate group is already in the process of "
|
||||
"being reloaded.");
|
||||
return;
|
||||
}
|
||||
if (!isolate_group->CanReload()) {
|
||||
js->PrintError(kFeatureDisabled,
|
||||
"The specified isolate group cannot reload from a kernel "
|
||||
"file right now.");
|
||||
return;
|
||||
}
|
||||
|
||||
Dart_FileOpenCallback file_open = Dart::file_open_callback();
|
||||
Dart_FileReadCallback file_read = Dart::file_read_callback();
|
||||
Dart_FileCloseCallback file_close = Dart::file_close_callback();
|
||||
if ((file_open == nullptr) || (file_read == nullptr) ||
|
||||
(file_close == nullptr)) {
|
||||
js->PrintError(kInternalError,
|
||||
"An internal error occurred when trying to read the "
|
||||
"specified kernel file.");
|
||||
return;
|
||||
}
|
||||
|
||||
void* file = (*file_open)(js->LookupParam("kernelFilePath"), /*write=*/false);
|
||||
if (file == nullptr) {
|
||||
js->PrintError(kIsolateReloadBarred,
|
||||
"The specified kernel file could not be read. Please ensure "
|
||||
"that the provided 'kernelFilePath' argument is correct.");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* kernel_buffer = nullptr;
|
||||
intptr_t kernel_buffer_size = -1;
|
||||
(*file_read)(&kernel_buffer, &kernel_buffer_size, file);
|
||||
|
||||
const bool force_reload =
|
||||
BoolParameter::Parse(js->LookupParam("force"), false);
|
||||
isolate_group->ReloadKernel(js, force_reload, kernel_buffer,
|
||||
kernel_buffer_size);
|
||||
|
||||
free(kernel_buffer);
|
||||
|
||||
Service::CheckForPause(isolate, js);
|
||||
#endif // defined(DART_PRECOMPILED_RUNTIME)
|
||||
}
|
||||
|
||||
static const MethodParameter* const reload_sources_params[] = {
|
||||
RUNNABLE_ISOLATE_PARAMETER,
|
||||
new BoolParameter("force", false),
|
||||
@@ -6207,6 +6290,8 @@ static const ServiceMethodDescriptor service_methods_[] = {
|
||||
pause_params },
|
||||
{ "removeBreakpoint", RemoveBreakpoint,
|
||||
remove_breakpoint_params },
|
||||
{ "_reloadKernel", ReloadKernel,
|
||||
reload_kernel_params },
|
||||
{ "reloadSources", ReloadSources,
|
||||
reload_sources_params },
|
||||
{ "_reloadSources", ReloadSources,
|
||||
|
||||
@@ -10,7 +10,27 @@ final class _CompileExpressionErrorDetails {
|
||||
_CompileExpressionErrorDetails(this.details);
|
||||
}
|
||||
|
||||
/// The message in an error response from the resident frontend compiler can
|
||||
/// either be in the 'errorMessage' property or the 'compilerOutputLines'
|
||||
/// property of the response.
|
||||
String _extractErrorMessageFromResidentFrontendCompilerResponse(
|
||||
Map<String, dynamic> response,
|
||||
) {
|
||||
const errorMessageString = 'errorMessage';
|
||||
|
||||
if (response[errorMessageString] != null) {
|
||||
return response[errorMessageString];
|
||||
} else {
|
||||
return (response['compilerOutputLines'] as List<dynamic>)
|
||||
.cast<String>()
|
||||
.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
class RunningIsolates implements MessageRouter {
|
||||
static const _isolateIdString = 'isolateId';
|
||||
static const _successString = 'success';
|
||||
|
||||
final isolates = <int, RunningIsolate>{};
|
||||
int? _rootPortId;
|
||||
|
||||
@@ -31,6 +51,77 @@ class RunningIsolates implements MessageRouter {
|
||||
(isolates.remove(portId))?.onIsolateExit();
|
||||
}
|
||||
|
||||
Future<Response> _handleReloadSourcesRequest(
|
||||
VMService service,
|
||||
Message message,
|
||||
RunningIsolate isolate,
|
||||
) async {
|
||||
if (VMServiceEmbedderHooks.getResidentCompilerInfoFile!() == null) {
|
||||
// If there isn't a resident frontend compiler available, we let the VM
|
||||
// take care of the request.
|
||||
return isolate.routeRequest(service, message);
|
||||
} else {
|
||||
const rootLibUriString = 'rootLibUri';
|
||||
|
||||
final String rootLibUri;
|
||||
if (message.params[rootLibUriString] == null) {
|
||||
// If a 'rootLibUri' property was not included in the request, we have
|
||||
// to ask the VM for [isolate]'s root library URI.
|
||||
final getIsolateRequest = Message.forMethod('getIsolate');
|
||||
getIsolateRequest.params[_isolateIdString] =
|
||||
message.params[isolate.serviceId];
|
||||
|
||||
final getIsolateResponse = await isolate.routeRequest(
|
||||
service,
|
||||
getIsolateRequest,
|
||||
);
|
||||
final isolateJson =
|
||||
(getIsolateResponse.decodeJson() as Map<String, dynamic>)['result']
|
||||
as Map<String, dynamic>;
|
||||
final rootLibJson = isolateJson['rootLib'] as Map<String, dynamic>;
|
||||
rootLibUri = rootLibJson['uri'];
|
||||
} else {
|
||||
rootLibUri = message.params[rootLibUriString];
|
||||
}
|
||||
|
||||
final tempDirectory = Directory.systemTemp.createTempSync();
|
||||
final outputDill = File(
|
||||
'${tempDirectory.path}${Platform.pathSeparator}for_hot_reload.dill',
|
||||
);
|
||||
final responseFromResidentCompiler =
|
||||
await _sendRequestToResidentFrontendCompilerAndRecieveResponse(
|
||||
jsonEncode(<String, Object?>{
|
||||
'command': 'compile',
|
||||
'executable': Uri.parse(rootLibUri).toFilePath(),
|
||||
'output-dill': outputDill.path,
|
||||
}),
|
||||
VMServiceEmbedderHooks.getResidentCompilerInfoFile!()!,
|
||||
);
|
||||
|
||||
if (responseFromResidentCompiler[_successString] == false) {
|
||||
return Response.from(
|
||||
encodeRpcError(
|
||||
message,
|
||||
kInternalError,
|
||||
details: _extractErrorMessageFromResidentFrontendCompilerResponse(
|
||||
responseFromResidentCompiler,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final reloadKernelRequest = Message.forMethod('_reloadKernel');
|
||||
reloadKernelRequest.params[_isolateIdString] =
|
||||
message.params[isolate.serviceId];
|
||||
reloadKernelRequest.params['kernelFilePath'] =
|
||||
outputDill.uri.toFilePath();
|
||||
final response = isolate.routeRequest(service, message);
|
||||
|
||||
tempDirectory.deleteSync(recursive: true);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response> routeRequest(VMService service, Message message) {
|
||||
String isolateParam = message.params['isolateId']! as String;
|
||||
@@ -71,6 +162,8 @@ class RunningIsolates implements MessageRouter {
|
||||
|
||||
if (message.method == 'evaluateInFrame' || message.method == 'evaluate') {
|
||||
return _Evaluator(message, isolate, service).run();
|
||||
} else if (message.method == 'reloadSources') {
|
||||
return _handleReloadSourcesRequest(service, message, isolate);
|
||||
} else {
|
||||
return isolate.routeRequest(service, message);
|
||||
}
|
||||
@@ -128,11 +221,46 @@ final class _ResidentCompilerInfo {
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: The following function is a duplicate of one in
|
||||
// 'package:frontend_server/resident_frontend_server_utils.dart'. We are
|
||||
// forced to duplicate it because `dart:_vmservice` is not allowed to import
|
||||
// `package:frontend_server`.
|
||||
|
||||
/// Sends a compilation [request] to the resident frontend compiler associated
|
||||
/// with [serverInfoFile], and returns the compiler's JSON response.
|
||||
///
|
||||
/// Throws a [FileSystemException] if [serverInfoFile] cannot be accessed.
|
||||
Future<Map<String, dynamic>>
|
||||
_sendRequestToResidentFrontendCompilerAndRecieveResponse(
|
||||
String request,
|
||||
File serverInfoFile,
|
||||
) async {
|
||||
Socket? client;
|
||||
Map<String, dynamic> jsonResponse;
|
||||
final residentCompilerInfo = _ResidentCompilerInfo.fromFile(serverInfoFile);
|
||||
|
||||
try {
|
||||
client = await Socket.connect(
|
||||
residentCompilerInfo.address,
|
||||
residentCompilerInfo.port,
|
||||
);
|
||||
client.write(request);
|
||||
final data = String.fromCharCodes(await client.first);
|
||||
jsonResponse = jsonDecode(data);
|
||||
} catch (e) {
|
||||
jsonResponse = <String, dynamic>{
|
||||
'success': false,
|
||||
'errorMessage': e.toString(),
|
||||
};
|
||||
}
|
||||
client?.destroy();
|
||||
return jsonResponse;
|
||||
}
|
||||
|
||||
/// Class that knows how to orchestrate expression evaluation in dart2 world.
|
||||
class _Evaluator {
|
||||
static const _successString = 'success';
|
||||
static const _kernelBytesString = 'kernelBytes';
|
||||
static const _compileExpressionString = 'compileExpression';
|
||||
static const _isolateIdString = 'isolateId';
|
||||
static const _expressionString = 'expression';
|
||||
static const _definitionsString = 'definitions';
|
||||
static const _definitionTypesString = 'definitionTypes';
|
||||
@@ -212,7 +340,7 @@ class _Evaluator {
|
||||
Map<String, dynamic> response,
|
||||
) {
|
||||
if (response['result'] != null) {
|
||||
return (response['result'] as Map<String, dynamic>)['kernelBytes']
|
||||
return (response['result'] as Map<String, dynamic>)[_kernelBytesString]
|
||||
as String;
|
||||
}
|
||||
final error = response['error'] as Map<String, dynamic>;
|
||||
@@ -220,42 +348,6 @@ class _Evaluator {
|
||||
throw _CompileExpressionErrorDetails(data['details']);
|
||||
}
|
||||
|
||||
// NOTE: The following function is a duplicate of one in
|
||||
// 'package:frontend_server/resident_frontend_server_utils.dart'. We are
|
||||
// forced to duplicate it because `dart:_vmservice` is not allowed to import
|
||||
// `package:frontend_server`.
|
||||
|
||||
/// Sends a compilation [request] to the resident frontend compiler associated
|
||||
/// with [serverInfoFile], and returns the compiler's JSON response.
|
||||
///
|
||||
/// Throws a [FileSystemException] if [serverInfoFile] cannot be accessed.
|
||||
static Future<Map<String, dynamic>>
|
||||
_sendRequestToResidentFrontendCompilerAndRecieveResponse(
|
||||
String request,
|
||||
File serverInfoFile,
|
||||
) async {
|
||||
Socket? client;
|
||||
Map<String, dynamic> jsonResponse;
|
||||
final residentCompilerInfo = _ResidentCompilerInfo.fromFile(serverInfoFile);
|
||||
|
||||
try {
|
||||
client = await Socket.connect(
|
||||
residentCompilerInfo.address,
|
||||
residentCompilerInfo.port,
|
||||
);
|
||||
client.write(request);
|
||||
final data = String.fromCharCodes(await client.first);
|
||||
jsonResponse = jsonDecode(data);
|
||||
} catch (e) {
|
||||
jsonResponse = <String, dynamic>{
|
||||
_successString: false,
|
||||
'errorMessage': e.toString(),
|
||||
};
|
||||
}
|
||||
client?.destroy();
|
||||
return jsonResponse;
|
||||
}
|
||||
|
||||
/// If compilation fails, this method will throw a
|
||||
/// [_CompileExpressionErrorDetails] object that will be used to populate the
|
||||
/// 'details' field of the response to the evaluation RPC that requested this
|
||||
@@ -268,7 +360,8 @@ class _Evaluator {
|
||||
);
|
||||
|
||||
final compileParams = <String, dynamic>{
|
||||
_isolateIdString: _message.params[_isolateIdString]!,
|
||||
RunningIsolates._isolateIdString:
|
||||
_message.params[RunningIsolates._isolateIdString]!,
|
||||
_expressionString: _message.params[_expressionString]!,
|
||||
_definitionsString: buildScopeResponseResult['param_names']!,
|
||||
_definitionTypesString: buildScopeResponseResult['param_types']!,
|
||||
@@ -346,14 +439,12 @@ class _Evaluator {
|
||||
VMServiceEmbedderHooks.getResidentCompilerInfoFile!()!,
|
||||
);
|
||||
|
||||
if (response[_successString] == true) {
|
||||
return response['kernelBytes'];
|
||||
} else if (response['errorMessage'] != null) {
|
||||
throw _CompileExpressionErrorDetails(response['errorMessage']);
|
||||
if (response[RunningIsolates._successString] == true) {
|
||||
return response[_kernelBytesString];
|
||||
} else {
|
||||
final compilerOutputLines =
|
||||
(response['compilerOutputLines'] as List<dynamic>).cast<String>();
|
||||
throw _CompileExpressionErrorDetails(compilerOutputLines.join('\n'));
|
||||
throw _CompileExpressionErrorDetails(
|
||||
_extractErrorMessageFromResidentFrontendCompilerResponse(response),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// fallback to compile using kernel service
|
||||
|
||||
@@ -7,7 +7,7 @@ library dart._vmservice;
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:isolate';
|
||||
import 'dart:io' show File, InternetAddress, Socket;
|
||||
import 'dart:io' show Directory, File, InternetAddress, Platform, Socket;
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user