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

Alon Levy alevy at redhat.com
Sun Dec 18 10:23:30 PST 2011


On Sun, Dec 18, 2011 at 06:27:25PM +0100, Marc-André Lureau wrote:
> 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.

MSGC ?

> ---
>  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);

This looks strange - is this removal related to this patch?

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


More information about the Spice-devel mailing list