[Spice-devel] [PATCH spice-gtk 6/6] Support semi-seamless migration

Marc-André Lureau marcandre.lureau at gmail.com
Sun Dec 18 09:27:25 PST 2011


Yonit Halperin described the flow, that I followed:

(1) when client_migrate_info is called SPICE_MSG_MAIN_MIGRATE_BEGIN is
sent to the client.

Then, the client should link to the target server (SpiceLinkMess),
i.e., connect all the channels, but it shouldn't poll from the target,
only from the source. You can refer to RedClient::Migrate class.  The
connection id in the link message should be the id of the connection
to the source server.

(2) The client sends to the source server
SPICE_MSGC_MAIN_MIGRATE_(CONNECTED|CONNECT_ERROR)

(3) When migration completes  SPICE_MSG_MAIN_MIGRATE_(END|CANCEL) is
sent to the client.

(3.1) In case of SPICE_MSG_MAIN_MIGRATE_CANCEL, the client closes the
connections to the target.

(3.2) In case of SPICE_MSG_MAIN_MIGRATE_END, the client should reset
all the data that is related to the connection to the source and
complete the transition to the target server (without sending
ATTACH_CHANNELS, and without guest display initialization via agent).

Then, the client sends SPICE_MSG_MAIN_MIGRATE_END to the target.
---
 gtk/channel-main.c       |   56 ++++++++++++++++++++++++++----
 gtk/spice-channel.c      |   30 ++++++++++++----
 gtk/spice-session-priv.h |    4 ++
 gtk/spice-session.c      |   84 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 158 insertions(+), 16 deletions(-)

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index 38a2c5d..42e4494 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -75,6 +75,7 @@ struct _SpiceMainChannelPrivate  {
     GQueue                      *agent_msg_queue;
 
     guint                       switch_host_delayed_id;
+    guint                       migrate_delayed_id;
 };
 
 typedef struct spice_migrate spice_migrate;
@@ -158,6 +159,8 @@ static void spice_main_channel_init(SpiceMainChannel *channel)
 
     c = channel->priv = SPICE_MAIN_CHANNEL_GET_PRIVATE(channel);
     c->agent_msg_queue = g_queue_new();
+
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
 }
 
 static void spice_main_get_property(GObject    *object,
@@ -242,6 +245,11 @@ static void spice_main_channel_dispose(GObject *obj)
         c->switch_host_delayed_id = 0;
     }
 
+    if (c->migrate_delayed_id) {
+        g_source_remove(c->migrate_delayed_id);
+        c->migrate_delayed_id = 0;
+    }
+
     if (G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose)
         G_OBJECT_CLASS(spice_main_channel_parent_class)->dispose(obj);
 }
@@ -1116,18 +1124,20 @@ static void main_handle_init(SpiceChannel *channel, SpiceMsgIn *in)
     session = spice_channel_get_session(channel);
     spice_session_set_connection_id(session, init->session_id);
 
-    out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_ATTACH_CHANNELS);
-    spice_msg_out_send_internal(out);
-
     set_mouse_mode(SPICE_MAIN_CHANNEL(channel), init->supported_mouse_modes,
                    init->current_mouse_mode);
 
+    spice_session_set_mm_time(session, init->multi_media_time);
+
     c->agent_tokens = init->agent_tokens;
-    if (init->agent_connected) {
+    if (init->agent_connected)
         agent_start(SPICE_MAIN_CHANNEL(channel));
-    }
 
-    spice_session_set_mm_time(session, init->multi_media_time);
+    if (spice_session_migrate_after_main_init(session))
+        return;
+
+    out = spice_msg_out_new(SPICE_CHANNEL(channel), SPICE_MSGC_MAIN_ATTACH_CHANNELS);
+    spice_msg_out_send_internal(out);
 }
 
 /* coroutine context */
@@ -1389,13 +1399,15 @@ static void migrate_channel_new_cb(SpiceSession *s, SpiceChannel *channel, gpoin
                      G_CALLBACK(migrate_channel_event_cb), data);
 }
 
-static void migrate_channel_connect(spice_migrate *mig, int type, int id)
+static SpiceChannel* migrate_channel_connect(spice_migrate *mig, int type, int id)
 {
     SPICE_DEBUG("migrate_channel_connect %d:%d", type, id);
 
     SpiceChannel *newc = spice_channel_new(mig->session, type, id);
     spice_channel_connect(newc);
     mig->nchannels++;
+
+    return newc;
 }
 
 /* main context */
@@ -1522,7 +1534,7 @@ static gboolean migrate_connect(gpointer data)
 
     /* the migration process is in 2 steps, first the main channel and
        then the rest of the channels */
-    migrate_channel_connect(mig, SPICE_CHANNEL_MAIN, 0);
+    mig->session->priv->cmain = migrate_channel_connect(mig, SPICE_CHANNEL_MAIN, 0);
 
     return FALSE;
 }
