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 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel