The channels list was not protected by a lock in red_client_destroy. This could cause for instance a RedChannelClient object to be created while scanning the list so potentially modifying the list while scanning with all race issues. Another issue was that was not clear how the ownership were managed. When red_client_destroy was called red_channel_client_destroy was called which freed the RedChannelClient object so this should imply ownership however same red_channel_client_destroy call was attempted by RedChannel using its list. Basically the two pointers (the one from the channel and the one from the client) were considered as one ownership. To avoid the confusion now the client list always decrement the counter. Another issue was that disconnecting a single channel from the client caused the server to keep a stale channel client till the client entirely disconnected. Calling red_client_remove_channel from red_channel_client_disconnect fix this last issue. Signed-off-by: Frediano Ziglio <fziglio@xxxxxxxxxx> --- server/red-channel-client.c | 11 ++++++++++- server/red-client.c | 48 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/server/red-channel-client.c b/server/red-channel-client.c index 1162ddca..b236e3bd 100644 --- a/server/red-channel-client.c +++ b/server/red-channel-client.c @@ -696,6 +696,8 @@ static void red_channel_client_ping_timer(void *opaque) { RedChannelClient *rcc = opaque; + g_object_ref(rcc); + spice_assert(rcc->priv->latency_monitor.state == PING_STATE_TIMER); red_channel_client_cancel_ping_timer(rcc); @@ -718,6 +720,7 @@ static void red_channel_client_ping_timer(void *opaque) /* More portable alternative code path (less accurate but avoids bogus ioctls)*/ red_channel_client_push_ping(rcc); #endif /* ifdef HAVE_LINUX_SOCKIOS_H */ + g_object_unref(rcc); } static inline int red_channel_client_waiting_for_ack(RedChannelClient *rcc) @@ -749,6 +752,8 @@ static void red_channel_client_connectivity_timer(void *opaque) RedChannelClientConnectivityMonitor *monitor = &rcc->priv->connectivity_monitor; int is_alive = TRUE; + g_object_ref(rcc); + if (monitor->state == CONNECTIVITY_STATE_BLOCKED) { if (!monitor->received_bytes && !monitor->sent_bytes) { if (!red_channel_client_is_blocked(rcc) && !red_channel_client_waiting_for_ack(rcc)) { @@ -793,6 +798,7 @@ static void red_channel_client_connectivity_timer(void *opaque) rcc, type, id, monitor->timeout); red_channel_client_disconnect(rcc); } + g_object_unref(rcc); } void red_channel_client_start_connectivity_monitoring(RedChannelClient *rcc, uint32_t timeout_ms) @@ -1012,7 +1018,6 @@ void red_channel_client_destroy(RedChannelClient *rcc) rcc->priv->destroying = TRUE; red_channel_client_disconnect(rcc); red_client_remove_channel(rcc); - g_object_unref(rcc); } void red_channel_client_shutdown(RedChannelClient *rcc) @@ -1709,6 +1714,10 @@ void red_channel_client_disconnect(RedChannelClient *rcc) } red_channel_remove_client(channel, rcc); red_channel_on_disconnect(channel, rcc); + // remove client from RedClient + // NOTE this may trigger the free of the object, if we are in a watch/timer + // we should make sure we keep a reference + red_client_remove_channel(rcc); } gboolean red_channel_client_is_blocked(RedChannelClient *rcc) diff --git a/server/red-client.c b/server/red-client.c index 3ce09e33..7394bd1f 100644 --- a/server/red-client.c +++ b/server/red-client.c @@ -192,9 +192,6 @@ void red_client_migrate(RedClient *client) void red_client_destroy(RedClient *client) { - RedChannelClient *rcc; - - spice_printerr("destroy client %p with #channels=%d", client, g_list_length(client->channels)); if (!pthread_equal(pthread_self(), client->thread_id)) { spice_warning("client->thread_id (0x%lx) != pthread_self (0x%lx)." "If one of the threads is != io-thread && != vcpu-thread," @@ -202,22 +199,46 @@ void red_client_destroy(RedClient *client) client->thread_id, pthread_self()); } - FOREACH_CHANNEL_CLIENT(client, rcc) { + + pthread_mutex_lock(&client->lock); + spice_printerr("destroy client %p with #channels=%d", client, g_list_length(client->channels)); + // this make sure that possible RedChannelClient setting up + // to be added won't be added to the list + client->disconnecting = TRUE; + while (client->channels) { RedChannel *channel; + RedChannelClient *rcc = client->channels->data; + + // Remove the list to the RedChannelClient we are processing + // note that we own the object so is safe to do some operations on it. + // This manual scan of the list is done to have a thread safe + // iteration of the list + client->channels = g_list_delete_link(client->channels, client->channels); + // some channels may be in other threads, so disconnection // is not synchronous. channel = red_channel_client_get_channel(rcc); red_channel_client_set_destroying(rcc); + + // prevent dead lock disconnecting rcc (which can happen + // in the same thread or synchronously on another one) + pthread_mutex_unlock(&client->lock); + // some channels may be in other threads. However we currently // assume disconnect is synchronous (we changed the dispatcher // to wait for disconnection) // TODO: should we go back to async. For this we need to use // ref count for channel clients. red_channel_disconnect_client(channel, rcc); + spice_assert(red_channel_client_pipe_is_empty(rcc)); spice_assert(red_channel_client_no_item_being_sent(rcc)); + red_channel_client_destroy(rcc); + g_object_unref(rcc); + pthread_mutex_lock(&client->lock); } + pthread_mutex_unlock(&client->lock); g_object_unref(client); } @@ -254,6 +275,16 @@ gboolean red_client_add_channel(RedClient *client, RedChannelClient *rcc, GError pthread_mutex_lock(&client->lock); g_object_get(channel, "channel-type", &type, "id", &id, NULL); + if (client->disconnecting) { + g_set_error(error, + SPICE_SERVER_ERROR, + SPICE_SERVER_ERROR_FAILED, + "Client %p got disconnected while connecting channel type %d id %d", + client, type, id); + result = FALSE; + goto cleanup; + } + if (red_client_get_channel(client, type, id)) { g_set_error(error, SPICE_SERVER_ERROR, @@ -316,10 +347,17 @@ int red_client_during_migrate_at_target(RedClient *client) void red_client_remove_channel(RedChannelClient *rcc) { + GList *link; RedClient *client = red_channel_client_get_client(rcc); pthread_mutex_lock(&client->lock); - client->channels = g_list_remove(client->channels, rcc); + link = g_list_find(client->channels, rcc); + if (link) { + client->channels = g_list_remove_link(client->channels, link); + } pthread_mutex_unlock(&client->lock); + if (link) { + g_object_unref(rcc); + } } /* returns TRUE If all channels are finished migrating, FALSE otherwise */ -- 2.13.5 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel