[Spice-devel] [spice-gtk PATCH 4/7] seamless migration: src and dest servers handshake

Yonit Halperin yhalperi at redhat.com
Wed Aug 15 00:56:39 PDT 2012


Flow:
(1) *src* main channel coroutine (main_handle_migrate_begin_seamless):
    handles SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS; yields to the main loop,
    supplying it the destination information needed for connection.
(2) main context (migrate_connect):
    Establishes a new session for connecting to the destination.
    After all the channels are opened (async), their state, except for
    the one of the main channel, is modified to
    SPICE_CHANNEL_STATE_MIGRATING (see migrate_channel_event_cb);
    no reading is done from the channel during this state.
    The dest main channel's state is changed to SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE

(3) *dest* main channel coroutine: sends to the dest server SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS
    (see spice_channel_recv_auth)
(4) *dest* main channel coroutine: recevices SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK/NACK.
     Then it emits the signal "migration-handshake-done".
(5) main context: when all the dest session channels are connected, and the main channel handshake
    is done, we yield to the src main channel coroutine (see
    migrate_channel_event_cb and main_migrate_handshake_done_cb)
(6) *src* main channel coroutine: sends to the src server
    SPICE_MSGC_MAIN_MIGRATE_(CONNECTED|CONNECTED_SEAMLESS|CONNECT_ERROR)

For more details see spice-protocol. commit
1ad5d259cb4b695ec3106de7ccd082e031e7ae11
---
 gtk/channel-main.c       |  163 +++++++++++++++++++++++++++++++++++++++++++--
 gtk/spice-channel-priv.h |    1 +
 gtk/spice-channel.c      |   17 +++++
 gtk/spice-channel.h      |    6 ++-
 4 files changed, 178 insertions(+), 9 deletions(-)

diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index bb0e361..bdcdbcb 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -80,6 +80,7 @@ struct _SpiceMainChannelPrivate  {
 
     guint                       switch_host_delayed_id;
     guint                       migrate_delayed_id;
+    guint32                     migrate_src_version;
 };
 
 typedef struct spice_migrate spice_migrate;
@@ -90,6 +91,12 @@ struct spice_migrate {
     SpiceSession *session;
     guint nchannels;
     SpiceChannel *channel;
+    bool do_seamless; /* used as input and output for the seamless migration handshake.
+                         input: whether to send to the dest SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS
+                         output: whether the dest approved seamless migration
+                         (SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK/NACK)
+                       */
+    uint32_t src_mig_version;
 };
 
 G_DEFINE_TYPE(SpiceMainChannel, spice_main_channel, SPICE_TYPE_CHANNEL)
@@ -121,6 +128,7 @@ enum {
     SPICE_MAIN_CLIPBOARD_SELECTION_REQUEST,
     SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE,
     SPICE_MIGRATION_STARTED,
+    SPICE_MIGRATION_HANDSHAKE_DONE,
     SPICE_MAIN_LAST_SIGNAL,
 };
 
@@ -131,6 +139,9 @@ static void agent_send_msg_queue(SpiceMainChannel *channel);
 static void agent_free_msg_queue(SpiceMainChannel *channel);
 static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
                                      gpointer data);
+static void main_migrate_handshake_done_cb(SpiceChannel *channel, gboolean seamless,
+                                           gpointer data);
+static void spice_main_channel_send_migration_handshake(SpiceChannel *channel);
 
 /* ------------------------------------------------------------------ */
 
@@ -164,6 +175,7 @@ static void spice_main_channel_reset_capabilties(SpiceChannel *channel)
     spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEMI_SEAMLESS_MIGRATE);
     spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_NAME_AND_UUID);
     spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_MAIN_CAP_SEAMLESS_MIGRATE);
 }
 
 static void spice_main_channel_init(SpiceMainChannel *channel)
@@ -327,6 +339,7 @@ static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
     channel_class->iterate_write = spice_channel_iterate_write;
     channel_class->channel_reset = spice_main_channel_reset;
     channel_class->channel_reset_capabilities = spice_main_channel_reset_capabilties;
+    channel_class->channel_send_migration_handshake = spice_main_channel_send_migration_handshake;
 
     /**
      * SpiceMainChannel:mouse-mode:
@@ -659,6 +672,27 @@ static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
                      G_TYPE_OBJECT);
 
 
+    /**
+     * SpiceMainChannel::migration-handshake-done:
+     * @main: the #SpiceMainChannel that emitted the signal
+     * @session: a migration #SpiceSession
+     *
+     * Inform when seamless migration handshake has completed, and
+     * its result: seamless or semi-seamless migration.
+     *
+     * Since: 0.13
+     **/
+    signals[SPICE_MIGRATION_HANDSHAKE_DONE] =
+        g_signal_new("migration-handshake-done",
+                     G_OBJECT_CLASS_TYPE(gobject_class),
+                     G_SIGNAL_RUN_LAST,
+                     0,
+                     NULL, NULL,
+                     g_cclosure_marshal_VOID__BOOLEAN,
+                     G_TYPE_NONE,
+                     1,
+                     G_TYPE_BOOLEAN);
+
     g_type_class_add_private(klass, sizeof(SpiceMainChannelPrivate));
 }
 
@@ -714,6 +748,10 @@ struct SPICE_MAIN_CLIPBOARD_SELECTION_RELEASE {
     guint8 selection;
 };
 
+struct SPICE_MIGRATION_HANDSHAKE_DONE {
+    gboolean seamless;
+};
+
 /* main context */
 static void do_emit_main_context(GObject *object, int signum, gpointer params)
 {
@@ -766,6 +804,11 @@ static void do_emit_main_context(GObject *object, int signum, gpointer params)
                       p->selection);
         break;
     }
+    case SPICE_MIGRATION_HANDSHAKE_DONE: {
+        struct SPICE_MIGRATION_HANDSHAKE_DONE *p = params;
+        g_signal_emit(object, signals[signum], 0,
+                      p->seamless);
+    }
     default:
         g_warn_if_reached();
     }
@@ -1573,6 +1616,25 @@ static SpiceChannel* migrate_channel_connect(spice_migrate *mig, int type, int i
     return newc;
 }
 
+/* coroutine context */
+static void spice_main_channel_send_migration_handshake(SpiceChannel *channel)
+{
+    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+    if (!spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) {
+        emit_main_context(channel, SPICE_MIGRATION_HANDSHAKE_DONE, false);
+    } else {
+        SpiceMsgcMainMigrateDstDoSeamless msg_data;
+        SpiceMsgOut *msg_out;
+
+        msg_data.src_version = c->migrate_src_version;
+
+        msg_out = spice_msg_out_new(channel, SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS);
+        msg_out->marshallers->msgc_main_migrate_dst_do_seamless(msg_out->marshaller, &msg_data);
+        spice_msg_out_send_internal(msg_out);
+    }
+}
+
 /* main context */
 static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
                                      gpointer data)
@@ -1588,9 +1650,20 @@ static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent ev
 
     switch (event) {
     case SPICE_CHANNEL_OPENED:
-        c->state = SPICE_CHANNEL_STATE_MIGRATING;
 
         if (c->channel_type == SPICE_CHANNEL_MAIN) {
+            if (mig->do_seamless) {
+                SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
+
+                c->state = SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE;
+                main_priv->migrate_src_version = mig->src_mig_version;
+                g_signal_connect(channel, "migration-handshake-done",
+                                 G_CALLBACK(main_migrate_handshake_done_cb),
+                                 mig);
+            } else {
+                c->state = SPICE_CHANNEL_STATE_MIGRATING;
+                mig->nchannels--;
+            }
             /* now connect the rest of the channels */
             GList *channels, *l;
             l = channels = spice_session_get_channels(session);
@@ -1602,9 +1675,11 @@ static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent ev
                 migrate_channel_connect(mig, curc->channel_type, curc->channel_id);
             }
             g_list_free(channels);
+        } else {
+            c->state = SPICE_CHANNEL_STATE_MIGRATING;
+            mig->nchannels--;
         }
 
-        mig->nchannels--;
         SPICE_DEBUG("migration: channel opened chan:%p, left %d", channel, mig->nchannels);
         if (mig->nchannels == 0)
             coroutine_yieldto(mig->from, NULL);
@@ -1616,6 +1691,25 @@ static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent ev
     }
 }
 
+/* main context */
+static void main_migrate_handshake_done_cb(SpiceChannel *channel, gboolean seamless,
+                                           gpointer data)
+{
+    spice_migrate *mig = data;
+    SpiceChannelPrivate  *c = SPICE_CHANNEL(channel)->priv;
+
+    g_return_if_fail(c->channel_type == SPICE_CHANNEL_MAIN);
+    g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
+    g_return_if_fail(mig->do_seamless == true);
+
+    g_signal_handlers_disconnect_by_func(channel, main_migrate_handshake_done_cb, data);
+    mig->do_seamless = seamless;
+    c->state = SPICE_CHANNEL_STATE_MIGRATING;
+    mig->nchannels--;
+    if (mig->nchannels == 0)
+        coroutine_yieldto(mig->from, NULL);
+}
+
 #ifdef __GNUC__
 typedef struct __attribute__ ((__packed__)) OldRedMigrationBegin {
 #else
@@ -1703,16 +1797,19 @@ static gboolean migrate_connect(gpointer data)
 }
 
 /* coroutine context */
-static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
+static void main_migrate_connect(SpiceChannel *channel,
+                                 SpiceMigrationDstInfo *dst_info, bool do_seamless,
+                                 uint32_t src_mig_version)
 {
-    SpiceMsgMainMigrationBegin *msg = spice_msg_in_parsed(in);
     spice_migrate mig = { 0, };
     SpiceMsgOut *out;
     int reply_type;
 
     mig.channel = channel;
-    mig.info = &msg->dst_info;
+    mig.info = dst_info;
     mig.from = coroutine_self();
+    mig.do_seamless = do_seamless;
+    mig.src_mig_version = src_mig_version;
 
     /* no need to track idle, call is sync for this coroutine */
     g_idle_add(migrate_connect, &mig);
@@ -1725,8 +1822,13 @@ static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
         reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR;
         spice_session_disconnect(mig.session);
     } else {
-        SPICE_DEBUG("migration: connections all ok");
-        reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED;
+        if (mig.do_seamless) {
+            SPICE_DEBUG("migration (seamless): connections all ok");
+            reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS;
+        } else {
+            SPICE_DEBUG("migration (semi-seamless): connections all ok");
+            reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED;
+        }
         spice_session_set_migration(spice_channel_get_session(channel), mig.session);
     }
     g_object_unref(mig.session);
@@ -1735,6 +1837,38 @@ static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
     spice_msg_out_send(out);
 }
 