@@ -1561,6 +1573,33 @@ static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
 }
 
 /* main context */
+static gboolean migrate_delayed(gpointer data)
+{
+    SpiceChannel *channel = data;
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    g_warn_if_fail(c->migrate_delayed_id != 0);
+    c->migrate_delayed_id = 0;
+
+    spice_session_migrate_end(channel->priv->session);
+
+    return FALSE;
+}
+
+/* coroutine context */
+static void main_handle_migrate_end(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    SPICE_DEBUG("migrate end");
+
+    g_return_if_fail(c->migrate_delayed_id == 0);
+    g_return_if_fail(spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE));
+
+    c->migrate_delayed_id = g_idle_add(migrate_delayed, channel);
+}
+
+/* main context */
 static gboolean switch_host_delayed(gpointer data)
 {
     SpiceChannel *channel = data;
@@ -1638,6 +1677,7 @@ static const spice_msg_handler main_handlers[] = {
     [ SPICE_MSG_MAIN_AGENT_TOKEN ]         = main_handle_agent_token,
 
     [ SPICE_MSG_MAIN_MIGRATE_BEGIN ]       = main_handle_migrate_begin,
+    [ SPICE_MSG_MAIN_MIGRATE_END ]         = main_handle_migrate_end,
     [ SPICE_MSG_MAIN_MIGRATE_CANCEL ]      = main_handle_migrate_cancel,
     [ SPICE_MSG_MAIN_MIGRATE_SWITCH_HOST ] = main_handle_migrate_switch_host,
 };
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index 6420c53..cbd89e1 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -1008,6 +1008,7 @@ static void spice_channel_recv_link_hdr(SpiceChannel *channel)
         goto error;
     }
 
+    SPICE_DEBUG("Peer version: %d:%d", c->peer_hdr.major_version, c->peer_hdr.minor_version);
     if (c->peer_hdr.major_version != c->link_hdr.major_version) {
         if (c->peer_hdr.major_version == 1) {
             /* enter spice 0.4 mode */
@@ -1810,11 +1811,26 @@ static void spice_channel_iterate_read(SpiceChannel *channel)
         spice_channel_recv_msg(channel,
             (handler_msg_in)SPICE_CHANNEL_GET_CLASS(channel)->handle_msg, NULL);
         break;
+    case SPICE_CHANNEL_STATE_MIGRATING:
+        return;
     default:
         g_critical("unknown state %d", c->state);
     }
 }
 
