Avoid forwarding the data after socket is disconnected. (#146665)
In a ProxiedDevicePortForwarder, there might be a race condition where the local socket has been disconnected, but the remote end was still sending new data. In this case, avoid forwarding new data to the socket.
diff --git a/packages/flutter_tools/lib/src/proxied_devices/devices.dart b/packages/flutter_tools/lib/src/proxied_devices/devices.dart
index 4b4a9403..c286ce2 100644
--- a/packages/flutter_tools/lib/src/proxied_devices/devices.dart
+++ b/packages/flutter_tools/lib/src/proxied_devices/devices.dart
@@ -633,11 +633,18 @@
'port': devicePort,
}));
final Stream<List<int>> dataStream = connection.listenToEvent('proxy.data.$id').asyncExpand((DaemonEventData event) => event.binary);
- dataStream.listen(socket.add);
+ final StreamSubscription<List<int>> subscription = dataStream.listen(socket.add);
final Future<DaemonEventData> disconnectFuture = connection.listenToEvent('proxy.disconnected.$id').first;
+
+ bool socketDoneCalled = false;
+
unawaited(disconnectFuture.then<void>((_) async {
try {
- await socket.close();
+ if (socketDoneCalled) {
+ await subscription.cancel();
+ } else {
+ await (subscription.cancel(), socket.close()).wait;
+ }
} on Exception {
// ignore
}
@@ -670,6 +677,8 @@
// Do nothing here. Everything will be handled in the `then` block below.
return false;
}).whenComplete(() {
+ socketDoneCalled = true;
+ unawaited(subscription.cancel());
// Send a proxy disconnect event just in case.
unawaited(connection.sendRequest('proxy.disconnect', <String, Object>{
'id': id,
diff --git a/packages/flutter_tools/test/general.shard/proxied_devices/proxied_devices_test.dart b/packages/flutter_tools/test/general.shard/proxied_devices/proxied_devices_test.dart
index 4fae899..8efd65c 100644
--- a/packages/flutter_tools/test/general.shard/proxied_devices/proxied_devices_test.dart
+++ b/packages/flutter_tools/test/general.shard/proxied_devices/proxied_devices_test.dart
@@ -231,6 +231,28 @@
// Wait the event queue and make sure that it doesn't crash.
await pumpEventQueue();
});
+
+ testWithoutContext('should not forward new data to socket after disconnection', () async {
+ // Data will be forwarded before disconnection
+ serverDaemonConnection.sendEvent('proxy.data.$id', null, <int>[1, 2, 3]);
+ await pumpEventQueue();
+ expect(fakeSocket.addedData, <List<int>>[<int>[1, 2, 3]]);
+
+ // It will try to disconnect the remote port when socket is done.
+ fakeSocket.doneCompleter.complete(true);
+ final DaemonMessage message = await broadcastOutput.first;
+
+ expect(message.data['id'], isNotNull);
+ expect(message.data['method'], 'proxy.disconnect');
+ expect(message.data['params'], <String, Object?>{
+ 'id': 'random_id',
+ });
+ await pumpEventQueue();
+
+ serverDaemonConnection.sendEvent('proxy.data.$id', null, <int>[4, 5, 6]);
+ await pumpEventQueue();
+ expect(fakeSocket.addedData, <List<int>>[<int>[1, 2, 3]]);
+ });
});
testWithoutContext('disposes multiple sockets correctly', () async {