[spice-gtk,v1,6/6] Introduce SpiceMainChannelMigration

Submitted by Victor Toso on Sept. 24, 2019, 9:15 a.m.

Details

Message ID 20190924091502.16038-7-victortoso@redhat.com
State New
Headers show
Series "migration object" ( rev: 2 1 ) in Spice

Not browsing as part of any series.

Commit Message

Victor Toso Sept. 24, 2019, 9:15 a.m.
From: Victor Toso <me@victortoso.com>

The migration is a complicated feature where there the Virtual Machine
is its connection details from its host, now to be called source-host,
to a new host, now called target-host.

For the client, the migration can happen in different ways as
supported by spice-protocol. The focus of this patch is to create an
object that should be handling the state of the migration itself as
much as possible.

With that goal in mind, we will be able to remove some code from
channel-main and spice-session and point out what are their roles
during the execution of each type of migration.

This patch introduces SpiceMainChannelMigration object. The name goes
after channel-main as this is a feature that goes bounded to this
channel. With this patch, we remove struct spice_migrate from
channel-main and make that become the base for the new object.

Note that one design choice is to the channel-main not hold a
reference to SpiceMainChannelMigration because, even if the migration
messages comes from channel-main, the migration scenario happens for
the whole session so, it is SpiceSession who holds a reference to
SpiceMainChannelMigration.

In future patches, non-seamless migration can be moved to
SpiceMainChannelMigration as well, reducing the logic in channel-main
and spice-session.

This has been tested a few times with RHEL8 Guest under RHV 4.3, also
under valgrind.

Signed-off-by: Victor Toso <victortoso@redhat.com>
---
 doc/reference/meson.build    |   1 +
 src/channel-main-migration.c | 398 +++++++++++++++++++++++++++++++++++
 src/channel-main-migration.h |  63 ++++++
 src/channel-main.c           | 217 ++++---------------
 src/meson.build              |   2 +
 src/spice-session-priv.h     |   4 +
 src/spice-session.c          |  26 +++
 7 files changed, 533 insertions(+), 178 deletions(-)
 create mode 100644 src/channel-main-migration.c
 create mode 100644 src/channel-main-migration.h

Patch hide | download patch | download mbox

diff --git a/doc/reference/meson.build b/doc/reference/meson.build
index 61c410f..d9d5203 100644
--- a/doc/reference/meson.build
+++ b/doc/reference/meson.build
@@ -1,6 +1,7 @@ 
 ignore_headers = [
   'bio-gio.h',
   'channel-display-priv.h',
+  'channel-main-migration.h',
   'channel-usbredir-priv.h',
   'client_sw_canvas.h',
   'continuation.h',
diff --git a/src/channel-main-migration.c b/src/channel-main-migration.c
new file mode 100644
index 0000000..9063e0e
--- /dev/null
+++ b/src/channel-main-migration.c
@@ -0,0 +1,398 @@ 
+/*
+   Copyright (C) 2019 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/>.
+*/
+
+#include "config.h"
+
+#include "channel-main-migration.h"
+#include "spice-session-priv.h"
+#include "spice-channel-priv.h"
+
+typedef struct _SpiceMainChannelMigrationPrivate SpiceMainChannelMigrationPrivate;
+
+struct _SpiceMainChannelMigrationPrivate
+{
+    SpiceSession *source_session;
+    SpiceSession *target_session;
+
+    guint source_host_version;
+    gboolean seamless_migration;
+
+    struct coroutine *source_coroutine;
+
+    guint num_channels; /* to migrate */
+    guint num_migrating_channels;
+};
+
+struct _SpiceMainChannelMigration
+{
+    GObject parent;
+
+    SpiceMainChannelMigrationPrivate *priv;
+};
+
+struct _SpiceMainChannelMigrationClass
+{
+    GObjectClass parent_class;
+};
+
+enum {
+    PROP_COROUTINE_CONTEXT = 1,
+    PROP_SEAMLESS_MIGRATION,
+    PROP_SOURCE_HOST_VERSION,
+    PROP_SOURCE_SESSION,
+    PROP_TARGET_SESSION,
+
+    PROP_LAST,
+};
+
+static GParamSpec *g_properties[PROP_LAST] = { NULL, };
+
+G_DEFINE_TYPE_WITH_PRIVATE(SpiceMainChannelMigration, spice_main_channel_migration, G_TYPE_OBJECT)
+
+/*******************************************************************************
+ * Helpers
+ ******************************************************************************/
+
+/* main context */
+static gboolean
+main_channel_migration_seamless_migration_handshake_cb(gpointer data)
+{
+    SpiceMainChannelMigration *self = SPICE_MAIN_CHANNEL_MIGRATION(data);
+    SpiceChannel *channel = spice_session_lookup_channel(self->priv->target_session, SPICE_CHANNEL_MAIN, 0);
+
+    g_return_val_if_fail(spice_channel_get_state(channel) == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE, FALSE);
+
+    spice_channel_set_state(channel, SPICE_CHANNEL_STATE_MIGRATING);
+    self->priv->num_migrating_channels++;
+
+    spice_debug("migration: channel opened chan:%p (main) - %u/%u in migrating state",
+                channel, self->priv->num_migrating_channels, self->priv->num_channels);
+
+    if (self->priv->num_migrating_channels == self->priv->num_channels) {
+        coroutine_yieldto(self->priv->source_coroutine, NULL);
+    }
+    return FALSE;
+}
+
+/* main context */
+static bool
+main_channel_migration_create_and_connect_channel(SpiceMainChannelMigration *self,
+                                                  int channel_type,
+                                                  int channel_id)
+{
+    spice_debug("migration: creating channel type: %d, id: %d", channel_type, channel_id);
+
+    SpiceChannel *channel = spice_channel_new(self->priv->target_session, channel_type, channel_id);
+    bool success = (channel != NULL && spice_channel_connect(channel));
+    if (!success) {
+        spice_debug("migration: failed to create channel");
+        return false;
+    }
+    self->priv->num_channels++;
+    return true;
+}
+
+/* main context */
+static void
+main_channel_migration_on_channel_event(SpiceChannel *channel,
+                                        SpiceChannelEvent event,
+                                        gpointer data)
+{
+    SpiceMainChannelMigration *self = SPICE_MAIN_CHANNEL_MIGRATION(data);
+
+    g_signal_handlers_disconnect_by_func(channel, main_channel_migration_on_channel_event, data);
+    /* Keeping the security check. Should always be positive here, the main-channel at least */
+    g_return_if_fail(self->priv->num_channels > 0);
+
+    switch (event) {
+    case SPICE_CHANNEL_OPENED:
+    {
+        if (spice_channel_get_channel_type(channel) != SPICE_CHANNEL_MAIN) {
+            spice_channel_set_state(channel, SPICE_CHANNEL_STATE_MIGRATING);
+            self->priv->num_migrating_channels++;
+            break;
+        }
+
+        if (self->priv->seamless_migration) {
+            /* Now that channel is open and seamless-migration is possible, we wait
+             * for the seamless-migration-handshake ack/nack from target host */
+            spice_channel_set_state(channel, SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
+        } else {
+            spice_channel_set_state(channel, SPICE_CHANNEL_STATE_MIGRATING);
+            self->priv->num_migrating_channels++;
+        }
+
+        /* now connect the rest of the channels */
+        GList *it, *channels = spice_session_get_channels(self->priv->source_session);
+        for (it = channels; it != NULL; it = it->next) {
+            SpiceChannel *it_channel = SPICE_CHANNEL(it->data);
+            if (spice_channel_get_channel_type(it_channel) != SPICE_CHANNEL_MAIN) {
+                main_channel_migration_create_and_connect_channel(self,
+                                        spice_channel_get_channel_type(it_channel),
+                                        spice_channel_get_channel_id(it_channel));
+            }
+        }
+        g_list_free(channels);
+        break;
+    }
+    default:
+        CHANNEL_DEBUG(channel, "error or unhandled channel event during migration: %u", event);
+        /* go back to main channel to report error */
+        coroutine_yieldto(self->priv->source_coroutine, NULL);
+        return;
+    }
+
+    spice_debug("migration: channel opened chan:%p - %u/%u in migrating state",
+                channel, self->priv->num_migrating_channels, self->priv->num_channels);
+
+    if (self->priv->num_migrating_channels == self->priv->num_channels) {
+        coroutine_yieldto(self->priv->source_coroutine, NULL);
+    }
+}
+
+/* main context */
+static void
+main_channel_migration_on_channel_new(SpiceSession *target_session,
+                                      SpiceChannel *channel,
+                                      gpointer data)
+{
+    g_signal_connect(channel, "channel-event",
+                     G_CALLBACK(main_channel_migration_on_channel_event), data);
+}
+
+/* main context */
+static gboolean
+main_channel_migration_run_cb(gpointer data)
+{
+    SpiceMainChannelMigration *self = SPICE_MAIN_CHANNEL_MIGRATION(data);
+
+    spice_session_set_migration_state(self->priv->target_session,
+                                      SPICE_SESSION_MIGRATION_CONNECTING);
+    g_signal_connect(self->priv->target_session, "channel-new",
+                     G_CALLBACK(main_channel_migration_on_channel_new), self);
+    /* the migration process is in 2 steps, first the main channel and
+       then the rest of the channels */
+    main_channel_migration_create_and_connect_channel(self, SPICE_CHANNEL_MAIN, 0);
+
+    return FALSE;
+}
+
+/*******************************************************************************
+ * Internal API
+ ******************************************************************************/
+
+/* coroutine context */
+G_GNUC_INTERNAL
+bool spice_main_channel_migration_init_migration(SpiceMainChannelMigration *self)
+{
+    /* On SpiceMainChannelMigration creation, we check both source_session and
+     * target_session and initialization would fail if minimum parameters were
+     * lacking. The guard below is enough for minimal check */
+    g_return_val_if_fail(SPICE_IS_MAIN_CHANNEL_MIGRATION(self), FALSE);
+
+    /* no need to track idle, call is sync for this coroutine */
+    g_idle_add(main_channel_migration_run_cb, self);
+
+    /* switch to main loop and wait for connections */
+    coroutine_yield(NULL);
+
+    return self->priv->num_channels == self->priv->num_migrating_channels;
+}
+
+/* coroutine context */
+G_GNUC_INTERNAL void
+spice_main_channel_migration_seamless_handshake_done(SpiceMainChannelMigration *self,
+                                                     gboolean seamless_migration)
+{
+    self->priv->seamless_migration = seamless_migration;
+    /* move to main context */
+    g_idle_add(main_channel_migration_seamless_migration_handshake_cb, self);
+}
+
+G_GNUC_INTERNAL
+guint spice_main_channel_migration_get_source_host_version(SpiceMainChannelMigration *self)
+{
+    return self->priv->source_host_version;
+}
+
+G_GNUC_INTERNAL
+bool spice_main_channel_migration_is_seamless(SpiceMainChannelMigration *self)
+{
+    return self->priv->seamless_migration;
+}
+
+/*******************************************************************************
+ * GObject
+ ******************************************************************************/
+
+static void
+spice_main_channel_migration_init(SpiceMainChannelMigration *self)
+{
+     self->priv = spice_main_channel_migration_get_instance_private(self);
+}
+
+static void
+spice_main_channel_migration_dispose(GObject *gobject)
+{
+    SpiceMainChannelMigration *self = SPICE_MAIN_CHANNEL_MIGRATION(gobject);
+
+    g_clear_object(&self->priv->target_session);
+    g_clear_object(&self->priv->source_session);
+
+    if (G_OBJECT_CLASS(spice_main_channel_migration_parent_class)->dispose) {
+        G_OBJECT_CLASS(spice_main_channel_migration_parent_class)->dispose(gobject);
+    }
+}
+
+static void
+spice_main_channel_migration_get_property(GObject *object,
+                                          guint property_id,
+                                          GValue *value,
+                                          GParamSpec *pspec)
+{
+    SpiceMainChannelMigration *self = SPICE_MAIN_CHANNEL_MIGRATION(object);
+
+    switch (property_id) {
+    case PROP_COROUTINE_CONTEXT:
+        g_value_set_pointer(value, self->priv->source_coroutine);
+        break;
+
+    case PROP_SEAMLESS_MIGRATION:
+        g_value_set_boolean(value, self->priv->seamless_migration);
+        break;
+
+    case PROP_SOURCE_HOST_VERSION:
+        g_value_set_uint(value, self->priv->source_host_version);
+        break;
+
+    case PROP_SOURCE_SESSION:
+        g_value_set_object(value, self->priv->source_session);
+        break;
+
+    case PROP_TARGET_SESSION:
+        g_value_set_object(value, self->priv->target_session);
+        break;
+
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+spice_main_channel_migration_set_property(GObject *object,
+                                          guint property_id,
+                                          const GValue *value,
+                                          GParamSpec *pspec)
+{
+    SpiceMainChannelMigration *self = SPICE_MAIN_CHANNEL_MIGRATION(object);
+
+    switch (property_id) {
+    case PROP_COROUTINE_CONTEXT:
+        self->priv->source_coroutine = g_value_get_pointer(value);
+        break;
+
+    case PROP_SEAMLESS_MIGRATION:
+        self->priv->seamless_migration = g_value_get_boolean(value);
+        break;
+
+    case PROP_SOURCE_HOST_VERSION:
+        self->priv->source_host_version = g_value_get_uint(value);
+        break;
+
+    case PROP_SOURCE_SESSION:
+        g_clear_object(&self->priv->source_session);
+        self->priv->source_session = g_value_dup_object(value);
+        break;
+
+    case PROP_TARGET_SESSION:
+        g_clear_object(&self->priv->target_session);
+        self->priv->target_session = g_value_dup_object(value);
+        break;
+
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+spice_main_channel_migration_constructed(GObject *gobject)
+{
+    SpiceMainChannelMigration *self = SPICE_MAIN_CHANNEL_MIGRATION(gobject);
+
+    g_return_if_fail(SPICE_IS_SESSION(self->priv->source_session));
+    g_return_if_fail(SPICE_IS_SESSION(self->priv->target_session));
+    g_return_if_fail(spice_session_get_main_channel_migration(self->priv->source_session) == NULL);
+    g_return_if_fail(spice_session_get_main_channel_migration(self->priv->target_session) == NULL);
+
+    spice_session_set_main_channel_migration(self->priv->source_session, self);
+    spice_session_set_main_channel_migration(self->priv->target_session, self);
+
+    if (G_OBJECT_CLASS(spice_main_channel_migration_parent_class)->constructed) {
+        G_OBJECT_CLASS(spice_main_channel_migration_parent_class)->constructed(gobject);
+    }
+}
+
+static void
+spice_main_channel_migration_class_init(SpiceMainChannelMigrationClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->dispose = spice_main_channel_migration_dispose;
+    gobject_class->get_property = spice_main_channel_migration_get_property;
+    gobject_class->set_property = spice_main_channel_migration_set_property;
+    gobject_class->constructed = spice_main_channel_migration_constructed;
+
+    g_properties[PROP_COROUTINE_CONTEXT] =
+        g_param_spec_pointer("coroutine-context",
+                             "Coroutine context",
+                             "Coroutine context to yield back on failure",
+                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+
+    g_properties[PROP_SEAMLESS_MIGRATION] =
+        g_param_spec_boolean("seamless-migration",
+                             "Seamless migration",
+                             "To enable seamless migration",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    g_properties[PROP_SOURCE_HOST_VERSION] =
+        g_param_spec_uint("source-host-version",
+                          "Source Session",
+                          "Current session that we are migrating from",
+                          0, G_MAXUINT, 0,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    g_properties[PROP_SOURCE_SESSION] =
+        g_param_spec_object("source-session",
+                            "Source Session",
+                            "Current session that we are migrating from",
+                            SPICE_TYPE_SESSION,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    g_properties[PROP_TARGET_SESSION] =
+        g_param_spec_object("target-session",
+                            "Target Session",
+                            "Target session we are connecting to",
+                            SPICE_TYPE_SESSION,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(gobject_class, PROP_LAST, g_properties);
+}
+
diff --git a/src/channel-main-migration.h b/src/channel-main-migration.h
new file mode 100644
index 0000000..15a7e37
--- /dev/null
+++ b/src/channel-main-migration.h
@@ -0,0 +1,63 @@ 
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2019 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 __SPICE_CLIENT_MAIN_CHANNEL_MIGRATION_H__
+#define __SPICE_CLIENT_MAIN_CHANNEL_MIGRATION_H__
+
+#if !defined(__SPICE_CLIENT_H_INSIDE__) && !defined(SPICE_COMPILATION)
+#warning "Only <spice-client.h> can be included directly"
+#endif
+
+#include "spice-client.h"
+
+#include <stdbool.h>
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_MAIN_CHANNEL_MIGRATION   (spice_main_channel_migration_get_type())
+#define SPICE_MAIN_CHANNEL_MIGRATION(obj)   (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+                                             SPICE_TYPE_MAIN_CHANNEL_MIGRATION, \
+                                             SpiceMainChannelMigration))
+#define SPICE_MAIN_CHANNEL_MIGRATION_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST((klass), \
+                                                     SPICE_TYPE_MAIN_CHANNEL_MIGRATION, \
+                                                     SpiceMainChannelMigrationClass))
+#define SPICE_IS_MAIN_CHANNEL_MIGRATION(obj)    (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+                                                 SPICE_TYPE_MAIN_CHANNEL_MIGRATION))
+#define SPICE_IS_MAIN_CHANNEL_MIGRATION_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass), \
+                                                         SPICE_TYPE_MAIN_CHANNEL_MIGRATION))
+#define SPICE_MAIN_CHANNEL_MIGRATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
+                                                     SPICE_TYPE_MAIN_CHANNEL_MIGRATION, \
+                                                     SpiceMainChannelMigrationClass))
+
+/**
+ * SpiceMainChannelMigration
+ *
+ * Opaque data structure.
+ **/
+typedef struct _SpiceMainChannelMigration SpiceMainChannelMigration;
+typedef struct _SpiceMainChannelMigrationClass SpiceMainChannelMigrationClass;
+
+GType spice_main_channel_migration_get_type(void) G_GNUC_CONST;
+bool spice_main_channel_migration_init_migration(SpiceMainChannelMigration *self);
+void spice_main_channel_migration_seamless_handshake_done(SpiceMainChannelMigration *self,
+                                                          gboolean seamless_migration);
+guint spice_main_channel_migration_get_source_host_version(SpiceMainChannelMigration *self);
+bool spice_main_channel_migration_is_seamless(SpiceMainChannelMigration *self);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_MAIN_CHANNEL_MIGRATION_H__ */
diff --git a/src/channel-main.c b/src/channel-main.c
index 3dba399..3bd91b8 100644
--- a/src/channel-main.c
+++ b/src/channel-main.c
@@ -31,6 +31,7 @@ 
 #include "spice-session-priv.h"
 #include "spice-audio-priv.h"
 #include "spice-file-transfer-task-priv.h"
+#include "channel-main-migration.h"
 
 /**
  * SECTION:channel-main
@@ -50,8 +51,6 @@ 
 
 #define MAX_DISPLAY 16 /* Note must fit in a guint32, see monitors_align */
 
-typedef struct spice_migrate spice_migrate;
-
 typedef enum {
     DISPLAY_UNDEFINED,
     DISPLAY_DISABLED,
@@ -109,7 +108,6 @@  struct _SpiceMainChannelPrivate  {
 
     guint                       switch_host_delayed_id;
     guint                       migrate_delayed_id;
-    spice_migrate               *migrate_data;
     int                         max_clipboard;
 
     gboolean                    agent_volume_playback_sync;
@@ -117,21 +115,6 @@  struct _SpiceMainChannelPrivate  {
     GCancellable                *cancellable_volume_info;
 };
 
-struct spice_migrate {
-    struct coroutine *from;
-    SpiceMigrationDstInfo *info;
-    SpiceSession *session;
-    guint nchannels;
-    SpiceChannel *src_channel;
-    SpiceChannel *dst_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_WITH_PRIVATE(SpiceMainChannel, spice_main_channel, SPICE_TYPE_CHANNEL)
 
 /* Properties */
@@ -172,9 +155,6 @@  static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
 static void channel_set_handlers(SpiceChannelClass *klass);
 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 gboolean main_migrate_handshake_done(gpointer data);
 static void spice_main_channel_send_migration_handshake(SpiceChannel *channel);
 static void file_xfer_flushed(SpiceMainChannel *channel, gboolean success);
 static void file_xfer_read_async_cb(GObject *source_object,
@@ -2161,37 +2141,19 @@  static void main_handle_agent_token(SpiceChannel *channel, SpiceMsgIn *in)
     agent_send_msg_queue(SPICE_MAIN_CHANNEL(channel));
 }
 
-/* main context */
-static void migrate_channel_new_cb(SpiceSession *s, SpiceChannel *channel, gpointer data)
-{
-    g_signal_connect(channel, "channel-event",
-                     G_CALLBACK(migrate_channel_event_cb), data);
-}
-
-static void
-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);
-    if (newc != NULL && spice_channel_connect(newc)) {
-        mig->nchannels++;
-    }
-}
-
 /* coroutine context */
 static void spice_main_channel_send_migration_handshake(SpiceChannel *channel)
 {
-    SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+    SpiceSession *session = spice_channel_get_session(channel);
+    SpiceMainChannelMigration *migration = spice_session_get_main_channel_migration(session);
 
     if (!spice_channel_test_capability(channel, SPICE_MAIN_CAP_SEAMLESS_MIGRATE)) {
-        c->migrate_data->do_seamless = false;
-        g_idle_add(main_migrate_handshake_done, c->migrate_data);
+        spice_main_channel_migration_seamless_handshake_done(migration, FALSE);
     } else {
         SpiceMsgcMainMigrateDstDoSeamless msg_data;
         SpiceMsgOut *msg_out;
 
-        msg_data.src_version = c->migrate_data->src_mig_version;
+        msg_data.src_version = spice_main_channel_migration_get_source_host_version(migration);
 
         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);
@@ -2199,76 +2161,6 @@  static void spice_main_channel_send_migration_handshake(SpiceChannel *channel)
     }
 }
 
-/* main context */
-static void migrate_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
-                                     gpointer data)
-{
-    spice_migrate *mig = data;
-
-    g_return_if_fail(mig->nchannels > 0);
-    g_signal_handlers_disconnect_by_func(channel, migrate_channel_event_cb, data);
-
-    switch (event) {
-    case SPICE_CHANNEL_OPENED:
-        if (spice_channel_get_channel_type(channel) == SPICE_CHANNEL_MAIN) {
-            SpiceSession *session = spice_channel_get_session(mig->src_channel);
-            if (mig->do_seamless) {
-                SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
-
-                spice_channel_set_state(channel, SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
-                mig->dst_channel = channel;
-                main_priv->migrate_data = mig;
-            } else {
-                spice_channel_set_state(channel, SPICE_CHANNEL_STATE_MIGRATING);
-                mig->nchannels--;
-            }
-            /* now connect the rest of the channels */
-            GList *channels, *l;
-            l = channels = spice_session_get_channels(session);
-            while (l != NULL) {
-                SpiceChannel *it = SPICE_CHANNEL(l->data);
-
-                l = l->next;
-                if (spice_channel_get_channel_type(it) == SPICE_CHANNEL_MAIN) {
-                    continue;
-                }
-                migrate_channel_connect(mig,
-                                        spice_channel_get_channel_type(it),
-                                        spice_channel_get_channel_id(it));
-            }
-            g_list_free(channels);
-        } else {
-            spice_channel_set_state(channel, SPICE_CHANNEL_STATE_MIGRATING);
-            mig->nchannels--;
-        }
-
-        SPICE_DEBUG("migration: channel opened chan:%p, left %u", channel, mig->nchannels);
-        if (mig->nchannels == 0)
-            coroutine_yieldto(mig->from, NULL);
-        break;
-    default:
-        CHANNEL_DEBUG(channel, "error or unhandled channel event during migration: %u", event);
-        /* go back to main channel to report error */
-        coroutine_yieldto(mig->from, NULL);
-    }
-}
-
-/* main context */
-static gboolean main_migrate_handshake_done(gpointer data)
-{
-    spice_migrate *mig = data;
-    SpiceChannel *channel = SPICE_CHANNEL(mig->dst_channel);
-
-    g_return_val_if_fail(spice_channel_get_channel_type(channel) == SPICE_CHANNEL_MAIN, FALSE);
-    g_return_val_if_fail(spice_channel_get_state(channel) == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE, FALSE);
-
-    spice_channel_set_state(channel, SPICE_CHANNEL_STATE_MIGRATING);
-    mig->nchannels--;
-    if (mig->nchannels == 0)
-        coroutine_yieldto(mig->from, NULL);
-    return FALSE;
-}
-
 #ifdef __GNUC__
 typedef struct __attribute__ ((__packed__)) OldRedMigrationBegin {
 #else
@@ -2279,38 +2171,6 @@  typedef struct __declspec(align(1)) OldRedMigrationBegin {
     char host[0];
 } OldRedMigrationBegin;
 
-/* main context */
-static gboolean migrate_connect(gpointer data)
-{
-    spice_migrate *mig = data;
-    SpiceChannelPrivate  *c;
-
-    g_return_val_if_fail(mig != NULL, FALSE);
-    g_return_val_if_fail(mig->info != NULL, FALSE);
-    g_return_val_if_fail(mig->nchannels == 0, FALSE);
-    c = SPICE_CHANNEL(mig->src_channel)->priv;
-    g_return_val_if_fail(c != NULL, FALSE);
-    g_return_val_if_fail(mig->session != NULL, FALSE);
-
-    spice_session_set_migration_state(mig->session, SPICE_SESSION_MIGRATION_CONNECTING);
-
-    SpiceMigrationDstInfo *info = mig->info;
-    SPICE_DEBUG("migrate_begin %u %s %d %d",
-                info->host_size, info->host_data, info->port, info->sport);
-
-    g_signal_connect(mig->session, "channel-new",
-                     G_CALLBACK(migrate_channel_new_cb), mig);
-
-    g_signal_emit(mig->src_channel, signals[SPICE_MIGRATION_STARTED], 0,
-                  mig->session);
-
-    /* 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);
-
-    return FALSE;
-}
-
 static void
 migration_session_set_destination_info(SpiceSession *target_session,
                                        SpiceMigrationDstInfo *info)
@@ -2348,50 +2208,51 @@  static void main_migrate_connect(SpiceChannel *channel,
                                  bool do_seamless,
                                  uint32_t src_mig_version)
 {
-    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
     int reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECT_ERROR;
-    spice_migrate mig = { 0, };
     SpiceMsgOut *out;
-    SpiceSession *session;
-
-    mig.src_channel = channel;
-    mig.info = dst_info;
-    mig.from = coroutine_self();
-    mig.do_seamless = do_seamless;
-    mig.src_mig_version = src_mig_version;
+    SpiceSession *session, *target_session;
+    SpiceMainChannelMigration *migration = NULL;
 
     CHANNEL_DEBUG(channel, "migrate connect");
     session = spice_channel_get_session(channel);
-    mig.session = spice_session_new_from_session(session);
-    if (!spice_session_set_migration_session(session, mig.session)) {
+    target_session = spice_session_new_from_session(session);
+    if (!spice_session_set_migration_session(session, target_session)) {
         goto end;
     }
 
-    migration_session_set_destination_info(mig.session, dst_info);
-
-    main_priv->migrate_data = &mig;
+    migration_session_set_destination_info(target_session, dst_info);
+    migration = g_object_new(SPICE_TYPE_MAIN_CHANNEL_MIGRATION,
+                             "source-session", session,
+                             "target-session", target_session,
+                             "coroutine-context", coroutine_self(),
+                             "seamless-migration", do_seamless,
+                             "source-host-version", src_mig_version,
+                             NULL);
 
-    /* no need to track idle, call is sync for this coroutine */
-    g_idle_add(migrate_connect, &mig);
+    /* Allow application to track new-channel events on target session */
+    g_signal_emit(channel, signals[SPICE_MIGRATION_STARTED], 0, target_session);
 
-    /* switch to main loop and wait for connections */
-    coroutine_yield(NULL);
+    /* Runs sync from coroutine perspective */
+    bool success = spice_main_channel_migration_init_migration(migration);
 
-    if (mig.nchannels != 0) {
+    if (!success) {
         CHANNEL_DEBUG(channel, "migrate failed: some channels failed to connect");
         spice_session_abort_migration(session);
+        goto end;
+    }
+
+    if (spice_main_channel_migration_is_seamless(migration)) {
+        SPICE_DEBUG("migration (seamless): connections all ok");
+        reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED_SEAMLESS;
     } else {
-        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_start_migrating(session, mig.do_seamless);
+        SPICE_DEBUG("migration (semi-seamless): connections all ok");
+        reply_type = SPICE_MSGC_MAIN_MIGRATE_CONNECTED;
     }
 
+    spice_session_start_migrating(session, spice_main_channel_migration_is_seamless(migration));
+
 end:
+    g_clear_object(&migration);
     CHANNEL_DEBUG(channel, "migrate connect reply %d", reply_type);
     out = spice_msg_out_new(channel, reply_type);
     spice_msg_out_send(out);
@@ -2415,20 +2276,20 @@  static void main_handle_migrate_begin_seamless(SpiceChannel *channel, SpiceMsgIn
 
 static void main_handle_migrate_dst_seamless_ack(SpiceChannel *channel, SpiceMsgIn *in)
 {
-    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
+    SpiceSession *session = spice_channel_get_session(channel);
+    SpiceMainChannelMigration *migration = spice_session_get_main_channel_migration(session);
 
     g_return_if_fail(spice_channel_get_state(channel) == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
-    main_priv->migrate_data->do_seamless = true;
-    g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
+    spice_main_channel_migration_seamless_handshake_done(migration, TRUE);
 }
 
 static void main_handle_migrate_dst_seamless_nack(SpiceChannel *channel, SpiceMsgIn *in)
 {
-    SpiceMainChannelPrivate *main_priv = SPICE_MAIN_CHANNEL(channel)->priv;
+    SpiceSession *session = spice_channel_get_session(channel);
+    SpiceMainChannelMigration *migration = spice_session_get_main_channel_migration(session);
 
     g_return_if_fail(spice_channel_get_state(channel) == SPICE_CHANNEL_STATE_MIGRATION_HANDSHAKE);
-    main_priv->migrate_data->do_seamless = false;
-    g_idle_add(main_migrate_handshake_done, main_priv->migrate_data);
+    spice_main_channel_migration_seamless_handshake_done(migration, FALSE);
 }
 
 /* main context */
diff --git a/src/meson.build b/src/meson.build
index 0461dea..84ee9be 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -90,6 +90,8 @@  spice_client_glib_sources = [
   'channel-base.c',
   'channel-display-gst.c',
   'channel-display-priv.h',
+  'channel-main-migration.c',
+  'channel-main-migration.h',
   'channel-playback-priv.h',
   'channel-usbredir-priv.h',
   'client_sw_canvas.c',
diff --git a/src/spice-session-priv.h b/src/spice-session-priv.h
index 62cebc5..b453f92 100644
--- a/src/spice-session-priv.h
+++ b/src/spice-session-priv.h
@@ -28,6 +28,7 @@ 
 typedef struct _PhodavServer PhodavServer;
 #endif
 
+#include "channel-main-migration.h"
 #include "desktop-integration.h"
 #include "spice-session.h"
 #include "spice-gtk-session.h"
@@ -95,6 +96,9 @@  PhodavServer *spice_session_get_webdav_server(SpiceSession *session);
 PhodavServer* channel_webdav_server_new(SpiceSession *session);
 guint spice_session_get_n_display_channels(SpiceSession *session);
 gboolean spice_session_set_migration_session(SpiceSession *session, SpiceSession *mig_session);
+SpiceMainChannelMigration * spice_session_get_main_channel_migration(SpiceSession *self);
+void spice_session_set_main_channel_migration(SpiceSession *self,
+                                              SpiceMainChannelMigration *mc_migration);
 SpiceAudio *spice_audio_get(SpiceSession *session, GMainContext *context);
 const gchar* spice_audio_data_mode_to_string(gint mode);
 G_END_DECLS
diff --git a/src/spice-session.c b/src/spice-session.c
index a770c92..e6bbb4f 100644
--- a/src/spice-session.c
+++ b/src/spice-session.c
@@ -110,6 +110,9 @@  struct _SpiceSessionPrivate {
     gboolean          migrate_wait_init;
     guint             after_main_init;
     gboolean          for_migration;
+    
+    /* FIXME: Can possibly remove lots of the above */
+    SpiceMainChannelMigration *mc_migration;
 
     display_cache     *images;
     display_cache     *palettes;
@@ -344,6 +347,7 @@  spice_session_dispose(GObject *gobject)
     g_clear_object(&s->usb_manager);
     g_clear_object(&s->proxy);
     g_clear_object(&s->webdav);
+    g_clear_object(&s->mc_migration);
 
     /* Chain up to the parent class */
     if (G_OBJECT_CLASS(spice_session_parent_class)->dispose)
@@ -1752,6 +1756,7 @@  void spice_session_start_migrating(SpiceSession *session,
                                    gboolean full_migration)
 {
     g_return_if_fail(SPICE_IS_SESSION(session));
+    g_return_if_fail(SPICE_IS_MAIN_CHANNEL_MIGRATION(session->priv->mc_migration));
 
     SpiceSessionPrivate *s = session->priv;
     SpiceSessionPrivate *m;
@@ -1834,6 +1839,7 @@  end:
     g_clear_pointer(&s->migration_left, g_list_free);
     session_disconnect(s->migration, FALSE);
     g_clear_pointer(&s->migration, g_object_unref);
+    g_clear_object(&s->mc_migration);
 
     s->migrate_wait_init = FALSE;
     if (s->after_main_init) {
@@ -1874,6 +1880,7 @@  void spice_session_channel_migrate(SpiceSession *session, SpiceChannel *channel)
         session_disconnect(s->migration, FALSE);
         g_clear_pointer(&s->migration, g_object_unref);
         spice_session_set_migration_state(session, SPICE_SESSION_MIGRATION_NONE);
+        g_clear_object(&s->mc_migration);
     }
 }
 
@@ -2864,3 +2871,22 @@  gboolean spice_session_set_migration_session(SpiceSession *session, SpiceSession
 
     return TRUE;
 }
+
+G_GNUC_INTERNAL void
+spice_session_set_main_channel_migration(SpiceSession *self,
+                                         SpiceMainChannelMigration *mc_migration)
+{
+    g_return_if_fail(SPICE_IS_SESSION(self));
+    g_return_if_fail(SPICE_IS_MAIN_CHANNEL_MIGRATION(mc_migration));
+    g_return_if_fail(self->priv->mc_migration == NULL);
+
+    self->priv->mc_migration = g_object_ref(mc_migration);
+}
+
+G_GNUC_INTERNAL SpiceMainChannelMigration *
+spice_session_get_main_channel_migration(SpiceSession *self)
+{
+    g_return_val_if_fail(SPICE_IS_SESSION(self), FALSE);
+
+    return self->priv->mc_migration;
+}

Comments