+/* coroutine context */
+static void main_handle_migrate_begin(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgMainMigrationBegin *msg = spice_msg_in_parsed(in);
+
+    main_migrate_connect(channel, &msg->dst_info, false, 0);
+}
+
+/* coroutine context */
+static void main_handle_migrate_begin_seamless(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceMsgMainMigrateBeginSeamless *msg = spice_msg_in_parsed(in);
+
+    main_migrate_connect(channel, &msg->dst_info, true, msg->src_mig_version);
+}
+
+static void main_handle_migrate_dst_seamless_ack(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceChannelPrivate  *c = SPICE_CHANNEL(channel)->priv;
+
+    g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
+    emit_main_context(channel, SPICE_MIGRATION_HANDSHAKE_DONE, true);
+}
+
+static void main_handle_migrate_dst_seamless_nack(SpiceChannel *channel, SpiceMsgIn *in)
+{
+    SpiceChannelPrivate  *c = SPICE_CHANNEL(channel)->priv;
+
+    g_return_if_fail(c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
+    emit_main_context(channel, SPICE_MIGRATION_HANDSHAKE_DONE, false);
+}
+
 /* main context */
 static gboolean migrate_delayed(gpointer data)
 {
@@ -1848,7 +1982,10 @@ static const spice_msg_handler main_handlers[] = {
     [ 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,
-    [ SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS ] = main_handle_agent_connected_tokens,
+    [ SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS ]   = main_handle_agent_connected_tokens,
+    [ SPICE_MSG_MAIN_MIGRATE_BEGIN_SEAMLESS ]   = main_handle_migrate_begin_seamless,
+    [ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK]  = main_handle_migrate_dst_seamless_ack,
+    [ SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK] = main_handle_migrate_dst_seamless_nack,
 };
 
 /* coroutine context */
@@ -1856,11 +1993,21 @@ static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
 {
     int type = spice_msg_in_type(msg);
     SpiceChannelClass *parent_class;
+    SpiceChannelPrivate *c = SPICE_CHANNEL(channel)->priv;
 
     g_return_if_fail(type < SPICE_N_ELEMENTS(main_handlers));
 
     parent_class = SPICE_CHANNEL_CLASS(spice_main_channel_parent_class);
 
+    if (c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE) {
+        if (type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_ACK &&
+            type != SPICE_MSG_MAIN_MIGRATE_DST_SEAMLESS_NACK) {
+            g_critical("unexpected msg (%d)."
+                       "Only MIGRATE_DST_SEAMLESS_ACK/NACK are allowed", type);
+            return;
+        }
+    }
+
     if (main_handlers[type] != NULL)
         main_handlers[type](channel, msg);
     else if (parent_class->handle_msg)
diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
index 4f094d8..8ed79fa 100644
--- a/gtk/spice-channel-priv.h
+++ b/gtk/spice-channel-priv.h
@@ -71,6 +71,7 @@ enum spice_channel_state {
     SPICE_CHANNEL_STATE_READY,
     SPICE_CHANNEL_STATE_SWITCHING,
     SPICE_CHANNEL_STATE_MIGRATING,
+    SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE,
 };
 
 struct _SpiceChannelPrivate {
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index 54d406d..5f37cbc 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -49,6 +49,7 @@ static void spice_channel_send_link(SpiceChannel *channel);
 static void channel_disconnect(SpiceChannel *channel);
 static void channel_reset(SpiceChannel *channel, gboolean migrating);
 static void spice_channel_reset_capabilities(SpiceChannel *channel);
+static void spice_channel_send_migration_handshake(SpiceChannel *channel);
 
 /**
  * SECTION:spice-channel
@@ -1080,6 +1081,10 @@ static void spice_channel_recv_auth(SpiceChannel *channel)
 
     emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_OPENED);
 
+    if (c->state == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE) {
+        spice_channel_send_migration_handshake(channel);
+    }
+
     if (c->state != SPICE_CHANNEL_STATE_MIGRATING)
         spice_channel_up(channel);
 }
@@ -2002,6 +2007,7 @@ static void spice_channel_iterate_read(SpiceChannel *channel)
         spice_channel_recv_auth(channel);
         break;
     case SPICE_CHANNEL_STATE_READY:
+    case SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE:
         spice_channel_recv_msg(channel,
             (handler_msg_in)SPICE_CHANNEL_GET_CLASS(channel)->handle_msg, NULL);
         break;
@@ -2654,3 +2660,14 @@ static void spice_channel_reset_capabilities(SpiceChannel *channel)
         SPICE_CHANNEL_GET_CLASS(channel)->channel_reset_capabilities(channel);
     }
 }
+
+static void spice_channel_send_migration_handshake(SpiceChannel *channel)
+{
+    SpiceChannelPrivate *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+    if (SPICE_CHANNEL_GET_CLASS(channel)->channel_send_migration_handshake) {
+        SPICE_CHANNEL_GET_CLASS(channel)->channel_send_migration_handshake(channel);
+    } else {
+        c->state = SPICE_CHANNEL_STATE_MIGRATING;
+    }
+}
diff --git a/gtk/spice-channel.h b/gtk/spice-channel.h
index 30ff1ba..d40b844 100644
--- a/gtk/spice-channel.h
+++ b/gtk/spice-channel.h
@@ -90,11 +90,15 @@ struct _SpiceChannelClass
     void (*channel_reset)(SpiceChannel *channel, gboolean migrating);
     void (*channel_reset_capabilities)(SpiceChannel *channel);
 
+    /*< private >*/
+    /* virtual methods, coroutine context */
+    void (*channel_send_migration_handshake)(SpiceChannel *channel);
+
     /*
      * If adding fields to this struct, remove corresponding
      * amount of padding to avoid changing overall struct size
      */
-    gchar _spice_reserved[SPICE_RESERVED_PADDING];
+    gchar _spice_reserved[SPICE_RESERVED_PADDING - sizeof(void *)];
 };
 
 GType spice_channel_get_type(void);
-- 
1.7.7.6



More information about the Spice-devel mailing list