Fix a race condition in vmservice_test.dart (#23835)
Fixes https://github.com/flutter/flutter/issues/19273
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index 54e5c30..633fc9d 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -105,7 +105,7 @@
// TODO(flutter/flutter#23031): Test this.
/// A connection to the Dart VM Service.
class VMService {
- VMService._(
+ VMService(
this._peer,
this.httpAddress,
this.wsAddress,
@@ -236,7 +236,7 @@
final Uri wsUri = httpUri.replace(scheme: 'ws', path: fs.path.join(httpUri.path, 'ws'));
final StreamChannel<String> channel = await _openChannel(wsUri);
final rpc.Peer peer = rpc.Peer.withoutJson(jsonDocument.bind(channel));
- final VMService service = VMService._(peer, httpUri, wsUri, requestTimeout, reloadSources, compileExpression);
+ final VMService service = VMService(peer, httpUri, wsUri, requestTimeout, reloadSources, compileExpression);
// This call is to ensure we are able to establish a connection instead of
// keeping on trucking and failing farther down the process.
await service._sendRequest('getVersion', const <String, dynamic>{});
@@ -311,7 +311,7 @@
final Map<String, dynamic> eventIsolate = eventData['isolate'];
// Log event information.
- printTrace(data.toString());
+ printTrace('Notification from VM: $data');
ServiceEvent event;
if (eventIsolate != null) {
@@ -341,15 +341,9 @@
}
/// Reloads the VM.
- Future<VM> getVM() async {
- return await _vm.reload();
- }
+ Future<void> getVM() async => await vm.reload();
- Future<void> refreshViews() async {
- if (!vm.isFlutterEngine)
- return;
- await vm.refreshViews();
- }
+ Future<void> refreshViews({ bool waitForViews = false }) => vm.refreshViews(waitForViews: waitForViews);
}
/// An error that is thrown when constructing/updating a service object.
@@ -371,9 +365,8 @@
/// to return a cached / canonicalized object.
void _upgradeCollection(dynamic collection,
ServiceObjectOwner owner) {
- if (collection is ServiceMap) {
+ if (collection is ServiceMap)
return;
- }
if (collection is Map<String, dynamic>) {
_upgradeMap(collection, owner);
} else if (collection is List) {
@@ -382,7 +375,7 @@
}
void _upgradeMap(Map<String, dynamic> map, ServiceObjectOwner owner) {
- map.forEach((String k, dynamic v) {
+ map.forEach((String k, Object v) {
if ((v is Map<String, dynamic>) && _isServiceMap(v)) {
map[k] = owner.getFromMap(v);
} else if (v is List) {
@@ -394,8 +387,8 @@
}
void _upgradeList(List<dynamic> list, ServiceObjectOwner owner) {
- for (int i = 0; i < list.length; i++) {
- final dynamic v = list[i];
+ for (int i = 0; i < list.length; i += 1) {
+ final Object v = list[i];
if ((v is Map<String, dynamic>) && _isServiceMap(v)) {
list[i] = owner.getFromMap(v);
} else if (v is List) {
@@ -477,9 +470,8 @@
/// If this is not already loaded, load it. Otherwise reload.
Future<ServiceObject> load() async {
- if (loaded) {
+ if (loaded)
return this;
- }
return reload();
}
@@ -499,15 +491,12 @@
// We should always reload the VM.
// We can't reload objects without an id.
// We shouldn't reload an immutable and already loaded object.
- final bool skipLoad = !isVM && (!hasId || (immutable && loaded));
- if (skipLoad) {
+ if (!isVM && (!hasId || (immutable && loaded)))
return this;
- }
if (_inProgressReload == null) {
final Completer<ServiceObject> completer = Completer<ServiceObject>();
_inProgressReload = completer.future;
-
try {
final Map<String, dynamic> response = await _fetchDirect();
if (_stripRef(response['type']) == 'Sentinel') {
@@ -661,9 +650,7 @@
VM get vm => this;
@override
- Future<Map<String, dynamic>> _fetchDirect() async {
- return invokeRpcRaw('getVM');
- }
+ Future<Map<String, dynamic>> _fetchDirect() => invokeRpcRaw('getVM');
@override
void _update(Map<String, dynamic> map, bool mapIsRef) {
@@ -675,11 +662,9 @@
_upgradeCollection(map, this);
_loaded = true;
- // TODO(johnmccutchan): Extract any properties we care about here.
_pid = map['pid'];
- if (map['_heapAllocatedMemoryUsage'] != null) {
+ if (map['_heapAllocatedMemoryUsage'] != null)
_heapAllocatedMemoryUsage = map['_heapAllocatedMemoryUsage'];
- }
_maxRSS = map['_maxRSS'];
_embedder = map['_embedder'];
@@ -818,6 +803,13 @@
return Future<Isolate>.value(_isolateCache[isolateId]);
}
+ static String _truncate(String message, int width, String ellipsis) {
+ assert(ellipsis.length < width);
+ if (message.length <= width)
+ return message;
+ return message.substring(0, width - ellipsis.length) + ellipsis;
+ }
+
/// Invoke the RPC and return the raw response.
///
/// If `timeoutFatal` is false, then a timeout will result in a null return
@@ -827,14 +819,14 @@
Duration timeout,
bool timeoutFatal = true,
}) async {
- printTrace('$method: $params');
-
+ printTrace('Sending to VM service: $method($params)');
assert(params != null);
timeout ??= _vmService._requestTimeout;
try {
final Map<String, dynamic> result = await _vmService
._sendRequest(method, params)
.timeout(timeout);
+ printTrace('Result: ${_truncate(result.toString(), 250, '...')}');
return result;
} on TimeoutException {
printTrace('Request to Dart VM Service timed out: $method($params)');
@@ -949,12 +941,29 @@
return invokeRpcRaw('_getVMTimeline', timeout: kLongRequestTimeout);
}
- Future<void> refreshViews() {
+ Future<void> refreshViews({ bool waitForViews = false }) async {
+ assert(waitForViews != null);
+ assert(loaded);
if (!isFlutterEngine)
- return Future<void>.value();
- _viewCache.clear();
- // Send one per-application request that refreshes all views in the app.
- return vmService.vm.invokeRpc<ServiceObject>('_flutter.listViews', timeout: kLongRequestTimeout);
+ return;
+ int failCount = 0;
+ while (true) {
+ _viewCache.clear();
+ // When the future returned by invokeRpc() below returns,
+ // the _viewCache will have been updated.
+ // This message updates all the views of every isolate.
+ await vmService.vm.invokeRpc<ServiceObject>(
+ '_flutter.listViews',
+ timeout: kLongRequestTimeout,
+ );
+ if (_viewCache.values.isNotEmpty || !waitForViews)
+ return;
+ failCount += 1;
+ if (failCount == 5) // waited 200ms
+ printStatus('Flutter is taking longer than expected to report its views. Still trying...');
+ await Future<void>.delayed(const Duration(milliseconds: 50));
+ await reload();
+ }
}
Iterable<FlutterView> get views => _viewCache.values;
@@ -1080,9 +1089,7 @@
}
@override
- Future<Map<String, dynamic>> _fetchDirect() {
- return invokeRpcRaw('getIsolate');
- }
+ Future<Map<String, dynamic>> _fetchDirect() => invokeRpcRaw('getIsolate');
/// Invoke the RPC and return the raw response.
Future<Map<String, dynamic>> invokeRpcRaw(String method, {
@@ -1427,7 +1434,7 @@
packagesUri,
assetsDirectoryUri);
await completer.future;
- await owner.vm.refreshViews();
+ await owner.vm.refreshViews(waitForViews: true);
await subscription.cancel();
}