+static gboolean wait_migration(gpointer data)
+{
+    SpiceChannel *channel = SPICE_CHANNEL(data);
+    SpiceChannelPrivate *c = channel->priv;
+
+    if (c->state != SPICE_CHANNEL_STATE_MIGRATING) {
+        SPICE_DEBUG("unfreeze channel %s", c->name);
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
 /* coroutine context */
 static gboolean spice_channel_iterate(SpiceChannel *channel)
 {
@@ -1822,11 +1838,9 @@ static gboolean spice_channel_iterate(SpiceChannel *channel)
     GIOCondition ret;
 
     do {
-        while (c->state == SPICE_CHANNEL_STATE_MIGRATING) {
-            /* freeze coroutine */
-            coroutine_yield(NULL);
-            g_return_val_if_fail(c->state != SPICE_CHANNEL_STATE_MIGRATING, FALSE);
-        }
+        /* freeze coroutine */
+        if (c->state == SPICE_CHANNEL_STATE_MIGRATING)
+            g_condition_wait(wait_migration, channel);
 
         if (c->has_error) {
             SPICE_DEBUG("channel has error, breaking loop");
@@ -1835,6 +1849,7 @@ static gboolean spice_channel_iterate(SpiceChannel *channel)
 
         SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);
         ret = g_io_wait_interruptible(&c->wait, c->sock, G_IO_IN);
+
 #ifdef WIN32
         /* FIXME: windows gsocket is buggy, it doesn't return correct condition... */
         ret = g_socket_condition_check(c->sock, G_IO_IN);
@@ -2211,6 +2226,7 @@ void spice_channel_disconnect(SpiceChannel *channel, SpiceChannelEvent reason)
 {
     SpiceChannelPrivate *c = SPICE_CHANNEL_GET_PRIVATE(channel);
 
+    SPICE_DEBUG("channel disconnect %d", reason);
     g_return_if_fail(c != NULL);
 
     if (c->state == SPICE_CHANNEL_STATE_UNCONNECTED)
@@ -2223,13 +2239,11 @@ void spice_channel_disconnect(SpiceChannel *channel, SpiceChannelEvent reason)
 
     if (c->state == SPICE_CHANNEL_STATE_MIGRATING) {
         c->state = SPICE_CHANNEL_STATE_READY;
-        coroutine_yieldto(&c->coroutine, NULL);
     } else
         spice_channel_wakeup(channel);
 
-    if (reason != SPICE_CHANNEL_NONE) {
+    if (reason != SPICE_CHANNEL_NONE)
         g_signal_emit(G_OBJECT(channel), signals[SPICE_CHANNEL_EVENT], 0, reason);
-    }
 }
 
 static gboolean test_capability(GArray *caps, guint32 cap)
diff --git a/gtk/spice-session-priv.h b/gtk/spice-session-priv.h
index 0851c39..4d52254 100644
--- a/gtk/spice-session-priv.h
+++ b/gtk/spice-session-priv.h
@@ -78,6 +78,8 @@ struct _SpiceSessionPrivate {
     GList             *migration_left;
     SpiceSessionMigration migration_state;
     gboolean          disconnecting;
+    gboolean          migrate_wait_init;
+    guint             after_main_init;
 
     display_cache     images;
     display_cache     palettes;
@@ -123,6 +125,8 @@ void spice_session_get_caches(SpiceSession *session,
                               SpiceGlzDecoderWindow **glz_window);
 void spice_session_palettes_clear(SpiceSession *session);
 void spice_session_images_clear(SpiceSession *session);
+void spice_session_migrate_end(SpiceSession *session);
+gboolean spice_session_migrate_after_main_init(SpiceSession *session);
 
 G_END_DECLS
 
diff --git a/gtk/spice-session.c b/gtk/spice-session.c
index 2b10635..9432073 100644
--- a/gtk/spice-session.c
+++ b/gtk/spice-session.c
@@ -146,6 +146,11 @@ spice_session_dispose(GObject *gobject)
         s->migration_left = NULL;
     }
 
+    if (s->after_main_init) {
+        g_source_remove(s->after_main_init);
+        s->after_main_init = 0;
+    }
+
     g_clear_object(&s->audio_manager);
     g_clear_object(&s->gtk_session);
     g_clear_object(&s->usb_manager);
@@ -1146,6 +1151,85 @@ void spice_session_channel_migrate(SpiceSession *session, SpiceChannel *channel)
     }
 }
 
+/* main context */
+static gboolean after_main_init(gpointer data)
+{
+    SpiceSession *self = data;
+    SpiceSessionPrivate *s = self->priv;
+    GList *l;
+
+    for (l = s->migration_left; l != NULL; ) {
+        SpiceChannel *channel = l->data;
+        l = l->next;
+
+        spice_session_channel_migrate(self, channel);
+        channel->priv->state = SPICE_CHANNEL_STATE_READY;
+        spice_channel_up(channel);
+    }
+
+    s->after_main_init = 0;
+    return FALSE;
+}
+
+/* coroutine context */
+G_GNUC_INTERNAL
+gboolean spice_session_migrate_after_main_init(SpiceSession *self)
+{
+    SpiceSessionPrivate *s = self->priv;
+
+    if (!s->migrate_wait_init)
+        return FALSE;
+
+    g_return_val_if_fail(g_list_length(s->migration_left) != 0, FALSE);
+    g_return_val_if_fail(s->after_main_init == 0, FALSE);
+
+    s->migrate_wait_init = FALSE;
+    s->after_main_init = g_idle_add(after_main_init, self);
+
+    return TRUE;
+}
+
+/* main context */
+G_GNUC_INTERNAL
+void spice_session_migrate_end(SpiceSession *self)
+{
+    SpiceSessionPrivate *s = self->priv;
+    SpiceMsgOut *out;
+    GList *l;
+
+    g_return_if_fail(s->migration);
+    g_return_if_fail(s->migration->priv->cmain);
+    g_return_if_fail(g_list_length(s->migration_left) != 0);
+
+    /* disconnect and reset all channels */
+    for (l = s->migration_left; l != NULL; ) {
+        SpiceChannel *channel = l->data;
+        l = l->next;
+
+        /* reset for migration, disconnect */
+        spice_channel_reset(channel, TRUE);
+
+        if (SPICE_IS_MAIN_CHANNEL(channel)) {
+            /* migrate main to target, so we can start talking */
+            spice_session_channel_migrate(self, channel);
+        } else {
+            /* freeze other channels */
+            channel->priv->state = SPICE_CHANNEL_STATE_MIGRATING;
+        }
+    }
+
+    spice_session_palettes_clear(self);
+    spice_session_images_clear(self);
+    glz_decoder_window_clear(s->glz_window);
+
+    /* send MIGRATE_END to target */
+    out = spice_msg_out_new(s->cmain, SPICE_MSGC_MAIN_MIGRATE_END);
+    spice_msg_out_send(out);
+
+    /* now wait after main init for the rest of channels migration */
+    s->migrate_wait_init = TRUE;
+}
+
 /**
  * spice_session_get_read_only:
  * @session: a #SpiceSession
-- 
1.7.7.4



More information about the Spice-devel mailing list