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.
This commit is contained in:
Lau Ching Jun 2024-04-12 14:45:32 -07:00 committed by GitHub
parent 7a30d2b4ca
commit 8a9e74e8d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 33 additions and 2 deletions

View file

@ -633,11 +633,18 @@ class ProxiedPortForwarder extends DevicePortForwarder {
'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 @@ class ProxiedPortForwarder extends DevicePortForwarder {
// 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,

View file

@ -231,6 +231,28 @@ void main() {
// 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 {