retry VMService connection; expect missing PRODUCT_BUNDLE_IDENTIFIER (#16770)
Fixes #13655
diff --git a/packages/flutter_tools/lib/src/base/io.dart b/packages/flutter_tools/lib/src/base/io.dart
index 5500d2a..4c166c6 100644
--- a/packages/flutter_tools/lib/src/base/io.dart
+++ b/packages/flutter_tools/lib/src/base/io.dart
@@ -73,6 +73,7 @@
SocketException,
SYSTEM_ENCODING,
WebSocket,
+ WebSocketException,
WebSocketTransformer;
/// Exits the process with the given [exitCode].
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 619c082..3be658d 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -516,7 +516,7 @@
}
if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice &&
- result.xcodeBuildExecution.buildSettings['PRODUCT_BUNDLE_IDENTIFIER'].contains('com.example')) {
+ result.xcodeBuildExecution.buildSettings['PRODUCT_BUNDLE_IDENTIFIER']?.contains('com.example') == true) {
printError('');
printError('It appears that your application still contains the default signing identifier.');
printError("Try replacing 'com.example' with your signing id in Xcode:");
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index 25420c3..3948cc8 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -16,11 +16,12 @@
import 'base/common.dart';
import 'base/file_system.dart';
+import 'base/io.dart' as io;
import 'globals.dart';
import 'vmservice_record_replay.dart';
/// A function that opens a two-way communication channel to the specified [uri].
-typedef StreamChannel<String> _OpenChannel(Uri uri);
+typedef Future<StreamChannel<String>> _OpenChannel(Uri uri);
_OpenChannel _openChannel = _defaultOpenChannel;
@@ -43,8 +44,32 @@
const String _kRecordingType = 'vmservice';
-StreamChannel<String> _defaultOpenChannel(Uri uri) =>
- new IOWebSocketChannel.connect(uri.toString()).cast();
+Future<StreamChannel<String>> _defaultOpenChannel(Uri uri) async {
+ const int _kMaxAttempts = 5;
+ Duration delay = const Duration(milliseconds: 100);
+ int attempts = 0;
+ io.WebSocket socket;
+ while (attempts < _kMaxAttempts && socket == null) {
+ attempts += 1;
+ try {
+ socket = await io.WebSocket.connect(uri.toString());
+ } on io.WebSocketException catch(e) {
+ printTrace('Exception attempting to connect to observatory: $e');
+ printTrace('This was attempt #$attempts. Will retry in $delay.');
+ await new Future<Null>.delayed(delay);
+ delay *= 2;
+ }
+ }
+
+ if (socket == null) {
+ throw new ToolExit(
+ 'Attempted to connect to Dart observatory $_kMaxAttempts times, and '
+ 'all attempts failed. Giving up. The URL was $uri'
+ );
+ }
+
+ return new IOWebSocketChannel(socket).cast<String>();
+}
/// The default VM service request timeout.
const Duration kDefaultRequestTimeout = const Duration(seconds: 30);
@@ -113,8 +138,8 @@
/// `"vmservice"` subdirectory.
static void enableRecordingConnection(String location) {
final Directory dir = getRecordingSink(location, _kRecordingType);
- _openChannel = (Uri uri) {
- final StreamChannel<String> delegate = _defaultOpenChannel(uri);
+ _openChannel = (Uri uri) async {
+ final StreamChannel<String> delegate = await _defaultOpenChannel(uri);
return new RecordingVMServiceChannel(delegate, dir);
};
}
@@ -126,7 +151,7 @@
/// passed to [enableRecordingConnection]), or a [ToolExit] will be thrown.
static void enableReplayConnection(String location) {
final Directory dir = getReplaySource(location, _kRecordingType);
- _openChannel = (Uri uri) => new ReplayVMServiceChannel(dir);
+ _openChannel = (Uri uri) async => new ReplayVMServiceChannel(dir);
}
/// Connect to a Dart VM Service at [httpUri].
@@ -146,7 +171,7 @@
ReloadSources reloadSources,
}) async {
final Uri wsUri = httpUri.replace(scheme: 'ws', path: fs.path.join(httpUri.path, 'ws'));
- final StreamChannel<String> channel = _openChannel(wsUri);
+ final StreamChannel<String> channel = await _openChannel(wsUri);
final rpc.Peer peer = new rpc.Peer.withoutJson(jsonDocument.bind(channel));
final VMService service = new VMService._(peer, httpUri, wsUri, requestTimeout, reloadSources);
// This call is to ensure we are able to establish a connection instead of
diff --git a/packages/flutter_tools/test/vmservice_test.dart b/packages/flutter_tools/test/vmservice_test.dart
index 6cb0714..9b88230 100644
--- a/packages/flutter_tools/test/vmservice_test.dart
+++ b/packages/flutter_tools/test/vmservice_test.dart
@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:test/test.dart';
+import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/port_scanner.dart';
import 'package:flutter_tools/src/vmservice.dart';
@@ -14,7 +14,7 @@
final int port = await const HostPortScanner().findAvailablePort();
expect(
VMService.connect(Uri.parse('http://localhost:$port')),
- throwsA(const isInstanceOf<WebSocketChannelException>()),
+ throwsA(const isInstanceOf<SocketException>()),
);
});
});