[Spice-devel] [PATCH 1/4] Re-arrange channel client creation to avoid exposing client lock

Frediano Ziglio fziglio at redhat.com
Thu Oct 27 13:01:29 UTC 2016


> **FIXME: introduces crash when session vdagent connects
> 
> Instead of requiring the channel client to lock the client's lock,
> re-arrange things so that all of the locking can be internal to
> RedClient methods. So instead of having a pre-create validate function,
> we simply move the check to red_client_add_channel() and return an error
> if a channel already exists. This encapsulates the client implementation
> better and also reduces code duplication in RedChannelClient and
> DummyChannelClient.
> ---
> 
> 
> **NOTE**: The "FIXME" comment above was added at some point during the branch
> development while bisecting a crash. I don't remember any additional details
> other than what is stated in the commit log above, and so I don't know
> exactly
> what scenario triggered the crash. I can no longer reproduce this bug, but
> decided to leave the comment in for now in the hopes that it might prompt
> others to do a bit more extensive testing to see if you can reproduce the
> issue.
> 
> 
>  server/dummy-channel-client.c | 33 ++++-----------------------------
>  server/red-channel-client.c   | 32 +++-----------------------------
>  server/red-channel.c          | 26 ++++++++++++++++++++++++--
>  server/red-channel.h          |  2 +-
>  4 files changed, 32 insertions(+), 61 deletions(-)
> 
> diff --git a/server/dummy-channel-client.c b/server/dummy-channel-client.c
> index b7fee6f..c937c87 100644
> --- a/server/dummy-channel-client.c
> +++ b/server/dummy-channel-client.c
> @@ -35,48 +35,23 @@ struct DummyChannelClientPrivate
>      gboolean connected;
>  };
>  
> -static int dummy_channel_client_pre_create_validate(RedChannel *channel,
> RedClient  *client)
> -{
> -    uint32_t type, id;
> -    g_object_get(channel, "channel-type", &type, "id", &id, NULL);
> -    if (red_client_get_channel(client, type, id)) {
> -        spice_printerr("Error client %p: duplicate channel type %d id %d",
> -                       client, type, id);
> -        return FALSE;
> -    }
> -    return TRUE;
> -}
> -
>  static gboolean dummy_channel_client_initable_init(GInitable *initable,
>                                                     GCancellable
>                                                     *cancellable,
>                                                     GError **error)
>  {
>      GError *local_error = NULL;
> -    DummyChannelClient *self = DUMMY_CHANNEL_CLIENT(initable);
> -    RedChannelClient *rcc = RED_CHANNEL_CLIENT(self);
> +    RedChannelClient *rcc = RED_CHANNEL_CLIENT(initable);
>      RedClient *client = red_channel_client_get_client(rcc);
>      RedChannel *channel = red_channel_client_get_channel(rcc);
> -    uint32_t type, id;
> -
> -    g_object_get(channel, "channel-type", &type, "id", &id, NULL);
> -    pthread_mutex_lock(&client->lock);
> -    if (!dummy_channel_client_pre_create_validate(channel,
> -                                                  client)) {
> -        g_set_error(&local_error,
> -                    SPICE_SERVER_ERROR,
> -                    SPICE_SERVER_ERROR_FAILED,
> -                    "Client %p: duplicate channel type %d id %d",
> -                    client, type, id);
> -        goto cleanup;
> -    }
>  
>      rcc->incoming.header.data = rcc->incoming.header_buf;
>  
> +    if (!red_client_add_channel(client, rcc, &local_error))
> +        goto cleanup;
> +
>      red_channel_add_client(channel, rcc);
> -    red_client_add_channel(client, rcc);
>  

I honestly wouldn't change red_client_add_channel/red_channel_add_client order.
In theory calls to RedChannelClient from another thread can came from RedClient
as soon as red_client_add_channel returns.

>  cleanup:
> -    pthread_mutex_unlock(&client->lock);
>      if (local_error) {
>          g_warning("Failed to create channel client: %s",
>          local_error->message);
>          g_propagate_error(error, local_error);
> diff --git a/server/red-channel-client.c b/server/red-channel-client.c
> index c562e8e..195ee3e 100644
> --- a/server/red-channel-client.c
> +++ b/server/red-channel-client.c
> @@ -851,18 +851,6 @@ static const SpiceDataHeaderOpaque mini_header_wrapper =
> {NULL, sizeof(SpiceMini
>                                                            mini_header_get_msg_type,
>                                                            mini_header_get_msg_size};
>  
> -static int red_channel_client_pre_create_validate(RedChannel *channel,
> RedClient  *client)
> -{
> -    uint32_t type, id;
> -    g_object_get(channel, "channel-type", &type, "id", &id, NULL);
> -    if (red_client_get_channel(client, type, id)) {
> -        spice_printerr("Error client %p: duplicate channel type %d id %d",
> -                       client, type, id);
> -        return FALSE;
> -    }
> -    return TRUE;
> -}
> -

Looks like a duplication. Definitively I should work on removing this
Dummy* virus..

>  static gboolean red_channel_client_initable_init(GInitable *initable,
>                                                   GCancellable *cancellable,
>                                                   GError **error)
> @@ -870,20 +858,9 @@ static gboolean
> red_channel_client_initable_init(GInitable *initable,
>      GError *local_error = NULL;
>      SpiceCoreInterfaceInternal *core;
>      RedChannelClient *self = RED_CHANNEL_CLIENT(initable);
> -    pthread_mutex_lock(&self->priv->client->lock);
> -    if (!red_channel_client_pre_create_validate(self->priv->channel,
> self->priv->client)) {
> -        uint32_t id, type;
> -        g_object_get(self->priv->channel,
> -                     "channel-type", &type,
> -                     "id", &id,
> -                     NULL);
> -        g_set_error(&local_error,
> -                    SPICE_SERVER_ERROR,
> -                    SPICE_SERVER_ERROR_FAILED,
> -                    "Client %p: duplicate channel type %d id %d",
> -                    self->priv->client, type, id);
> +    self->priv->id = red_channel_get_n_clients(self->priv->channel);

OT: Nobody even point out this is a bug...

Assume 2 clients connect they will have IDs 0, 1 and 2 respectively.
Now assume 0 disconnect and a new one connect, this last will receive
2 as ID so at the end there will be 3 clients with IDs 1, 2 and 2 (again!).

> +    if (!red_client_add_channel(self->priv->client, self, &local_error))
>          goto cleanup;
> -    }
>  
>      core = red_channel_get_core_interface(self->priv->channel);
>      if (self->priv->monitor_latency
> @@ -891,7 +868,7 @@ static gboolean
> red_channel_client_initable_init(GInitable *initable,
>          self->priv->latency_monitor.timer =
>              core->timer_add(core, red_channel_client_ping_timer, self);
>  
> -        if (!self->priv->client->during_target_migrate) {
> +        if (!red_client_during_migrate_at_target(self->priv->client)) {
>              red_channel_client_start_ping_timer(self,
>                                                  PING_TEST_IDLE_NET_TIMEOUT_MS);
>          }

Looking at original code looks like the initialization sequence is a bit
different. Not much of a problem as RedChannel and their RedChannelClients are
executed in the same thread.
However digging more into the code looks like we should change/revert a bit,
there could be some cases leading to uninitialized fields.

> @@ -914,9 +891,7 @@ static gboolean
> red_channel_client_initable_init(GInitable *initable,
>                              SPICE_WATCH_EVENT_READ,
>                              red_channel_client_event,
>                              self);
> -    self->priv->id = red_channel_get_n_clients(self->priv->channel);
>      red_channel_add_client(self->priv->channel, self);
> -    red_client_add_channel(self->priv->client, self);
>  
>      if (!red_channel_config_socket(self->priv->channel, self)) {
>          g_set_error_literal(&local_error,
> @@ -926,7 +901,6 @@ static gboolean
> red_channel_client_initable_init(GInitable *initable,
>      }
>  
>  cleanup:
> -    pthread_mutex_unlock(&self->priv->client->lock);
>      if (local_error) {
>          g_warning("Failed to create channel client: %s",
>          local_error->message);
>          g_propagate_error(error, local_error);
> diff --git a/server/red-channel.c b/server/red-channel.c
> index 3b14fbf..eb63b07 100644
> --- a/server/red-channel.c
> +++ b/server/red-channel.c
> @@ -796,15 +796,37 @@ RedChannelClient *red_client_get_channel(RedClient
> *client, int type, int id)
>      return ret;
>  }
>  
> -/* client->lock should be locked */
> -void red_client_add_channel(RedClient *client, RedChannelClient *rcc)
> +gboolean red_client_add_channel(RedClient *client, RedChannelClient *rcc,
> GError **error)
>  {
> +    uint32_t type, id;
> +    RedChannel *channel;
> +    gboolean result = TRUE;
> +
>      spice_assert(rcc && client);
> +    channel = red_channel_client_get_channel(rcc);
> +
> +    pthread_mutex_lock(&client->lock);
> +
> +    g_object_get(channel, "channel-type", &type, "id", &id, NULL);
> +    if (red_client_get_channel(client, type, id)) {
> +        g_set_error(error,
> +                    SPICE_SERVER_ERROR,
> +                    SPICE_SERVER_ERROR_FAILED,
> +                    "Client %p: duplicate channel type %d id %d",
> +                    client, type, id);
> +        result = FALSE;
> +        goto cleanup;
> +    }
> +
>      client->channels = g_list_prepend(client->channels, rcc);
>      if (client->during_target_migrate && client->seamless_migrate) {
>          if (red_channel_client_set_migration_seamless(rcc))
>              client->num_migrated_channels++;
>      }
> +
> +cleanup:
> +    pthread_mutex_unlock(&client->lock);
> +    return result;
>  }
>  
>  MainChannelClient *red_client_get_main(RedClient *client) {
> diff --git a/server/red-channel.h b/server/red-channel.h
> index 89aab8d..1b46c01 100644
> --- a/server/red-channel.h
> +++ b/server/red-channel.h
> @@ -365,7 +365,7 @@ RedClient *red_client_ref(RedClient *client);
>  RedClient *red_client_unref(RedClient *client);
>  
>  /* client->lock should be locked */
> -void red_client_add_channel(RedClient *client, RedChannelClient *rcc);
> +gboolean red_client_add_channel(RedClient *client, RedChannelClient *rcc,
> GError **error);
>  void red_client_remove_channel(RedChannelClient *rcc);
>  RedChannelClient *red_client_get_channel(RedClient *client, int type, int
>  id);
>

I'll post some order rollback and try to update this patch too to have
RedClient addition as last. Also I'm investigating of a possible
regression in this code leading to some leaks (not sure this patch or
a previous one... or no one).

Frediano


More information about the Spice-devel mailing list