[Spice-devel] [PATCH spice-server 5/5] red-client: Prevent some nasty threading problems disconnecting channels

Christophe Fergeau cfergeau at redhat.com
Fri Aug 25 15:49:15 UTC 2017


On Thu, Aug 24, 2017 at 03:37:20PM +0100, Frediano Ziglio wrote:
> 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.

Just reading the log, I get the impression there are 3 fixes together in
this commit. Given its title ("nasty threading issues"), this is really
not a nice trick to play to the person reviewing the patch :(

Christophe

> 
> Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
> ---
>  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 at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/spice-devel


More information about the Spice-devel mailing list