Fix flaky peer connection (#36089)
Fixes https://github.com/flutter/flutter/issues/36091.
Previously, a sendRequest will be sent even if the peer is closed during
a driver test. That will cause a time out without any error information.
Such issue is unreproducible on my Mac Book Pro, but 100% reproducible
on our new Mac mini (2018).
The closing issue is tracked in https://github.com/flutter/flutter/issues/36268
Additional to this fix, we should also patch the jason_rpc_2 so the peer
will throw exception if sendRequest is attempted while the connection is
closed.
**Test**:
tiles_scroll_perf_iphonexs__timeline_summary failed without this patch.
It will pass after this patch.
I'm not sure how to add a unit test for this. Please let me know if you have
some ideas.
This patch will generate the following warning log on the new Mac Mini
```
flutter: Observatory listening on http://127.0.0.1:50192/cZPDF4sW7MM=/
Installing and launching... 10.2s
00:00 +0: scrolling performance test (setUpAll)
[info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:1069/cZPDF4sW7MM=/
[trace] FlutterDriver: Isolate found with number: 3684677742843303
[trace] FlutterDriver: Isolate is paused at start.
[trace] FlutterDriver: Attempting to resume isolate
[trace] FlutterDriver: Waiting for service extension
[info ] FlutterDriver: Connected to Flutter application.
00:00 +0: scrolling performance test complex_layout_scroll_perf
[warning] FlutterDriver: Instance of '_WebSocketImpl' is closed with an unexpected code 1005
[warning] FlutterDriver: Peer connection is closed! Trying to restore the connection...
00:10 +1: scrolling performance test tiles_scroll_perf
[warning] FlutterDriver: Instance of '_WebSocketImpl' is closed with an unexpected code 1005
[warning] FlutterDriver: Peer connection is closed! Trying to restore the connection...
00:20 +2: scrolling performance test (tearDownAll)
00:20 +2: All tests passed!
Stopping application instance.
```
diff --git a/packages/flutter_driver/lib/src/driver/driver.dart b/packages/flutter_driver/lib/src/driver/driver.dart
index 285860a..869f0c4 100644
--- a/packages/flutter_driver/lib/src/driver/driver.dart
+++ b/packages/flutter_driver/lib/src/driver/driver.dart
@@ -268,6 +268,8 @@
logCommunicationToFile: logCommunicationToFile,
);
+ driver._dartVmReconnectUrl = dartVmServiceUrl;
+
// Attempts to resume the isolate, but does not crash if it fails because
// the isolate is already resumed. There could be a race with other tools,
// such as a debugger, any of which could have resumed the isolate.
@@ -392,7 +394,27 @@
final VMServiceClient serviceClient;
/// JSON-RPC client useful for sending raw JSON requests.
- final rpc.Peer _peer;
+ rpc.Peer _peer;
+
+ String _dartVmReconnectUrl;
+
+ Future<void> _restorePeerConnectionIfNeeded() async {
+ if (!_peer.isClosed || _dartVmReconnectUrl == null) {
+ return;
+ }
+
+ _log.warning(
+ 'Peer connection is closed! Trying to restore the connection...'
+ );
+
+ final String webSocketUrl = _getWebSocketUrl(_dartVmReconnectUrl);
+ final WebSocket ws = await WebSocket.connect(webSocketUrl);
+ ws.done.then((dynamic _) => _checkCloseCode(ws));
+ _peer = rpc.Peer(
+ IOWebSocketChannel(ws).cast(),
+ onUnhandledError: _unhandledJsonRpcError,
+ )..listen();
+ }
/// The main isolate hosting the Flutter application.
///
@@ -840,6 +862,7 @@
///
/// [getFlagList]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#getflaglist
Future<List<Map<String, dynamic>>> getVmFlags() async {
+ await _restorePeerConnectionIfNeeded();
final Map<String, dynamic> result = await _peer.sendRequest('getFlagList');
return result != null
? result['flags'].cast<Map<String,dynamic>>()
@@ -1084,9 +1107,7 @@
// assert(false);
}
-/// Waits for a real Dart VM service to become available, then connects using
-/// the [VMServiceClient].
-Future<VMServiceClientConnection> _waitAndConnect(String url) async {
+String _getWebSocketUrl(String url) {
Uri uri = Uri.parse(url);
final List<String> pathSegments = <String>[];
// If there's an authentication code (default), we need to add it to our path.
@@ -1096,16 +1117,36 @@
pathSegments.add('ws');
if (uri.scheme == 'http')
uri = uri.replace(scheme: 'ws', pathSegments: pathSegments);
+ return uri.toString();
+}
+
+void _checkCloseCode(WebSocket ws) {
+ if (ws.closeCode != 1000 && ws.closeCode != null) {
+ _log.warning('$ws is closed with an unexpected code ${ws.closeCode}');
+ }
+}
+
+/// Waits for a real Dart VM service to become available, then connects using
+/// the [VMServiceClient].
+Future<VMServiceClientConnection> _waitAndConnect(String url) async {
+ final String webSocketUrl = _getWebSocketUrl(url);
int attempts = 0;
while (true) {
WebSocket ws1;
WebSocket ws2;
try {
- ws1 = await WebSocket.connect(uri.toString());
- ws2 = await WebSocket.connect(uri.toString());
+ ws1 = await WebSocket.connect(webSocketUrl);
+ ws2 = await WebSocket.connect(webSocketUrl);
+
+ ws1.done.then((dynamic _) => _checkCloseCode(ws1));
+ ws2.done.then((dynamic _) => _checkCloseCode(ws2));
+
return VMServiceClientConnection(
VMServiceClient(IOWebSocketChannel(ws1).cast()),
- rpc.Peer(IOWebSocketChannel(ws2).cast(), onUnhandledError: _unhandledJsonRpcError)..listen(),
+ rpc.Peer(
+ IOWebSocketChannel(ws2).cast(),
+ onUnhandledError: _unhandledJsonRpcError,
+ )..listen(),
);
} catch (e) {
await ws1?.close();
diff --git a/packages/flutter_driver/test/flutter_driver_test.dart b/packages/flutter_driver/test/flutter_driver_test.dart
index a08f8b3..0ecf036 100644
--- a/packages/flutter_driver/test/flutter_driver_test.dart
+++ b/packages/flutter_driver/test/flutter_driver_test.dart
@@ -599,4 +599,7 @@
class MockVMResumeEvent extends Mock implements VMResumeEvent { }
-class MockPeer extends Mock implements rpc.Peer { }
+class MockPeer extends Mock implements rpc.Peer {
+ @override
+ bool get isClosed => false;
+}