Commit 9fdd4f47 authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

[fuchsia_reload] Reload each instance of a module. (#9379)

parent ea71bdca
......@@ -40,15 +40,24 @@ class FuchsiaReloadCommand extends FlutterCommand {
help: 'Path to Fuchsia source tree.');
argParser.addOption('gn-target',
abbr: 'g',
help: 'GN target of the application, e.g //path/to/app:app');
help: 'GN target of the application, e.g //path/to/app:app.');
argParser.addFlag('list',
abbr: 'l',
defaultsTo: false,
help: 'Lists the running modules. '
'Requires the flags --address(-a) and --fuchsia-root(-f).');
argParser.addOption('name-override',
abbr: 'n',
help: 'On-device name of the application binary.');
argParser.addOption('isolate-number',
abbr: 'i',
help: 'To reload only one instance, speficy the isolate number, e.g. '
'the number in foo\$main-###### given by --list.');
argParser.addOption('target',
abbr: 't',
defaultsTo: flx.defaultMainPath,
help: 'Target app path / main entry-point file. '
'Relative to --gn-target path, e.g. lib/main.dart');
'Relative to --gn-target path, e.g. lib/main.dart.');
}
@override
......@@ -61,11 +70,14 @@ class FuchsiaReloadCommand extends FlutterCommand {
String _projectRoot;
String _projectName;
String _binaryName;
String _isolateNumber;
String _fuchsiaProjectPath;
String _target;
String _address;
String _dotPackagesPath;
bool _list;
@override
Future<Null> runCommand() async {
Cache.releaseLockEarly();
......@@ -79,9 +91,14 @@ class FuchsiaReloadCommand extends FlutterCommand {
for (int port in servicePorts)
printTrace('Fuchsia service port: $port');
if (_list) {
await _listViews(servicePorts);
return;
}
// Check that there are running VM services on the returned
// ports, and find the Isolates that are running the target app.
final String isolateName = '$_binaryName\$main';
final String isolateName = '$_binaryName\$main$_isolateNumber';
final List<int> targetPorts = await _filterPorts(servicePorts, isolateName);
if (targetPorts.isEmpty)
throwToolExit('No VMs found running $_binaryName.');
......@@ -90,43 +107,55 @@ class FuchsiaReloadCommand extends FlutterCommand {
// Set up a device and hot runner and attach the hot runner to the first
// vm service we found.
final int firstPort = targetPorts[0];
final String fullAddress = '$_address:$firstPort';
final FuchsiaDevice device = new FuchsiaDevice(fullAddress);
final List<String> fullAddresses = targetPorts.map(
(int p) => '$_address:$p'
).toList();
final FuchsiaDevice device = new FuchsiaDevice(fullAddresses[0]);
final HotRunner hotRunner = new HotRunner(
device,
debuggingOptions: new DebuggingOptions.enabled(getBuildMode()),
target: _target,
projectRootPath: _fuchsiaProjectPath,
packagesFilePath: _dotPackagesPath,
packagesFilePath: _dotPackagesPath
);
final Uri observatoryUri = Uri.parse('http://$fullAddress');
printStatus('Connecting to $_binaryName at $observatoryUri');
await hotRunner.attach(observatoryUri, isolateFilter: isolateName);
final List<Uri> observatoryUris =
fullAddresses.map((String a) => Uri.parse('http://$a')).toList();
printStatus('Connecting to $_binaryName');
await hotRunner.attach(observatoryUris, isolateFilter: isolateName);
}
// Find ports where there is a view isolate with the given name
Future<List<int>> _filterPorts(List<int> ports, String isolateFilter) async {
final List<int> result = <int>[];
Future<List<FlutterView>> _getViews(List<int> ports) async {
final List<FlutterView> views = <FlutterView>[];
for (int port in ports) {
final String addr = 'http://$_address:$port';
final Uri uri = Uri.parse(addr);
final VMService vmService = VMService.connect(uri);
await vmService.getVM();
await vmService.waitForViews();
if (vmService.vm.firstView == null) {
printTrace('Found no views at $addr');
continue;
}
for (FlutterView v in vmService.vm.views) {
printTrace('At $addr, found view: ${v.uiIsolate.name}');
if (v.uiIsolate.name.indexOf(isolateFilter) == 0)
result.add(port);
}
views.addAll(vmService.vm.views);
}
return views;
}
// Find ports where there is a view isolate with the given name
Future<List<int>> _filterPorts(List<int> ports, String isolateFilter) async {
final List<int> result = <int>[];
for (FlutterView v in await _getViews(ports)) {
final Uri addr = v.owner.vmService.httpAddress;
printTrace('At $addr, found view: ${v.uiIsolate.name}');
if (v.uiIsolate.name.indexOf(isolateFilter) == 0)
result.add(addr.port);
}
return result;
}
Future<Null> _listViews(List<int> ports) async {
for (FlutterView v in await _getViews(ports)) {
final Uri addr = v.owner.vmService.httpAddress;
printStatus('At $addr, found view: ${v.uiIsolate.name}');
}
}
void _validateArguments() {
_fuchsiaRoot = argResults['fuchsia-root'];
if (_fuchsiaRoot == null)
......@@ -138,6 +167,12 @@ class FuchsiaReloadCommand extends FlutterCommand {
if (_address == null)
throwToolExit('Give the address of the device running Fuchsia with --address.');
_list = argResults['list'];
if (_list) {
// For --list, we only need the device address and the Fuchsia tree root.
return;
}
final List<String> gnTarget = _extractPathAndName(argResults['gn-target']);
_projectRoot = gnTarget[0];
_projectName = gnTarget[1];
......@@ -166,6 +201,13 @@ class FuchsiaReloadCommand extends FlutterCommand {
} else {
_binaryName = nameOverride;
}
final String isolateNumber = argResults['isolate-number'];
if (isolateNumber == null) {
_isolateNumber = '';
} else {
_isolateNumber = '-$isolateNumber';
}
}
List<String> _extractPathAndName(String gnTarget) {
......
......@@ -64,8 +64,7 @@ abstract class ResidentRunner {
bool get isRunningRelease => debuggingOptions.buildMode == BuildMode.release;
bool get supportsServiceProtocol => isRunningDebug || isRunningProfile;
VMService vmService;
FlutterView currentView;
List<VMService> vmServices;
StreamSubscription<String> _loggingSubscription;
/// Start the app and keep the process running during its lifetime.
......@@ -78,6 +77,25 @@ abstract class ResidentRunner {
bool get supportsRestart => false;
String isolateFilter;
List<FlutterView> _currentViewsCache;
List<FlutterView> get currentViews {
if (_currentViewsCache == null) {
if ((vmServices == null) || vmServices.isEmpty)
return null;
if (isolateFilter == null)
return vmServices[0].vm.views.toList();
final List<FlutterView> result = <FlutterView>[];
for (VMService service in vmServices)
result.addAll(service.vm.allViewsWithName(isolateFilter));
_currentViewsCache = result;
}
return _currentViewsCache;
}
FlutterView get currentView => (currentViews != null) ? currentViews[0] : null;
Future<OperationResult> restart({ bool fullRestart: false, bool pauseAfterRestart: false }) {
throw 'unsupported';
}
......@@ -94,21 +112,26 @@ abstract class ResidentRunner {
appFinished();
}
Future<Null> refreshViews() async {
if ((vmServices == null) || vmServices.isEmpty)
return;
for (VMService service in vmServices)
await service.vm.refreshViews();
_currentViewsCache = null;
}
Future<Null> _debugDumpApp() async {
if (vmService != null)
await vmService.vm.refreshViews();
await refreshViews();
await currentView.uiIsolate.flutterDebugDumpApp();
}
Future<Null> _debugDumpRenderTree() async {
if (vmService != null)
await vmService.vm.refreshViews();
await refreshViews();
await currentView.uiIsolate.flutterDebugDumpRenderTree();
}
Future<Null> _debugToggleDebugPaintSizeEnabled() async {
if (vmService != null)
await vmService.vm.refreshViews();
await refreshViews();
await currentView.uiIsolate.flutterToggleDebugPaintSizeEnabled();
}
......@@ -117,8 +140,7 @@ abstract class ResidentRunner {
final File outputFile = getUniqueFile(fs.currentDirectory, 'flutter', 'png');
try {
if (supportsServiceProtocol && isRunningDebug) {
if (vmService != null)
await vmService.vm.refreshViews();
await refreshViews();
try {
await currentView.uiIsolate.flutterDebugAllowBanner(false);
} catch (error) {
......@@ -148,8 +170,7 @@ abstract class ResidentRunner {
}
Future<String> _debugRotatePlatform() async {
if (vmService != null)
await vmService.vm.refreshViews();
await refreshViews();
switch (await currentView.uiIsolate.flutterPlatformOverride()) {
case 'iOS':
return await currentView.uiIsolate.flutterPlatformOverride('android');
......@@ -209,25 +230,37 @@ abstract class ResidentRunner {
_loggingSubscription = null;
}
Future<Null> connectToServiceProtocol(Uri uri, {String isolateFilter}) async {
if (!debuggingOptions.debuggingEnabled)
Future<Null> connectToServiceProtocol(
List<Uri> uris, {
String isolateFilter
}) async {
if (!debuggingOptions.debuggingEnabled) {
return new Future<Null>.error('Error the service protocol is not enabled.');
vmService = VMService.connect(uri);
printTrace('Connected to service protocol: $uri');
await vmService.getVM();
}
final List<VMService> services = new List<VMService>(uris.length);
for (int i = 0; i < uris.length; i++) {
services[i] = VMService.connect(uris[i]);
printTrace('Connected to service protocol: ${uris[i]}');
}
vmServices = services;
for (VMService service in services)
await service.getVM();
this.isolateFilter = isolateFilter;
// Refresh the view list, and wait a bit for the list to populate.
await vmService.waitForViews();
currentView = (isolateFilter == null)
? vmService.vm.firstView
: vmService.vm.firstViewWithName(isolateFilter);
for (VMService service in services)
await service.waitForViews();
if (currentView == null)
throwToolExit('No Flutter view is available');
// Listen for service protocol connection to close.
vmService.done.then<Null>(
for (VMService service in services) {
service.done.then<Null>(
_serviceProtocolDone,
onError: _serviceProtocolError).whenComplete(appFinished);
onError: _serviceProtocolError
).whenComplete(appFinished);
}
}
Future<Null> _serviceProtocolDone(dynamic object) {
......@@ -357,7 +390,10 @@ abstract class ResidentRunner {
Future<Null> preStop() async { }
Future<Null> stopApp() async {
if (vmService != null && !vmService.isClosed) {
if (vmServices != null &&
vmServices.isNotEmpty &&
!vmServices[0].isClosed) {
// TODO(zra): iterate over all the views.
if ((currentView != null) && (currentView.uiIsolate != null)) {
// TODO(johnmccutchan): Wait for the exit command to complete.
currentView.uiIsolate.flutterExit();
......
......@@ -101,27 +101,27 @@ class ColdRunner extends ResidentRunner {
// Connect to observatory.
if (debuggingOptions.debuggingEnabled)
await connectToServiceProtocol(_result.observatoryUri);
await connectToServiceProtocol(<Uri>[_result.observatoryUri]);
if (_result.hasObservatory) {
connectionInfoCompleter?.complete(new DebugConnectionInfo(
httpUri: _result.observatoryUri,
wsUri: vmService.wsAddress,
wsUri: vmServices[0].wsAddress,
));
}
printTrace('Application running.');
if (vmService != null) {
device.getLogReader(app: package).appPid = vmService.vm.pid;
await vmService.vm.refreshViews();
printTrace('Connected to ${vmService.vm.firstView}\.');
if (vmServices != null && vmServices.isNotEmpty) {
device.getLogReader(app: package).appPid = vmServices[0].vm.pid;
await refreshViews();
printTrace('Connected to $currentView.');
}
if (vmService != null && traceStartup) {
if (vmServices != null && vmServices.isNotEmpty && traceStartup) {
printStatus('Downloading startup trace info...');
try {
await downloadStartupTrace(vmService);
await downloadStartupTrace(vmServices[0]);
} catch(error) {
printError(error);
return 2;
......@@ -174,7 +174,7 @@ class ColdRunner extends ResidentRunner {
@override
Future<Null> preStop() async {
// If we're running in release mode, stop the app using the device logic.
if (vmService == null)
if (vmServices == null || vmServices.isEmpty)
await device.stopApp(package);
}
}
......@@ -87,21 +87,21 @@ class HotRunner extends ResidentRunner {
return true;
}
Future<int> attach(Uri observatoryUri, {
Future<int> attach(List<Uri> observatoryUris, {
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<Null> appStartedCompleter,
String isolateFilter,
}) async {
_observatoryUri = observatoryUri;
_observatoryUri = observatoryUris[0];
try {
await connectToServiceProtocol(
_observatoryUri, isolateFilter: isolateFilter);
observatoryUris, isolateFilter: isolateFilter);
} catch (error) {
printError('Error connecting to the service protocol: $error');
return 2;
}
device.getLogReader(app: package).appPid = vmService.vm.pid;
device.getLogReader(app: package).appPid = vmServices[0].vm.pid;
try {
final Uri baseUri = await _initDevFS();
......@@ -109,7 +109,7 @@ class HotRunner extends ResidentRunner {
connectionInfoCompleter.complete(
new DebugConnectionInfo(
httpUri: _observatoryUri,
wsUri: vmService.wsAddress,
wsUri: vmServices[0].wsAddress,
baseUri: baseUri.toString()
)
);
......@@ -124,8 +124,9 @@ class HotRunner extends ResidentRunner {
return 3;
}
await vmService.vm.refreshViews();
printTrace('Connected to $currentView.');
await refreshViews();
for (FlutterView view in currentViews)
printTrace('Connected to $view.');
if (stayResident) {
setupTerminal();
......@@ -140,7 +141,7 @@ class HotRunner extends ResidentRunner {
// Measure time to perform a hot restart.
printStatus('Benchmarking hot restart');
await restart(fullRestart: true);
await vmService.vm.refreshViews();
await refreshViews();
// TODO(johnmccutchan): Modify script entry point.
printStatus('Benchmarking hot reload');
// Measure time to perform a hot reload.
......@@ -219,7 +220,7 @@ class HotRunner extends ResidentRunner {
return 2;
}
return attach(result.observatoryUri,
return attach(<Uri>[result.observatoryUri],
connectionInfoCompleter: connectionInfoCompleter,
appStartedCompleter: appStartedCompleter);
}
......@@ -241,7 +242,7 @@ class HotRunner extends ResidentRunner {
Future<Uri> _initDevFS() {
final String fsName = fs.path.basename(projectRootPath);
_devFS = new DevFS(vmService,
_devFS = new DevFS(vmServices[0],
fsName,
fs.directory(projectRootPath),
packagesFilePath: packagesFilePath);
......@@ -425,11 +426,18 @@ class HotRunner extends ResidentRunner {
final Uri devicePackagesUri = _devFS.baseUri.resolve('.packages');
if (benchmarkMode)
vmReloadTimer.start();
final Map<String, dynamic> reloadReport =
await currentView.uiIsolate.reloadSources(
pause: pause,
rootLibUri: deviceEntryUri,
packagesUri: devicePackagesUri);
Map<String, dynamic> reloadReport;
for (FlutterView view in currentViews) {
final Map<String, dynamic> report = await view.uiIsolate.reloadSources(
pause: pause,
rootLibUri: deviceEntryUri,
packagesUri: devicePackagesUri
);
// Just take the first one until we think of something smart to do.
if (reloadReport == null)
reloadReport = report;
}
if (!validateReloadReport(reloadReport)) {
// Reload failed.
flutterUsage.sendEvent('hot', 'reload-reject');
......@@ -464,29 +472,43 @@ class HotRunner extends ResidentRunner {
if (benchmarkMode)
reassembleTimer.start();
// Reload the isolate.
await currentView.uiIsolate.reload();
for (FlutterView view in currentViews)
await view.uiIsolate.reload();
// We are now running from source.
_runningFromSnapshot = false;
// Check if the isolate is paused.
final ServiceEvent pauseEvent = currentView.uiIsolate.pauseEvent;
if ((pauseEvent != null) && (pauseEvent.isPauseEvent)) {
// Isolate is paused. Stop here.
printTrace('Skipping reassemble because isolate is paused.');
final List<FlutterView> reassembleViews = <FlutterView>[];
for (FlutterView view in currentViews) {
final ServiceEvent pauseEvent = view.uiIsolate.pauseEvent;
if ((pauseEvent != null) && (pauseEvent.isPauseEvent)) {
// Isolate is paused. Don't reassemble.
continue;
}
reassembleViews.add(view);
}
if (reassembleViews.isEmpty) {
printTrace('Skipping reassemble because all isolates are paused.');
return new OperationResult(OperationResult.ok.code, reloadMessage);
}
await _evictDirtyAssets();
printTrace('Reassembling application');
try {
await currentView.uiIsolate.flutterReassemble();
} catch (_) {
printError('Reassembling application failed.');
return new OperationResult(1, 'error reassembling application');
}
try {
/* ensure that a frame is scheduled */
await currentView.uiIsolate.uiWindowScheduleFrame();
} catch (_) {
/* ignore any errors */
bool reassembleAndScheduleErrors = false;
for (FlutterView view in reassembleViews) {
try {
await view.uiIsolate.flutterReassemble();
} catch (error) {
reassembleAndScheduleErrors = true;
printError('Reassembling ${view.uiIsolate.name} failed: $error');
continue;
}
try {
/* ensure that a frame is scheduled */
await view.uiIsolate.uiWindowScheduleFrame();
} catch (error) {
reassembleAndScheduleErrors = true;
printError('Scheduling a frame for ${view.uiIsolate.name} failed: $error');
}
}
reloadTimer.stop();
printTrace('Hot reload performed in '
......@@ -503,7 +525,10 @@ class HotRunner extends ResidentRunner {
}
if (shouldReportReloadTime)
flutterUsage.sendTiming('hot', 'reload', reloadTimer.elapsed);
return new OperationResult(OperationResult.ok.code, reloadMessage);
return new OperationResult(
reassembleAndScheduleErrors ? 1 : OperationResult.ok.code,
reloadMessage
);
}
@override
......
......@@ -764,13 +764,12 @@ class VM extends ServiceObjectOwner {
return _viewCache.values.isEmpty ? null : _viewCache.values.first;
}
FlutterView firstViewWithName(String isolateFilter) {
if (_viewCache.values.isEmpty) {
List<FlutterView> allViewsWithName(String isolateFilter) {
if (_viewCache.values.isEmpty)
return null;
}
return _viewCache.values.firstWhere(
(FlutterView v) => v.uiIsolate.name.contains(isolateFilter),
orElse: () => null);
return _viewCache.values.where(
(FlutterView v) => v.uiIsolate.name.contains(isolateFilter)
).toList();
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment