eis: don't send a disconnected event when the client requests disconnect

As the protocol spec says, EIS should treat this as already disconnected
and not touch the connection.

This fixes a memleak if a client connects and immediately disconnects -
when EIS processes the EIS_EVENT_CLIENT_CONNECT it may set up a bunch of
things like seats (the eis-demo-server does this). Then, later, when
the EIS_EVENTE_CLIENT_DISCONNECT is processed, it calls
eis_client_disconnect() but we were already in the disconnected state
and the seats would not get released.
This commit is contained in:
Peter Hutterer 2023-03-01 15:20:50 +10:00
parent c8483c3a30
commit 9b390f5703
3 changed files with 38 additions and 18 deletions

View file

@ -227,6 +227,15 @@ eis_client_connect(struct eis_client *client)
client->state = EIS_CLIENT_STATE_CONNECTED;
}
static void
client_drop_seats(struct eis_client *client)
{
struct eis_seat *s;
list_for_each_safe(s, &client->seats, link) {
eis_seat_drop(s);
}
}
static void
client_disconnect(struct eis_client *client,
enum eis_connection_disconnect_reason reason,
@ -237,20 +246,29 @@ client_disconnect(struct eis_client *client,
/* Client already disconnected? don't bother sending an
* event */
return;
case EIS_CLIENT_STATE_REQUESTED_DISCONNECT:
/* Drop the seats again (see client_msg_disconnect) because
* the server may have added seats between the client requesting the
* disconnect and EIS actually processing that event
*/
client_drop_seats(client);
/* DISCONNECTED event is already in the EIS queue */
/* Must not send disconnected to the client */
client->connection = eis_connection_unref(client->connection);
client->state = EIS_CLIENT_STATE_DISCONNECTED;
source_remove(client->source);
break;
case EIS_CLIENT_STATE_CONNECTING:
case EIS_CLIENT_STATE_CONNECTED:
{
struct eis_seat *s;
list_for_each_safe(s, &client->seats, link) {
eis_seat_drop(s);
}
}
client_drop_seats(client);
eis_queue_disconnect_event(client);
eis_connection_event_disconnected(client->connection,
client->last_client_serial,
reason, explanation);
client->connection = eis_connection_unref(client->connection);
_fallthrough_;
client->state = EIS_CLIENT_STATE_DISCONNECTED;
source_remove(client->source);
break;
case EIS_CLIENT_STATE_NEW:
client->state = EIS_CLIENT_STATE_DISCONNECTED;
source_remove(client->source);
@ -301,7 +319,15 @@ static struct brei_result *
client_msg_disconnect(struct eis_connection *connection)
{
struct eis_client * client = eis_connection_get_client(connection);
client_disconnect(client, EIS_CONNECTION_DISCONNECT_REASON_DISCONNECTED, NULL);
/* We need to drop the seats because that unrolls the EIS device state
* so that EIS gets the correct DEVICE_REMOVED sequence, etc.
* Then queue the DISCONNECTED event and wait for EIS to call
* eis_client_disconnect()
*/
client_drop_seats(client);
eis_queue_disconnect_event(client);
client->state = EIS_CLIENT_STATE_REQUESTED_DISCONNECT;
return NULL;
}
@ -339,6 +365,7 @@ static const struct eis_connection_interface *interfaces[] = {
[EIS_CLIENT_STATE_NEW] = &intf_state_new,
[EIS_CLIENT_STATE_CONNECTING] = &intf_state_connecting,
[EIS_CLIENT_STATE_CONNECTED] = &intf_state_connected,
[EIS_CLIENT_STATE_REQUESTED_DISCONNECT] = NULL,
[EIS_CLIENT_STATE_DISCONNECTED] = NULL,
};
@ -390,6 +417,7 @@ client_dispatch(struct source *source, void *userdata)
"NEW",
"CONNECTING",
"CONNECTED",
"REQUESTED_DISCONNECT",
"DISCONNECTED",
};
if (result)

View file

@ -35,6 +35,7 @@ enum eis_client_state {
EIS_CLIENT_STATE_NEW, /* socket just handed over */
EIS_CLIENT_STATE_CONNECTING, /* client completed setup but hasn't been accepted yet */
EIS_CLIENT_STATE_CONNECTED, /* caller has done eis_client_connect */
EIS_CLIENT_STATE_REQUESTED_DISCONNECT, /* caller has disconnected */
EIS_CLIENT_STATE_DISCONNECTED,
};

View file

@ -476,16 +476,7 @@ class TestEiProtocol:
)
for call in connection.calllog:
if call.name == "Disconnected":
assert call.args["explanation"] is None
assert (
call.args["reason"] == EiConnection.EiDisconnectReason.DISCONNECTED
)
break
else:
assert (
False
), f"Expected Disconnected event, got none in {connection.calllog}"
assert call.name != "Disconnected", "No disconnect event allowed here"
try:
ei.send(connection.Disconnect())