diff options
author | Jonathon Jongsma <jjongsma@redhat.com> | 2015-05-28 13:19:37 -0500 |
---|---|---|
committer | Jonathon Jongsma <jjongsma@redhat.com> | 2015-05-29 14:10:05 -0500 |
commit | e7bb7a20f37bce4ef71a400b30d6775450008043 (patch) | |
tree | 85f974c4105e76cb12da3ea9bea1d466ce6e7742 | |
parent | 30d4cc06fecb0f992d5f85c020124073bdb95b81 (diff) |
Convert RedClient to GObjectreplay-rebase
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-worker-channel-client.h | 2 | ||||
-rw-r--r-- | server/common-worker-channel.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-client.h | 2 | ||||
-rw-r--r-- | server/red-channel.c | 247 | ||||
-rw-r--r-- | server/red-channel.h | 6 | ||||
-rw-r--r-- | server/red-client.c | 363 | ||||
-rw-r--r-- | server/red-client.h | 88 | ||||
-rw-r--r-- | server/reds-private.h | 3 | ||||
-rw-r--r-- | server/reds.c | 58 |
17 files changed, 520 insertions, 280 deletions
diff --git a/server/Makefile.am b/server/Makefile.am index e2ce41cf..4e01dbc1 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -101,6 +101,8 @@ libspice_server_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 d7afd4ca..adab0ea4 100644 --- a/server/char-device.c +++ b/server/char-device.c @@ -22,7 +22,7 @@ #include <config.h> #include "char-device.h" -#include "red-channel.h" +#include "red-client.h" #include "reds.h" #define CHAR_DEVICE_WRITE_TO_TIMEOUT 100 @@ -726,8 +726,11 @@ SpiceCharDeviceClientState *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-worker-channel-client.h b/server/common-worker-channel-client.h index 8fe28952..7bac03c4 100644 --- a/server/common-worker-channel-client.h +++ b/server/common-worker-channel-client.h @@ -50,7 +50,7 @@ struct _CommonWorkerChannelClientClass GType common_worker_channel_client_get_type(void) G_GNUC_CONST; typedef struct RedWorker RedWorker; -typedef struct RedClient RedClient; +typedef struct _RedClient RedClient; typedef struct RedsStream RedsStream; void common_worker_channel_client_set_low_bandwidth(CommonWorkerChannelClient *self, diff --git a/server/common-worker-channel.c b/server/common-worker-channel.c index df763008..8598ccb9 100644 --- a/server/common-worker-channel.c +++ b/server/common-worker-channel.c @@ -8,6 +8,7 @@ #include "common-worker-channel.h" #include "common-worker-channel-client.h" #include "red-worker.h" +#include "red-client.h" #define CHANNEL_RECEIVE_BUF_SIZE 1024 diff --git a/server/dummy-channel-client.c b/server/dummy-channel-client.c index fa04bf84..2007c7bb 100644 --- a/server/dummy-channel-client.c +++ b/server/dummy-channel-client.c @@ -17,6 +17,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 034395c7..b231f69d 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 e204881e..d211547b 100644 --- a/server/main-channel-client.c +++ b/server/main-channel-client.c @@ -20,6 +20,7 @@ #include "main-channel-client.h" #include "main-channel.h" #include "red-channel-client-private.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 c2736cb8..241c59e3 100644 --- a/server/main-channel.c +++ b/server/main-channel.c @@ -44,6 +44,7 @@ #include "main-channel.h" #include "main-channel-client.h" #include "red-channel.h" +#include "red-client.h" #include "common.h" #include "reds.h" #include "migration-protocol.h" diff --git a/server/main-dispatcher.c b/server/main-dispatcher.c index 95a345da..d8ede461 100644 --- a/server/main-dispatcher.c +++ b/server/main-dispatcher.c @@ -23,7 +23,7 @@ #include "common.h" #include "dispatcher.h" #include "main-dispatcher.h" -#include "red-channel.h" +#include "red-client.h" #include "reds.h" /* @@ -209,7 +209,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, @@ -219,7 +219,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, @@ -231,7 +231,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, @@ -244,7 +244,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); } @@ -258,7 +258,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); @@ -268,9 +268,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 8a1eb320..e32dc6dc 100644 --- a/server/red-channel-client.c +++ b/server/red-channel-client.c @@ -35,6 +35,7 @@ #include "common/generated_server_marshallers.h" #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-client.h b/server/red-channel-client.h index 639c50c5..ca9245df 100644 --- a/server/red-channel-client.h +++ b/server/red-channel-client.h @@ -46,7 +46,7 @@ G_BEGIN_DECLS #define RED_IS_CHANNEL_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), RED_TYPE_CHANNEL_CLIENT)) #define RED_CHANNEL_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), RED_TYPE_CHANNEL_CLIENT, RedChannelClientClass)) -typedef struct RedClient RedClient; +typedef struct _RedClient RedClient; typedef struct IncomingHandler IncomingHandler; typedef struct _RedChannel RedChannel; diff --git a/server/red-channel.c b/server/red-channel.c index f621b1a8..b92c2bf3 100644 --- a/server/red-channel.c +++ b/server/red-channel.c @@ -395,30 +395,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, - RedChannelClient *rcc) -{ - 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_waits_for_migrate_data(RedChannel *channel) { RedChannelClient *rcc; @@ -581,14 +557,6 @@ void red_channel_remove_client(RedChannel *channel, RedChannelClient *rcc) // TODO: should we set rcc->channel to NULL??? } -void red_client_remove_channel(RedClient *client, RedChannelClient *rcc) -{ - g_return_if_fail(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); @@ -682,210 +650,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); - client->refs++; - return client; -} - -RedClient *red_client_unref(RedClient *client) -{ - if (!--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_append(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. */ @@ -1138,3 +902,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 3bce3fb2..0e05070b 100644 --- a/server/red-channel.h +++ b/server/red-channel.h @@ -41,7 +41,7 @@ G_BEGIN_DECLS typedef struct RedsStream RedsStream; -typedef struct RedClient RedClient; +typedef struct _RedClient RedClient; typedef uint8_t *(*channel_alloc_msg_recv_buf_proc)(RedChannelClient *channel, uint16_t type, uint32_t size); @@ -352,6 +352,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..7ad989a6 --- /dev/null +++ b/server/red-client.c @@ -0,0 +1,363 @@ +/* red-client.c */ + +#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_append(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(RedClient *client, RedChannelClient *rcc) +{ + g_return_if_fail(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, + RedChannelClient *rcc) +{ + 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; +} + +void red_client_lock(RedClient* self) +{ + pthread_mutex_lock(&self->priv->lock); +} + +void red_client_unlock(RedClient* self) +{ + pthread_mutex_unlock(&self->priv->lock); +} + +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..08288602 --- /dev/null +++ b/server/red-client.h @@ -0,0 +1,88 @@ +/* + 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, + RedChannelClient *rcc); +/* 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(RedClient *client, 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 3c23bec0..1847346c 100644 --- a/server/reds-private.h +++ b/server/reds-private.h @@ -101,8 +101,7 @@ struct RedsState { SpiceWatch *secure_listen_watch; VDIPortState *agent_state; 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 a17931a2..77fb44f4 100644 --- a/server/reds.c +++ b/server/reds.c @@ -71,6 +71,7 @@ #include "smartcard.h" #endif #include "reds-stream.h" +#include "red-client.h" #include "reds-private.h" @@ -527,7 +528,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; } @@ -537,7 +538,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 @@ -559,13 +560,12 @@ void reds_client_disconnect(RedsState *reds, RedClient *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_state->priv->agent_attached) { SpiceCharDeviceWriteBuffer *char_dev_buf; @@ -608,11 +608,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); } @@ -921,7 +924,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 *s) @@ -953,7 +956,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 && !channel_is_secondary(channel)) { + if (g_list_length(reds->clients) > 1 && !channel_is_secondary(channel)) { continue; } g_object_get(channel, "channel-type", &type, "id", &id, NULL); @@ -1184,7 +1187,7 @@ void reds_on_main_channel_migrate(RedsState *reds, MainChannelClient *mcc) VDIPortState *agent_state = reds->agent_state; uint32_t read_data_len; - spice_assert(reds->num_clients == 1); + spice_assert(g_list_length(reds->clients) == 1); if (agent_state->priv->read_state != VDI_PORT_READ_STATE_READ_DATA) { return; @@ -1669,12 +1672,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; } @@ -1685,13 +1687,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 @@ -1738,8 +1740,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, @@ -2912,13 +2913,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); } @@ -3366,8 +3367,7 @@ static int do_spice_init(RedsState *reds, SpiceCoreInterface *core_interface) "self-token-released", (GCallback)vdi_port_on_free_self_token, 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; @@ -3906,7 +3906,7 @@ SPICE_GNUC_VISIBLE int spice_server_migrate_connect(SpiceServer *s, const char* try_seamless)) { reds_mig_started(s); } else { - if (s->num_clients == 0) { + if (g_list_length(s->clients) == 0) { reds_mig_release(s); spice_info("no client connected"); } @@ -3948,7 +3948,7 @@ SPICE_GNUC_VISIBLE int spice_server_migrate_end(SpiceServer *s, int completed) spice_assert(s->migration_interface); sif = SPICE_CONTAINEROF(s->migration_interface->base.sif, SpiceMigrateInterface, base); - if (completed && !s->expect_migrate && s->num_clients) { + if (completed && !s->expect_migrate && g_list_length(s->clients)) { spice_warning("spice_server_migrate_info was not called, disconnecting clients"); reds_disconnect(s); ret = -1; @@ -3973,7 +3973,7 @@ complete: SPICE_GNUC_VISIBLE int spice_server_migrate_switch(SpiceServer *s) { spice_info(NULL); - if (!s->num_clients) { + if (!g_list_length(s->clients)) { return 0; } s->expect_migrate = FALSE; |