tools: add timeout to VM service client (#7175)

diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index 2c8a940..b44405f 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -14,9 +14,15 @@
 
 import 'globals.dart';
 
+/// The default VM service request timeout.
+const Duration kDefaultRequestTimeout = const Duration(seconds: 10);
+
+/// Used for RPC requests that may take a long time.
+const Duration kLongRequestTimeout = const Duration(minutes: 1);
+
 /// A connection to the Dart VM Service.
 class VMService {
-  VMService._(this.peer, this.httpAddress, this.wsAddress) {
+  VMService._(this.peer, this.httpAddress, this.wsAddress, this._requestTimeout) {
     _vm = new VM._empty(this);
 
     peer.registerMethod('streamNotify', (rpc.Parameters event) {
@@ -24,8 +30,11 @@
     });
   }
 
-  /// Connect to '127.0.0.1' at [port].
-  static Future<VMService> connect(Uri httpUri) async {
+  /// Connect to a Dart VM Service at [httpUri].
+  ///
+  /// Requests made via the returns [VMService] time out after [requestTimeout]
+  /// amount of time, which is [kDefaultRequestTimeout] by default.
+  static Future<VMService> connect(Uri httpUri, {Duration requestTimeout: kDefaultRequestTimeout}) async {
     Uri wsUri = httpUri.replace(scheme: 'ws', path: path.join(httpUri.path, 'ws'));
     WebSocket ws;
     try {
@@ -35,11 +44,13 @@
     }
     rpc.Peer peer = new rpc.Peer.withoutJson(jsonDocument.bind(new IOWebSocketChannel(ws).cast()));
     peer.listen();
-    return new VMService._(peer, httpUri, wsUri);
+    return new VMService._(peer, httpUri, wsUri, requestTimeout);
   }
+
   final Uri httpAddress;
   final Uri wsAddress;
   final rpc.Peer peer;
+  final Duration _requestTimeout;
 
   VM _vm;
   /// The singleton [VM] object. Owns [Isolate] and [FlutterView] objects.
@@ -558,13 +569,20 @@
 
   /// Invoke the RPC and return the raw response.
   Future<Map<String, dynamic>> invokeRpcRaw(
-      String method, [Map<String, dynamic> params]) async {
+      String method, [Map<String, dynamic> params, Duration timeout]) async {
     if (params == null) {
       params = <String, dynamic>{};
     }
-    Map<String, dynamic> result =
-        await _vmService.peer.sendRequest(method, params);
-    return result;
+
+    try {
+      Map<String, dynamic> result = await _vmService.peer
+          .sendRequest(method, params)
+          .timeout(timeout ?? _vmService._requestTimeout);
+      return result;
+    } on TimeoutException catch(_) {
+      printError('Request to Dart VM Service timed out: $method($params)');
+      rethrow;
+    }
   }
 
   /// Invoke the RPC and return a ServiceObject response.
@@ -661,7 +679,7 @@
   }
 
   Future<Map<String, dynamic>> getVMTimeline() {
-    return invokeRpcRaw('_getVMTimeline', <String, dynamic> {});
+    return invokeRpcRaw('_getVMTimeline', <String, dynamic> {}, kLongRequestTimeout);
   }
 
   Future<Null> refreshViews() async {
@@ -724,7 +742,7 @@
 
   /// Invoke the RPC and return the raw response.
   Future<Map<String, dynamic>> invokeRpcRaw(
-      String method, [Map<String, dynamic> params]) {
+      String method, [Map<String, dynamic> params, Duration timeout]) {
     // Inject the 'isolateId' parameter.
     if (params == null) {
       params = <String, dynamic>{
@@ -733,7 +751,7 @@
     } else {
       params['isolateId'] = id;
     }
-    return vm.invokeRpcRaw(method, params);
+    return vm.invokeRpcRaw(method, params, timeout);
   }
 
   /// Invoke the RPC and return a ServiceObject response.
@@ -793,9 +811,9 @@
       String method, [Map<String, dynamic> params]) async {
     try {
       return await invokeRpcRaw(method, params);
-    } catch (e) {
+    } on rpc.RpcException catch (e) {
       // If an application is not using the framework
-      if (_isMethodNotFoundException(e))
+      if (e.code == rpc_error_code.METHOD_NOT_FOUND)
         return null;
       rethrow;
     }
@@ -818,11 +836,6 @@
     return state;
   }
 
-  static bool _isMethodNotFoundException(dynamic e) {
-    return (e is rpc.RpcException) &&
-           (e.code == rpc_error_code.METHOD_NOT_FOUND);
-  }
-
   // Reload related extension methods.
   Future<Map<String, dynamic>> flutterReassemble() async {
     return await invokeFlutterExtensionRpcRaw('ext.flutter.reassemble');