diff options
author | Jonathon Jongsma <jjongsma@redhat.com> | 2015-05-28 13:19:37 -0500 |
---|---|---|
committer | Jonathon Jongsma <jjongsma@redhat.com> | 2016-09-01 17:42:58 -0500 |
commit | 3089a712c724338a35b2da1ab415c1318be2b5ae (patch) | |
tree | d813934fe5c31f1585b48905db02c48ac0d4cddb | |
parent | 2c0b62c6923ad850485c389c53e684d015afa436 (diff) |
5a35888b85122a80a12919a428396faa4d62b4b8 Convert RedClient to GObject
Move to a separate file, and add a few public methods to avoid accessing
internal data.
-rw-r--r-- | server/Makefile.am | 2 | ||||
-rw-r--r-- | server/char-device.c | 7 | ||||
-rw-r--r-- | server/common-graphics-channel.c | 2 | ||||
-rw-r--r-- | server/dcc.c | 1 | ||||
-rw-r--r-- | server/dummy-channel-client.c | 1 | ||||
-rw-r--r-- | server/inputs-channel.c | 1 | ||||
-rw-r--r-- | server/main-channel-client.c | 1 | ||||
-rw-r--r-- | server/main-channel.c | 1 | ||||
-rw-r--r-- | server/main-dispatcher.c | 16 | ||||
-rw-r--r-- | server/red-channel-client.c | 1 | ||||
-rw-r--r-- | server/red-channel.c | 246 | ||||
-rw-r--r-- | server/red-channel.h | 63 | ||||
-rw-r--r-- | server/red-client.c | 355 | ||||
-rw-r--r-- | server/red-client.h | 87 | ||||
-rw-r--r-- | server/reds-private.h | 3 | ||||
-rw-r--r-- | server/reds.c | 59 | ||||
-rw-r--r-- | server/sound.c | 1 | ||||
-rw-r--r-- | server/stream.c | 1 |
18 files changed, 511 insertions, 337 deletions
diff --git a/server/Makefile.am b/server/Makefile.am index 6c61221b..9579e75d 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -102,6 +102,8 @@ libserver_la_SOURCES = \ red-channel-client.c \ red-channel-client.h \ red-channel-client-private.h \ + red-client.c \ + red-client.h \ dummy-channel.c \ dummy-channel.h \ dummy-channel-client.c \ diff --git a/server/char-device.c b/server/char-device.c index 957fb264..92a4a6db 100644 --- a/server/char-device.c +++ b/server/char-device.c @@ -23,7 +23,7 @@ #include <config.h> #include <inttypes.h> #include "char-device.h" -#include "red-channel.h" +#include "red-client.h" #include "reds.h" #define CHAR_DEVICE_WRITE_TO_TIMEOUT 100 @@ -723,8 +723,11 @@ static RedCharDeviceClient *red_char_device_client_new(RedClient *client, dev_client->max_send_queue_size = max_send_queue_size; dev_client->do_flow_control = do_flow_control; if (do_flow_control) { + RedsState *reds; + + g_object_get(client, "spice-server", &reds, NULL); dev_client->wait_for_tokens_timer = - reds_core_timer_add(client->reds, device_client_wait_for_tokens_timeout, + reds_core_timer_add(reds, device_client_wait_for_tokens_timeout, dev_client); if (!dev_client->wait_for_tokens_timer) { spice_error("failed to create wait for tokens timer"); diff --git a/server/common-graphics-channel.c b/server/common-graphics-channel.c index d8ee9ddb..799c08f7 100644 --- a/server/common-graphics-channel.c +++ b/server/common-graphics-channel.c @@ -25,7 +25,7 @@ #include "common-graphics-channel.h" #include "dcc.h" -#include "main-channel-client.h" +#include "red-client.h" #define CHANNEL_RECEIVE_BUF_SIZE 1024 diff --git a/server/dcc.c b/server/dcc.c index aaa597cc..bfc9fc32 100644 --- a/server/dcc.c +++ b/server/dcc.c @@ -24,6 +24,7 @@ #include "display-channel-private.h" #include "red-channel-client-private.h" #include "spice-server-enums.h" +#include "red-client.h" G_DEFINE_TYPE(DisplayChannelClient, display_channel_client, RED_TYPE_CHANNEL_CLIENT) diff --git a/server/dummy-channel-client.c b/server/dummy-channel-client.c index f4158be0..99710cc9 100644 --- a/server/dummy-channel-client.c +++ b/server/dummy-channel-client.c @@ -20,6 +20,7 @@ #include "dummy-channel-client.h" #include "red-channel.h" +#include "red-client.h" static void dummy_channel_client_initable_interface_init(GInitableIface *iface); diff --git a/server/inputs-channel.c b/server/inputs-channel.c index 880f0299..50d40631 100644 --- a/server/inputs-channel.c +++ b/server/inputs-channel.c @@ -40,6 +40,7 @@ #include "reds-stream.h" #include "red-channel.h" #include "red-channel-client.h" +#include "red-client.h" #include "inputs-channel-client.h" #include "main-channel-client.h" #include "inputs-channel.h" diff --git a/server/main-channel-client.c b/server/main-channel-client.c index 580d6c14..b8a309c0 100644 --- a/server/main-channel-client.c +++ b/server/main-channel-client.c @@ -26,6 +26,7 @@ #include "main-channel-client.h" #include "main-channel.h" #include "red-channel-client.h" +#include "red-client.h" #include "reds.h" #define NET_TEST_WARMUP_BYTES 0 diff --git a/server/main-channel.c b/server/main-channel.c index 8a5e1006..41af57cd 100644 --- a/server/main-channel.c +++ b/server/main-channel.c @@ -23,6 +23,7 @@ #include "red-common.h" #include "main-channel.h" +#include "red-client.h" #include "reds.h" #include "red-channel-client.h" diff --git a/server/main-dispatcher.c b/server/main-dispatcher.c index bc0de241..09ac1ccd 100644 --- a/server/main-dispatcher.c +++ b/server/main-dispatcher.c @@ -24,7 +24,7 @@ #include "red-common.h" #include "dispatcher.h" #include "main-dispatcher.h" -#include "red-channel.h" +#include "red-client.h" #include "reds.h" /* @@ -208,7 +208,7 @@ static void main_dispatcher_handle_migrate_complete(void *opaque, MainDispatcherMigrateSeamlessDstCompleteMessage *mig_complete = payload; reds_on_client_seamless_migrate_complete(self->priv->reds, mig_complete->client); - red_client_unref(mig_complete->client); + g_object_unref(mig_complete->client); } static void main_dispatcher_handle_mm_time_latency(void *opaque, @@ -217,7 +217,7 @@ static void main_dispatcher_handle_mm_time_latency(void *opaque, MainDispatcher *self = opaque; MainDispatcherMmTimeLatencyMessage *msg = payload; reds_set_client_mm_time_latency(self->priv->reds, msg->client, msg->latency); - red_client_unref(msg->client); + g_object_unref(msg->client); } static void main_dispatcher_handle_client_disconnect(void *opaque, @@ -228,7 +228,7 @@ static void main_dispatcher_handle_client_disconnect(void *opaque, spice_debug("client=%p", msg->client); reds_client_disconnect(self->priv->reds, msg->client); - red_client_unref(msg->client); + g_object_unref(msg->client); } void main_dispatcher_seamless_migrate_dst_complete(MainDispatcher *self, @@ -241,7 +241,7 @@ void main_dispatcher_seamless_migrate_dst_complete(MainDispatcher *self, return; } - msg.client = red_client_ref(client); + msg.client = g_object_ref(client); dispatcher_send_message(DISPATCHER(self), MAIN_DISPATCHER_MIGRATE_SEAMLESS_DST_COMPLETE, &msg); } @@ -255,7 +255,7 @@ void main_dispatcher_set_mm_time_latency(MainDispatcher *self, RedClient *client return; } - msg.client = red_client_ref(client); + msg.client = g_object_ref(client); msg.latency = latency; dispatcher_send_message(DISPATCHER(self), MAIN_DISPATCHER_SET_MM_TIME_LATENCY, &msg); @@ -265,9 +265,9 @@ void main_dispatcher_client_disconnect(MainDispatcher *self, RedClient *client) { MainDispatcherClientDisconnectMessage msg; - if (!client->disconnecting) { + if (!red_client_is_disconnecting(client)) { spice_debug("client %p", client); - msg.client = red_client_ref(client); + msg.client = g_object_ref(client); dispatcher_send_message(DISPATCHER(self), MAIN_DISPATCHER_CLIENT_DISCONNECT, &msg); } else { diff --git a/server/red-channel-client.c b/server/red-channel-client.c index d52ea4d1..652743a7 100644 --- a/server/red-channel-client.c +++ b/server/red-channel-client.c @@ -35,6 +35,7 @@ #include "red-channel-client-private.h" #include "red-channel.h" +#include "red-client.h" static void red_channel_client_initable_interface_init(GInitableIface *iface); diff --git a/server/red-channel.c b/server/red-channel.c index 2e579035..b918d869 100644 --- a/server/red-channel.c +++ b/server/red-channel.c @@ -387,29 +387,6 @@ int red_channel_test_remote_cap(RedChannel *channel, uint32_t cap) return TRUE; } -/* returns TRUE If all channels are finished migrating, FALSE otherwise */ -gboolean red_client_seamless_migration_done_for_channel(RedClient *client) -{ - gboolean ret = FALSE; - - pthread_mutex_lock(&client->lock); - client->num_migrated_channels--; - /* we assume we always have at least one channel who has migration data transfer, - * otherwise, this flag will never be set back to FALSE*/ - if (!client->num_migrated_channels) { - client->during_target_migrate = FALSE; - client->seamless_migrate = FALSE; - /* migration completion might have been triggered from a different thread - * than the main thread */ - main_dispatcher_seamless_migrate_dst_complete(reds_get_main_dispatcher(client->reds), - client); - ret = TRUE; - } - pthread_mutex_unlock(&client->lock); - - return ret; -} - int red_channel_is_waiting_for_migrate_data(RedChannel *channel) { RedChannelClient *rcc; @@ -573,14 +550,6 @@ void red_channel_remove_client(RedChannel *channel, RedChannelClient *rcc) // TODO: should we set rcc->channel to NULL??? } -void red_client_remove_channel(RedChannelClient *rcc) -{ - RedClient *client = red_channel_client_get_client(rcc); - pthread_mutex_lock(&client->lock); - client->channels = g_list_remove(client->channels, rcc); - pthread_mutex_unlock(&client->lock); -} - void red_channel_disconnect(RedChannel *channel) { g_list_foreach(channel->priv->clients, (GFunc)red_channel_client_disconnect, NULL); @@ -672,210 +641,6 @@ int red_channel_no_item_being_sent(RedChannel *channel) } /* - * RedClient implementation - kept in red-channel.c because they are - * pretty tied together. - */ - -RedClient *red_client_new(RedsState *reds, int migrated) -{ - RedClient *client; - - client = spice_malloc0(sizeof(RedClient)); - client->reds = reds; - pthread_mutex_init(&client->lock, NULL); - client->thread_id = pthread_self(); - client->during_target_migrate = migrated; - client->refs = 1; - - return client; -} - -RedClient *red_client_ref(RedClient *client) -{ - spice_assert(client); - g_atomic_int_inc(&client->refs); - return client; -} - -RedClient *red_client_unref(RedClient *client) -{ - if (g_atomic_int_dec_and_test(&client->refs)) { - spice_debug("release client=%p", client); - pthread_mutex_destroy(&client->lock); - free(client); - return NULL; - } - return client; -} - -void red_client_set_migration_seamless(RedClient *client) // dest -{ - GList *link; - spice_assert(client->during_target_migrate); - pthread_mutex_lock(&client->lock); - client->seamless_migrate = TRUE; - /* update channel clients that got connected before the migration - * type was set. red_client_add_channel will handle newer channel clients */ - for (link = client->channels; link != NULL; link = link->next) { - if (red_channel_client_set_migration_seamless(link->data)) - client->num_migrated_channels++; - } - pthread_mutex_unlock(&client->lock); -} - -void red_client_migrate(RedClient *client) -{ - GList *link, *next; - RedChannelClient *rcc; - RedChannel *channel; - - spice_printerr("migrate client with #channels %d", 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," - " this might be a BUG", - client->thread_id, pthread_self()); - } - link = client->channels; - while (link) { - next = link->next; - rcc = link->data; - channel = red_channel_client_get_channel(rcc); - if (red_channel_client_is_connected(rcc)) { - channel->priv->client_cbs.migrate(rcc); - } - link = next; - } -} - -void red_client_destroy(RedClient *client) -{ - GList *link, *next; - 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," - " this might be a BUG", - client->thread_id, - pthread_self()); - } - link = client->channels; - while (link) { - RedChannel *channel; - next = link->next; - // some channels may be in other threads, so disconnection - // is not synchronous. - rcc = link->data; - channel = red_channel_client_get_channel(rcc); - red_channel_client_set_destroying(rcc); - // 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. - channel->priv->client_cbs.disconnect(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); - link = next; - } - red_client_unref(client); -} - -/* client->lock should be locked */ -RedChannelClient *red_client_get_channel(RedClient *client, int type, int id) -{ - GList *link; - RedChannelClient *rcc; - RedChannelClient *ret = NULL; - - for (link = client->channels; link != NULL; link = link->next) { - RedChannel *channel; - rcc = link->data; - channel = red_channel_client_get_channel(rcc); - if (channel->priv->type == type && channel->priv->id == id) { - ret = rcc; - break; - } - } - return ret; -} - -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) { - return client->mcc; -} - -void red_client_set_main(RedClient *client, MainChannelClient *mcc) { - client->mcc = mcc; -} - -void red_client_semi_seamless_migrate_complete(RedClient *client) -{ - GList *link, *next; - - pthread_mutex_lock(&client->lock); - if (!client->during_target_migrate || client->seamless_migrate) { - spice_error("unexpected"); - pthread_mutex_unlock(&client->lock); - return; - } - client->during_target_migrate = FALSE; - link = client->channels; - while (link) { - next = link->next; - red_channel_client_semi_seamless_migration_complete(link->data); - link = next; - } - pthread_mutex_unlock(&client->lock); - reds_on_client_semi_seamless_migrate_complete(client->reds, client); -} - -/* should be called only from the main thread */ -int red_client_during_migrate_at_target(RedClient *client) -{ - int ret; - pthread_mutex_lock(&client->lock); - ret = client->during_target_migrate; - pthread_mutex_unlock(&client->lock); - return ret; -} - -/* * Functions to push the same item to multiple pipes. */ @@ -1116,3 +881,14 @@ RedChannelCapabilities* red_channel_get_local_capabilities(RedChannel *self) { return &self->priv->local_caps; } + +void red_channel_migrate_client(RedChannel *channel, RedChannelClient *rcc) +{ + channel->priv->client_cbs.migrate(rcc); +} + +void red_channel_disconnect_client(RedChannel *channel, RedChannelClient *rcc) +{ + channel->priv->client_cbs.disconnect(rcc); +} + diff --git a/server/red-channel.h b/server/red-channel.h index 3bb2df8c..a4676025 100644 --- a/server/red-channel.h +++ b/server/red-channel.h @@ -337,65 +337,6 @@ OutgoingHandlerInterface* red_channel_get_outgoing_handler(RedChannel *self); RedChannelCapabilities* red_channel_get_local_capabilities(RedChannel *self); -struct RedClient { - RedsState *reds; - RingItem link; - GList *channels; - MainChannelClient *mcc; - pthread_mutex_t lock; // different channels can be in different threads - - pthread_t thread_id; - - int disconnecting; - /* Note that while semi-seamless migration is conducted by the main thread, seamless migration - * involves all channels, and thus the related varaibles can be accessed from different - * threads */ - int during_target_migrate; /* if seamless=TRUE, migration_target is turned off when all - the clients received their migration data. Otherwise (semi-seamless), - it is turned off, when red_client_semi_seamless_migrate_complete - is called */ - int seamless_migrate; - int num_migrated_channels; /* for seamless - number of channels that wait for migrate data*/ - int refs; -}; - -RedClient *red_client_new(RedsState *reds, int migrated); - -/* - * disconnects all the client's channels (should be called from the client's thread) - */ -void red_client_destroy(RedClient *client); - -RedClient *red_client_ref(RedClient *client); - -/* - * releases the client resources when refs == 0. - * We assume the red_client_derstroy was called before - * we reached refs==0 - */ -RedClient *red_client_unref(RedClient *client); - -/* client->lock should be locked */ -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); - -MainChannelClient *red_client_get_main(RedClient *client); -// main should be set once before all the other channels are created -void red_client_set_main(RedClient *client, MainChannelClient *mcc); - -/* called when the migration handshake results in seamless migration (dst side). - * By default we assume semi-seamless */ -void red_client_set_migration_seamless(RedClient *client); -void red_client_semi_seamless_migrate_complete(RedClient *client); /* dst side */ -/* TRUE if the migration is seamless and there are still channels that wait from migration data. - * Or, during semi-seamless migration, and the main channel still waits for MIGRATE_END - * from the client. - * Note: Call it only from the main thread */ -int red_client_during_migrate_at_target(RedClient *client); - -void red_client_migrate(RedClient *client); -gboolean red_client_seamless_migration_done_for_channel(RedClient *client); /* * blocking functions. * @@ -407,6 +348,10 @@ gboolean red_client_seamless_migration_done_for_channel(RedClient *client); int red_channel_wait_all_sent(RedChannel *channel, int64_t timeout); +/* wrappers for client callbacks */ +void red_channel_migrate_client(RedChannel *channel, RedChannelClient *rcc); +void red_channel_disconnect_client(RedChannel *channel, RedChannelClient *rcc); + #define CHANNEL_BLOCKED_SLEEP_DURATION 10000 //micro G_END_DECLS diff --git a/server/red-client.c b/server/red-client.c new file mode 100644 index 00000000..9a6d5f52 --- /dev/null +++ b/server/red-client.c @@ -0,0 +1,355 @@ +/* red-client.c */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "red-channel.h" +#include "red-client.h" +#include "reds.h" + +G_DEFINE_TYPE(RedClient, red_client, G_TYPE_OBJECT) + +#define CLIENT_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), RED_TYPE_CLIENT, RedClientPrivate)) + +struct RedClientPrivate +{ + RedsState *reds; + RingItem link; + GList *channels; + int channels_num; + MainChannelClient *mcc; + pthread_mutex_t lock; // different channels can be in different threads + + pthread_t thread_id; + + int disconnecting; + /* Note that while semi-seamless migration is conducted by the main thread, seamless migration + * involves all channels, and thus the related varaibles can be accessed from different + * threads */ + int during_target_migrate; /* if seamless=TRUE, migration_target is turned off when all + the clients received their migration data. Otherwise (semi-seamless), + it is turned off, when red_client_semi_seamless_migrate_complete + is called */ + int seamless_migrate; + int num_migrated_channels; /* for seamless - number of channels that wait for migrate data*/ + int refs; +}; + +enum { + PROP0, + PROP_SPICE_SERVER, + PROP_MIGRATED +}; + +static void +red_client_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + RedClient *self = RED_CLIENT(object); + + switch (property_id) + { + case PROP_SPICE_SERVER: + g_value_set_pointer(value, self->priv->reds); + break; + case PROP_MIGRATED: + g_value_set_boolean(value, self->priv->during_target_migrate); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +red_client_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + RedClient *self = RED_CLIENT(object); + + switch (property_id) + { + case PROP_SPICE_SERVER: + self->priv->reds = g_value_get_pointer(value); + break; + case PROP_MIGRATED: + self->priv->during_target_migrate = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +red_client_dispose (GObject *object) +{ + G_OBJECT_CLASS (red_client_parent_class)->dispose (object); +} + +static void +red_client_finalize (GObject *object) +{ + RedClient *self = RED_CLIENT(object); + + spice_debug("release client=%p", self); + pthread_mutex_destroy(&self->priv->lock); + + G_OBJECT_CLASS (red_client_parent_class)->finalize (object); +} + +static void +red_client_class_init (RedClientClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (RedClientPrivate)); + + object_class->get_property = red_client_get_property; + object_class->set_property = red_client_set_property; + object_class->dispose = red_client_dispose; + object_class->finalize = red_client_finalize; + + g_object_class_install_property(object_class, + PROP_SPICE_SERVER, + g_param_spec_pointer("spice-server", + "Spice server", + "The Spice Server", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property(object_class, + PROP_MIGRATED, + g_param_spec_boolean("migrated", + "migrated", + "Whether this client was migrated", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +red_client_init (RedClient *self) +{ + self->priv = CLIENT_PRIVATE (self); + pthread_mutex_init(&self->priv->lock, NULL); + self->priv->thread_id = pthread_self(); +} + +RedClient * red_client_new(RedsState *reds, int migrated) +{ + return g_object_new (RED_TYPE_CLIENT, + "spice-server", reds, + "migrated", migrated, + NULL); +} + +void red_client_set_migration_seamless(RedClient *client) // dest +{ + GList *link; + spice_assert(client->priv->during_target_migrate); + pthread_mutex_lock(&client->priv->lock); + client->priv->seamless_migrate = TRUE; + /* update channel clients that got connected before the migration + * type was set. red_client_add_channel will handle newer channel clients */ + for (link = client->priv->channels; link != NULL; link = link->next) { + if (red_channel_client_set_migration_seamless(link->data)) + client->priv->num_migrated_channels++; + } + pthread_mutex_unlock(&client->priv->lock); +} + +void red_client_migrate(RedClient *client) +{ + GList *link, *next; + RedChannelClient *rcc; + RedChannel *channel; + + spice_printerr("migrate client with #channels %d", g_list_length(client->priv->channels)); + if (!pthread_equal(pthread_self(), client->priv->thread_id)) { + spice_warning("client->priv->thread_id (0x%lx) != pthread_self (0x%lx)." + "If one of the threads is != io-thread && != vcpu-thread," + " this might be a BUG", + client->priv->thread_id, pthread_self()); + } + link = client->priv->channels; + while (link) { + next = link->next; + rcc = link->data; + channel = red_channel_client_get_channel(rcc); + if (red_channel_client_is_connected(rcc)) { + red_channel_migrate_client(channel, rcc); + } + link = next; + } +} + +void red_client_destroy(RedClient *client) +{ + GList *link, *next; + RedChannelClient *rcc; + + spice_printerr("destroy client %p with #channels=%d", client, g_list_length(client->priv->channels)); + if (!pthread_equal(pthread_self(), client->priv->thread_id)) { + spice_warning("client->priv->thread_id (0x%lx) != pthread_self (0x%lx)." + "If one of the threads is != io-thread && != vcpu-thread," + " this might be a BUG", + client->priv->thread_id, + pthread_self()); + } + link = client->priv->channels; + while (link) { + RedChannel *channel; + next = link->next; + // some channels may be in other threads, so disconnection + // is not synchronous. + rcc = link->data; + channel = red_channel_client_get_channel(rcc); + red_channel_client_set_destroying(rcc); + // 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); + link = next; + } + g_object_unref(client); +} + +/* client->priv->lock should be locked */ +RedChannelClient *red_client_get_channel(RedClient *client, int type, int id) +{ + GList *link; + RedChannelClient *rcc; + RedChannelClient *ret = NULL; + + for (link = client->priv->channels; link != NULL; link = link->next) { + int channel_type, channel_id; + RedChannel *channel; + + rcc = link->data; + channel = red_channel_client_get_channel(rcc); + g_object_get(channel, "channel-type", &channel_type, "id", &channel_id, NULL); + if (channel_type == type && channel_id == id) { + ret = rcc; + break; + } + } + return ret; +} + +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->priv->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->priv->channels = g_list_prepend(client->priv->channels, rcc); + if (client->priv->during_target_migrate && client->priv->seamless_migrate) { + if (red_channel_client_set_migration_seamless(rcc)) + client->priv->num_migrated_channels++; + } + +cleanup: + pthread_mutex_unlock(&client->priv->lock); + return result; +} + +MainChannelClient *red_client_get_main(RedClient *client) { + return client->priv->mcc; +} + +void red_client_set_main(RedClient *client, MainChannelClient *mcc) { + client->priv->mcc = mcc; +} + +void red_client_semi_seamless_migrate_complete(RedClient *client) +{ + GList *link, *next; + + pthread_mutex_lock(&client->priv->lock); + if (!client->priv->during_target_migrate || client->priv->seamless_migrate) { + spice_error("unexpected"); + pthread_mutex_unlock(&client->priv->lock); + return; + } + client->priv->during_target_migrate = FALSE; + link = client->priv->channels; + while (link) { + next = link->next; + red_channel_client_semi_seamless_migration_complete(link->data); + link = next; + } + pthread_mutex_unlock(&client->priv->lock); + reds_on_client_semi_seamless_migrate_complete(client->priv->reds, client); +} + +/* should be called only from the main thread */ +int red_client_during_migrate_at_target(RedClient *client) +{ + int ret; + pthread_mutex_lock(&client->priv->lock); + ret = client->priv->during_target_migrate; + pthread_mutex_unlock(&client->priv->lock); + return ret; +} + +void red_client_remove_channel(RedChannelClient *rcc) +{ + RedClient *client = red_channel_client_get_client(rcc); + pthread_mutex_lock(&client->priv->lock); + client->priv->channels = g_list_remove(client->priv->channels, rcc); + pthread_mutex_unlock(&client->priv->lock); +} + +/* returns TRUE If all channels are finished migrating, FALSE otherwise */ +gboolean red_client_seamless_migration_done_for_channel(RedClient *client) +{ + gboolean ret = FALSE; + + pthread_mutex_lock(&client->priv->lock); + client->priv->num_migrated_channels--; + /* we assume we always have at least one channel who has migration data transfer, + * otherwise, this flag will never be set back to FALSE*/ + if (!client->priv->num_migrated_channels) { + client->priv->during_target_migrate = FALSE; + client->priv->seamless_migrate = FALSE; + /* migration completion might have been triggered from a different thread + * than the main thread */ + main_dispatcher_seamless_migrate_dst_complete(reds_get_main_dispatcher(client->priv->reds), + client); + ret = TRUE; + } + pthread_mutex_unlock(&client->priv->lock); + + return ret; +} + +gboolean red_client_is_disconnecting(RedClient *client) +{ + return client->priv->disconnecting; +} + +void red_client_set_disconnecting(RedClient *client) +{ + client->priv->disconnecting = TRUE; +} diff --git a/server/red-client.h b/server/red-client.h new file mode 100644 index 00000000..95749786 --- /dev/null +++ b/server/red-client.h @@ -0,0 +1,87 @@ +/* + Copyright (C) 2009-2015 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. + + +*/ + +#ifndef _H_RED_CLIENT +#define _H_RED_CLIENT + +#include <glib-object.h> + +#include "main-channel-client.h" + +G_BEGIN_DECLS + +#define RED_TYPE_CLIENT red_client_get_type() + +#define RED_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), RED_TYPE_CLIENT, RedClient)) +#define RED_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), RED_TYPE_CLIENT, RedClientClass)) +#define RED_IS_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), RED_TYPE_CLIENT)) +#define RED_IS_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), RED_TYPE_CLIENT)) +#define RED_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), RED_TYPE_CLIENT, RedClientClass)) + +typedef struct RedClient RedClient; +typedef struct RedClientClass RedClientClass; +typedef struct RedClientPrivate RedClientPrivate; + +struct RedClient +{ + GObject parent; + + RedClientPrivate *priv; +}; + +struct RedClientClass +{ + GObjectClass parent_class; +}; + +GType red_client_get_type (void) G_GNUC_CONST; + +RedClient *red_client_new(RedsState *reds, int migrated); + +/* + * disconnects all the client's channels (should be called from the client's thread) + */ +void red_client_destroy(RedClient *client); + +MainChannelClient *red_client_get_main(RedClient *client); +// main should be set once before all the other channels are created +void red_client_set_main(RedClient *client, MainChannelClient *mcc); + +/* called when the migration handshake results in seamless migration (dst side). + * By default we assume semi-seamless */ +void red_client_set_migration_seamless(RedClient *client); +void red_client_semi_seamless_migrate_complete(RedClient *client); /* dst side */ +gboolean red_client_seamless_migration_done_for_channel(RedClient *client); +/* TRUE if the migration is seamless and there are still channels that wait from migration data. + * Or, during semi-seamless migration, and the main channel still waits for MIGRATE_END + * from the client. + * Note: Call it only from the main thread */ +int red_client_during_migrate_at_target(RedClient *client); + +void red_client_migrate(RedClient *client); +gboolean red_client_add_channel(RedClient *client, RedChannelClient *rcc, GError **error); +RedChannelClient *red_client_get_channel(RedClient *client, int type, int id); +void red_client_remove_channel(RedChannelClient *rcc); + +gboolean red_client_is_disconnecting(RedClient *client); +void red_client_set_disconnecting(RedClient *client); + +G_END_DECLS + +#endif /* _H_RED_CLIENT */ diff --git a/server/reds-private.h b/server/reds-private.h index 51f303d0..85307691 100644 --- a/server/reds-private.h +++ b/server/reds-private.h @@ -104,8 +104,7 @@ struct RedsState { SpiceWatch *secure_listen_watch; RedCharDeviceVDIPort *agent_dev; int pending_mouse_event; - Ring clients; - int num_clients; + GList *clients; MainChannel *main_channel; InputsChannel *inputs_channel; diff --git a/server/reds.c b/server/reds.c index db64a27d..8016db9c 100644 --- a/server/reds.c +++ b/server/reds.c @@ -68,7 +68,7 @@ #include "smartcard.h" #endif #include "reds-stream.h" -#include "utils.h" +#include "red-client.h" #include "reds-private.h" #include "video-encoder.h" @@ -579,7 +579,7 @@ void reds_client_disconnect(RedsState *reds, RedClient *client) exit(0); } - if (!client || client->disconnecting) { + if (!client || red_client_is_disconnecting(client)) { spice_debug("client %p already during disconnection", client); return; } @@ -589,7 +589,7 @@ void reds_client_disconnect(RedsState *reds, RedClient *client) * main_channel_client_on_disconnect-> * reds_client_disconnect->red_client_destroy->main_channel... */ - client->disconnecting = TRUE; + red_client_set_disconnecting(client); // TODO: we need to handle agent properly for all clients!!!! (e.g., cut and paste, how?) // We shouldn't initialize the agent when there are still clients connected @@ -609,13 +609,12 @@ void reds_client_disconnect(RedsState *reds, RedClient *client) red_char_device_client_remove(RED_CHAR_DEVICE(reds->agent_dev), client); } - ring_remove(&client->link); - reds->num_clients--; + reds->clients = g_list_remove(reds->clients, client); red_client_destroy(client); // TODO: we need to handle agent properly for all clients!!!! (e.g., cut and paste, how? Maybe throw away messages // if we are in the middle of one from another client) - if (reds->num_clients == 0) { + if (g_list_length(reds->clients) == 0) { /* Let the agent know the client is disconnected */ if (reds->agent_dev->priv->agent_attached) { RedCharDeviceWriteBuffer *char_dev_buf; @@ -658,11 +657,14 @@ void reds_client_disconnect(RedsState *reds, RedClient *client) // reds_client_disconnect static void reds_disconnect(RedsState *reds) { - RingItem *link, *next; + GList *link, *next; spice_info(NULL); - RING_FOREACH_SAFE(link, next, &reds->clients) { - reds_client_disconnect(reds, SPICE_CONTAINEROF(link, RedClient, link)); + link = reds->clients; + while (link) { + next = link->next; + reds_client_disconnect(reds, link->data); + link = next; } reds_mig_cleanup(reds); } @@ -982,7 +984,7 @@ void reds_handle_agent_mouse_event(RedsState *reds, const VDAgentMouseState *mou static int reds_get_n_clients(RedsState *reds) { - return reds ? reds->num_clients : 0; + return reds ? g_list_length(reds->clients) : 0; } SPICE_GNUC_VISIBLE int spice_server_get_num_clients(SpiceServer *reds) @@ -1012,7 +1014,7 @@ static void reds_fill_channels(RedsState *reds, SpiceMsgChannels *channels_info) for (l = reds->channels; l != NULL; l = l->next) { uint32_t type, id; RedChannel *channel = l->data; - if (reds->num_clients > 1 && + if (g_list_length(reds->clients) > 1 && !channel_supports_multiple_clients(channel)) { continue; } @@ -1254,7 +1256,7 @@ void reds_on_main_channel_migrate(RedsState *reds, MainChannelClient *mcc) RedCharDeviceVDIPort *agent_dev = reds->agent_dev; uint32_t read_data_len; - spice_assert(reds->num_clients == 1); + spice_assert(g_list_length(reds->clients) == 1); if (agent_dev->priv->read_state != VDI_PORT_READ_STATE_READ_DATA) { return; @@ -1734,12 +1736,11 @@ static void reds_mig_target_client_disconnect_all(RedsState *reds) static int reds_find_client(RedsState *reds, RedClient *client) { - RingItem *item; + GList *l; - RING_FOREACH(item, &reds->clients) { - RedClient *list_client; + for (l = reds->clients; l != NULL; l = l->next) { + RedClient *list_client = l->data;; - list_client = SPICE_CONTAINEROF(item, RedClient, link); if (list_client == client) { return TRUE; } @@ -1750,13 +1751,13 @@ static int reds_find_client(RedsState *reds, RedClient *client) /* should be used only when there is one client */ static RedClient *reds_get_client(RedsState *reds) { - spice_assert(reds->num_clients <= 1); + spice_assert(g_list_length(reds->clients) <= 1); - if (reds->num_clients == 0) { + if (g_list_length(reds->clients) == 0) { return NULL; } - return SPICE_CONTAINEROF(ring_get_head(&reds->clients), RedClient, link); + return RED_CLIENT(reds->clients->data); } // TODO: now that main is a separate channel this should @@ -1803,8 +1804,7 @@ static void reds_handle_main_link(RedsState *reds, RedLinkInfo *link) reds_link_free(link); caps = (uint32_t *)((uint8_t *)link_mess + link_mess->caps_offset); client = red_client_new(reds, mig_target); - ring_add(&reds->clients, &client->link); - reds->num_clients++; + reds->clients = g_list_append(reds->clients, client); mcc = main_channel_link(reds->main_channel, client, stream, connection_id, mig_target, link_mess->num_common_caps, @@ -2998,13 +2998,13 @@ static void reds_mig_started(RedsState *reds) static void reds_mig_fill_wait_disconnect(RedsState *reds) { - RingItem *client_item; + GList *l; - spice_assert(reds->num_clients > 0); + spice_assert(g_list_length(reds->clients) > 0); /* tracking the clients, in order to ignore disconnection * of clients that got connected to the src after migration completion.*/ - RING_FOREACH(client_item, &reds->clients) { - RedClient *client = SPICE_CONTAINEROF(client_item, RedClient, link); + for (l = reds->clients; l != NULL; l = l->next) { + RedClient *client = l->data; reds->mig_wait_disconnect_clients = g_list_append(reds->mig_wait_disconnect_clients, client); } @@ -3442,8 +3442,7 @@ static int do_spice_init(RedsState *reds, SpiceCoreInterface *core_interface) reds->listen_socket = -1; reds->secure_listen_socket = -1; reds->agent_dev = red_char_device_vdi_port_new(reds); - ring_init(&reds->clients); - reds->num_clients = 0; + reds->clients = NULL; reds->main_dispatcher = main_dispatcher_new(reds, reds->core); ring_init(&reds->mig_target_clients); reds->char_devices = NULL; @@ -4117,7 +4116,7 @@ SPICE_GNUC_VISIBLE int spice_server_migrate_connect(SpiceServer *reds, const cha try_seamless)) { reds_mig_started(reds); } else { - if (reds->num_clients == 0) { + if (g_list_length(reds->clients) == 0) { reds_mig_release(reds); spice_info("no client connected"); } @@ -4159,7 +4158,7 @@ SPICE_GNUC_VISIBLE int spice_server_migrate_end(SpiceServer *reds, int completed spice_assert(reds->migration_interface); sif = SPICE_CONTAINEROF(reds->migration_interface->base.sif, SpiceMigrateInterface, base); - if (completed && !reds->expect_migrate && reds->num_clients) { + if (completed && !reds->expect_migrate && g_list_length(reds->clients)) { spice_warning("spice_server_migrate_info was not called, disconnecting clients"); reds_disconnect(reds); ret = -1; @@ -4184,7 +4183,7 @@ complete: SPICE_GNUC_VISIBLE int spice_server_migrate_switch(SpiceServer *reds) { spice_info(NULL); - if (!reds->num_clients) { + if (!g_list_length(reds->clients)) { return 0; } reds->expect_migrate = FALSE; diff --git a/server/sound.c b/server/sound.c index c2012f02..65cf5c3e 100644 --- a/server/sound.c +++ b/server/sound.c @@ -38,6 +38,7 @@ #include "red-qxl.h" /* FIXME: for now, allow sound channel access to private RedChannelClient data */ #include "red-channel-client-private.h" +#include "red-client.h" #include "sound.h" #include <common/snd_codec.h> #include "demarshallers.h" diff --git a/server/stream.c b/server/stream.c index 15306ddd..3431e50e 100644 --- a/server/stream.c +++ b/server/stream.c @@ -21,6 +21,7 @@ #include "stream.h" #include "display-channel-private.h" #include "main-channel-client.h" +#include "red-client.h" #define FPS_TEST_INTERVAL 1 #define FOREACH_STREAMS(display, item) \ |