[Spice-commits] 7 commits - common/messages.h gtk/channel-cursor.c gtk/channel-display.c gtk/channel-main.c gtk/channel-playback.c gtk/channel-record.c gtk/channel-smartcard.c gtk/channel-usbredir.c gtk/coroutine_winfibers.c gtk/gio-coroutine.c gtk/spice-channel.c gtk/spice-channel.h gtk/spice-channel-priv.h gtk/spice-session.c gtk/spice-session-priv.h spice.proto
Marc-André Lureau
elmarco at kemper.freedesktop.org
Mon Dec 19 04:08:16 PST 2011
common/messages.h | 2
gtk/channel-cursor.c | 12 ++++
gtk/channel-display.c | 83 +++++++++++++++++++++--------
gtk/channel-main.c | 128 +++++++++++++++++++++++++++++++++++++++-------
gtk/channel-playback.c | 19 ++++++
gtk/channel-record.c | 21 +++++++
gtk/channel-smartcard.c | 56 ++++++++++++++------
gtk/channel-usbredir.c | 10 +++
gtk/coroutine_winfibers.c | 1
gtk/gio-coroutine.c | 13 ++++
gtk/spice-channel-priv.h | 2
gtk/spice-channel.c | 65 +++++++++++++++++------
gtk/spice-channel.h | 4 +
gtk/spice-session-priv.h | 4 +
gtk/spice-session.c | 84 ++++++++++++++++++++++++++++++
spice.proto | 9 ++-
16 files changed, 437 insertions(+), 76 deletions(-)
New commits:
commit 1ce8a99930bc2183d4b896a587db749d617101db
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Sun Dec 18 18:00:18 2011 +0100
Support semi-seamless migration
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.
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
commit fce2d8a23ce5f26a288755b054bf6266910377a4
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Sun Dec 18 17:59:21 2011 +0100
main: Handle semi-seamless MIGRATION_BEGIN
diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index 47e41ab..38a2c5d 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -1470,7 +1470,7 @@ static gboolean migrate_connect(gpointer data)
mig->session = spice_session_new_from_session(session);
if ((c->peer_hdr.major_version == 1) &&
- (c->peer_hdr.minor_version < 2)) {
+ (c->peer_hdr.minor_version < 1)) {
OldRedMigrationBegin *info = (OldRedMigrationBegin *)mig->info;
SPICE_DEBUG("migrate_begin old %s %d %d",
info->host, info->port, info->sport);
@@ -1478,19 +1478,34 @@ static gboolean migrate_connect(gpointer data)
sport = info->sport;
host = info->host;
} else {
- GByteArray *pubkey = g_byte_array_new();
SpiceMsgMainMigrationBegin *info = mig->info;
SPICE_DEBUG("migrate_begin %d %s %d %d",
info->host_size, info->host_data, info->port, info->sport);
port = info->port;
sport = info->sport;
host = (char*)info->host_data;
- g_byte_array_append(pubkey, info->pub_key_data, info->pub_key_size);
- g_object_set(mig->session,
- "pubkey", pubkey,
- "verify", SPICE_SESSION_VERIFY_PUBKEY,
- NULL);
- g_byte_array_unref(pubkey);
+
+ if ((c->peer_hdr.major_version == 1) ||
+ (c->peer_hdr.major_version == 2 && c->peer_hdr.minor_version < 1)) {
+ GByteArray *pubkey = g_byte_array_new();
+
+ g_byte_array_append(pubkey, info->pub_key_data, info->pub_key_size);
+ g_object_set(mig->session,
+ "pubkey", pubkey,
+ "verify", SPICE_SESSION_VERIFY_PUBKEY,
+ NULL);
+ g_byte_array_unref(pubkey);
+ } else {
+ gchar *subject = g_alloca(info->cert_subject_size + 1);
+ strncpy(subject, (const char*)info->cert_subject_data, info->cert_subject_size);
+ subject[info->cert_subject_size] = '\0';
+
+ // session data are already copied
+ g_object_set(mig->session,
+ "cert-subject", subject,
+ "verify", SPICE_SESSION_VERIFY_SUBJECT,
+ NULL);
+ }
}
if (g_getenv("SPICE_MIG_HOST"))
commit 842039603e660c06ede77cdbd65ed3bb86c7be3b
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Sun Dec 18 17:55:19 2011 +0100
display: try to reuse exisiting primary surface
diff --git a/gtk/channel-display.c b/gtk/channel-display.c
index 47beaa1..08e3e16 100644
--- a/gtk/channel-display.c
+++ b/gtk/channel-display.c
@@ -122,13 +122,17 @@ static void spice_display_channel_dispose(GObject *object)
G_OBJECT_CLASS(spice_display_channel_parent_class)->dispose(object);
}
-static void spice_display_channel_finalize(GObject *obj)
+static void spice_display_channel_finalize(GObject *object)
{
- clear_surfaces(SPICE_CHANNEL(obj));
- clear_streams(SPICE_CHANNEL(obj));
+ SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(object)->priv;
+
+ c->migrate_wait_primary = FALSE;
+
+ clear_surfaces(SPICE_CHANNEL(object));
+ clear_streams(SPICE_CHANNEL(object));
if (G_OBJECT_CLASS(spice_display_channel_parent_class)->finalize)
- G_OBJECT_CLASS(spice_display_channel_parent_class)->finalize(obj);
+ G_OBJECT_CLASS(spice_display_channel_parent_class)->finalize(object);
}
static void spice_display_channel_constructed(GObject *object)
@@ -590,7 +594,26 @@ static int create_canvas(SpiceChannel *channel, display_surface *surface)
SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
if (surface->primary) {
+ display_surface *primary = find_surface(c, 0);
+
+ if (primary) {
+ if (c->migrate_wait_primary &&
+ primary->width == surface->width &&
+ primary->height == surface->height) {
+ c->migrate_wait_primary = FALSE;
+ SPICE_DEBUG("Reusing existing primary surface");
+ return 0;
+ }
+
+ emit_main_context(channel, SPICE_DISPLAY_PRIMARY_DESTROY);
+ ring_remove(&primary->link);
+ destroy_canvas(primary);
+ free(primary);
+ }
+
SPICE_DEBUG("display: create primary canvas");
+ c->migrate_wait_primary = FALSE;
+
#ifdef HAVE_SYS_SHM_H
surface->shmid = shmget(IPC_PRIVATE, surface->size, IPC_CREAT | 0777);
if (surface->shmid >= 0) {
@@ -637,6 +660,12 @@ static int create_canvas(SpiceChannel *channel, display_surface *surface)
surface->zlib_decoder);
g_return_val_if_fail(surface->canvas != NULL, 0);
+ ring_add(&c->surfaces, &surface->link);
+
+ if (surface->primary)
+ emit_main_context(channel, SPICE_DISPLAY_PRIMARY_CREATE,
+ surface->format, surface->width, surface->height,
+ surface->stride, surface->shmid, surface->data);
return 0;
}
@@ -685,9 +714,15 @@ static void clear_surfaces(SpiceChannel *channel)
display_surface *surface;
RingItem *item;
- while (!ring_is_empty(&c->surfaces)) {
- item = ring_get_head(&c->surfaces);
+ for (item = ring_get_head(&c->surfaces); item != NULL; ) {
surface = SPICE_CONTAINEROF(item, display_surface, link);
+ item = ring_next(&c->surfaces, item);
+
+ if (c->migrate_wait_primary && surface->primary) {
+ SPICE_DEBUG("Try to keep exisiting primary surface during migration");
+ continue;
+ }
+
ring_remove(&surface->link);
destroy_canvas(surface);
free(surface);
@@ -738,17 +773,10 @@ static void display_handle_mode(SpiceChannel *channel, SpiceMsgIn *in)
{
SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
SpiceMsgDisplayMode *mode = spice_msg_in_parsed(in);
- display_surface *surface = find_surface(c, 0);
+ display_surface *surface;
g_warn_if_fail(c->mark == FALSE);
- if (surface) {
- emit_main_context(channel, SPICE_DISPLAY_PRIMARY_DESTROY);
- ring_remove(&surface->link);
- destroy_canvas(surface);
- free(surface);
- }
-
surface = spice_new0(display_surface, 1);
surface->format = mode->bits == 32 ?
SPICE_SURFACE_FMT_32_xRGB : SPICE_SURFACE_FMT_16_555;
@@ -758,15 +786,11 @@ static void display_handle_mode(SpiceChannel *channel, SpiceMsgIn *in)
surface->size = surface->height * surface->stride;
surface->primary = true;
create_canvas(channel, surface);
- emit_main_context(channel, SPICE_DISPLAY_PRIMARY_CREATE,
- surface->format, surface->width, surface->height,
- surface->stride, surface->shmid, surface->data);
#ifdef HAVE_SYS_SHM_H
if (surface->shmid != -1) {
shmctl(surface->shmid, IPC_RMID, 0);
}
#endif
- ring_add(&c->surfaces, &surface->link);
}
/* coroutine context */
@@ -1219,9 +1243,6 @@ static void display_handle_surface_create(SpiceChannel *channel, SpiceMsgIn *in)
if (create->flags == SPICE_SURFACE_FLAGS_PRIMARY) {
surface->primary = true;
create_canvas(channel, surface);
- emit_main_context(channel, SPICE_DISPLAY_PRIMARY_CREATE,
- surface->format, surface->width, surface->height,
- surface->stride, surface->shmid, surface->data);
if (c->mark_false_event_id != 0) {
g_source_remove(c->mark_false_event_id);
c->mark_false_event_id = FALSE;
@@ -1230,8 +1251,6 @@ static void display_handle_surface_create(SpiceChannel *channel, SpiceMsgIn *in)
surface->primary = false;
create_canvas(channel, surface);
}
-
- ring_add(&c->surfaces, &surface->link);
}
static gboolean display_mark_false(gpointer data)
commit f7c5f1e8fd7af0abc5512e504ba9f06e0e9411e8
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Sun Dec 18 17:48:34 2011 +0100
Add channel_reset method
This new private method is to reset the channel to its initial state,
used by semi-seamless migration
diff --git a/gtk/channel-cursor.c b/gtk/channel-cursor.c
index 4ad402c..6766b40 100644
--- a/gtk/channel-cursor.c
+++ b/gtk/channel-cursor.c
@@ -92,6 +92,17 @@ static void spice_cursor_channel_finalize(GObject *obj)
G_OBJECT_CLASS(spice_cursor_channel_parent_class)->finalize(obj);
}
+/* coroutine context */
+static void spice_cursor_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+ SpiceCursorChannelPrivate *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+
+ delete_cursor_all(channel);
+ c->init_done = FALSE;
+
+ SPICE_CHANNEL_CLASS(spice_cursor_channel_parent_class)->channel_reset(channel, migrating);
+}
+
static void spice_cursor_channel_class_init(SpiceCursorChannelClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
@@ -99,6 +110,7 @@ static void spice_cursor_channel_class_init(SpiceCursorChannelClass *klass)
gobject_class->finalize = spice_cursor_channel_finalize;
channel_class->handle_msg = spice_cursor_handle_msg;
+ channel_class->channel_reset = spice_cursor_channel_reset;
/**
* SpiceCursorChannel::cursor-set:
diff --git a/gtk/channel-display.c b/gtk/channel-display.c
index 120128f..47beaa1 100644
--- a/gtk/channel-display.c
+++ b/gtk/channel-display.c
@@ -73,6 +73,7 @@ struct _SpiceDisplayChannelPrivate {
#ifdef WIN32
HDC dc;
#endif
+ gboolean migrate_wait_primary;
};
G_DEFINE_TYPE(SpiceDisplayChannel, spice_display_channel, SPICE_TYPE_CHANNEL)
@@ -103,6 +104,8 @@ static void clear_surfaces(SpiceChannel *channel);
static void clear_streams(SpiceChannel *channel);
static display_surface *find_surface(SpiceDisplayChannelPrivate *c, int surface_id);
static gboolean display_stream_render(display_stream *st);
+static void spice_display_channel_reset(SpiceChannel *channel, gboolean migrating);
+static void destroy_canvas(display_surface *surface);
/* ------------------------------------------------------------------ */
@@ -181,6 +184,20 @@ static void spice_display_set_property(GObject *object,
}
}
+/* main or coroutine context */
+static void spice_display_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+ SpiceDisplayChannelPrivate *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+
+ /* palettes, images, and glz_window are cleared in the session */
+
+ c->migrate_wait_primary = migrating;
+ clear_streams(channel);
+ clear_surfaces(channel);
+
+ SPICE_CHANNEL_CLASS(spice_display_channel_parent_class)->channel_reset(channel, migrating);
+}
+
static void spice_display_channel_class_init(SpiceDisplayChannelClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
@@ -194,6 +211,7 @@ static void spice_display_channel_class_init(SpiceDisplayChannelClass *klass)
channel_class->handle_msg = spice_display_handle_msg;
channel_class->channel_up = spice_display_channel_up;
+ channel_class->channel_reset = spice_display_channel_reset;
g_object_class_install_property
(gobject_class, PROP_HEIGHT,
diff --git a/gtk/channel-main.c b/gtk/channel-main.c
index 615cdf6..47e41ab 100644
--- a/gtk/channel-main.c
+++ b/gtk/channel-main.c
@@ -122,6 +122,7 @@ static guint signals[SPICE_MAIN_LAST_SIGNAL];
static void spice_main_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
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);
@@ -250,7 +251,7 @@ static void spice_main_channel_finalize(GObject *obj)
SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(obj)->priv;
g_free(c->agent_msg_data);
- g_queue_free(c->agent_msg_queue);
+ agent_free_msg_queue(SPICE_MAIN_CHANNEL(obj));
if (G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize)
G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize(obj);
@@ -265,6 +266,26 @@ static void spice_channel_iterate_write(SpiceChannel *channel)
SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->iterate_write(channel);
}
+/* main or coroutine context */
+static void spice_main_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+ SpiceMainChannelPrivate *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+ c->agent_connected = FALSE;
+ c->agent_caps_received = FALSE;
+ c->agent_display_config_sent = FALSE;
+ c->agent_tokens = 0;
+ c->agent_msg_pos = 0;
+ g_free(c->agent_msg_data);
+ c->agent_msg_data = NULL;
+ c->agent_msg_size = 0;
+
+ agent_free_msg_queue(SPICE_MAIN_CHANNEL(channel));
+ c->agent_msg_queue = g_queue_new();
+
+ SPICE_CHANNEL_CLASS(spice_main_channel_parent_class)->channel_reset(channel, migrating);
+}
+
static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
@@ -277,6 +298,7 @@ static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
channel_class->handle_msg = spice_main_handle_msg;
channel_class->iterate_write = spice_channel_iterate_write;
+ channel_class->channel_reset = spice_main_channel_reset;
/**
* SpiceMainChannel:mouse-mode:
@@ -706,6 +728,23 @@ static void do_emit_main_context(GObject *object, int signum, gpointer params)
/* ------------------------------------------------------------------ */
+static void agent_free_msg_queue(SpiceMainChannel *channel)
+{
+ SpiceMainChannelPrivate *c = channel->priv;
+ SpiceMsgOut *out;
+
+ if (!c->agent_msg_queue)
+ return;
+
+ while (!g_queue_is_empty(c->agent_msg_queue)) {
+ out = g_queue_pop_head(c->agent_msg_queue);
+ spice_msg_out_unref(out);
+ }
+
+ g_queue_free(c->agent_msg_queue);
+ c->agent_msg_queue = NULL;
+}
+
/* coroutine context */
static void agent_send_msg_queue(SpiceMainChannel *channel)
{
diff --git a/gtk/channel-playback.c b/gtk/channel-playback.c
index 4804682..32f8b1a 100644
--- a/gtk/channel-playback.c
+++ b/gtk/channel-playback.c
@@ -154,6 +154,24 @@ static void spice_playback_channel_set_property(GObject *gobject,
}
}
+/* main or coroutine context */
+static void spice_playback_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+ SpicePlaybackChannelPrivate *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+
+ if (c->celt_decoder) {
+ celt051_decoder_destroy(c->celt_decoder);
+ c->celt_decoder = NULL;
+ }
+
+ if (c->celt_mode) {
+ celt051_mode_destroy(c->celt_mode);
+ c->celt_mode = NULL;
+ }
+
+ SPICE_CHANNEL_CLASS(spice_playback_channel_parent_class)->channel_reset(channel, migrating);
+}
+
static void spice_playback_channel_class_init(SpicePlaybackChannelClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
@@ -164,6 +182,7 @@ static void spice_playback_channel_class_init(SpicePlaybackChannelClass *klass)
gobject_class->set_property = spice_playback_channel_set_property;
channel_class->handle_msg = spice_playback_handle_msg;
+ channel_class->channel_reset = spice_playback_channel_reset;
g_object_class_install_property
(gobject_class, PROP_NCHANNELS,
diff --git a/gtk/channel-record.c b/gtk/channel-record.c
index 4e5e893..c5aded9 100644
--- a/gtk/channel-record.c
+++ b/gtk/channel-record.c
@@ -163,6 +163,26 @@ static void spice_record_channel_set_property(GObject *gobject,
}
}
+static void channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+ SpiceRecordChannelPrivate *c = SPICE_RECORD_CHANNEL(channel)->priv;
+
+ g_free(c->last_frame);
+ c->last_frame = NULL;
+
+ if (c->celt_encoder) {
+ celt051_encoder_destroy(c->celt_encoder);
+ c->celt_encoder = NULL;
+ }
+
+ if (c->celt_mode) {
+ celt051_mode_destroy(c->celt_mode);
+ c->celt_mode = NULL;
+ }
+
+ SPICE_CHANNEL_CLASS(spice_record_channel_parent_class)->channel_reset(channel, migrating);
+}
+
static void spice_record_channel_class_init(SpiceRecordChannelClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
@@ -173,6 +193,7 @@ static void spice_record_channel_class_init(SpiceRecordChannelClass *klass)
gobject_class->set_property = spice_record_channel_set_property;
channel_class->handle_msg = spice_record_handle_msg;
channel_class->channel_up = channel_up;
+ channel_class->channel_reset = channel_reset;
g_object_class_install_property
(gobject_class, PROP_NCHANNELS,
diff --git a/gtk/channel-smartcard.c b/gtk/channel-smartcard.c
index 98e77eb..6fa4435 100644
--- a/gtk/channel-smartcard.c
+++ b/gtk/channel-smartcard.c
@@ -144,31 +144,56 @@ static void spice_smartcard_channel_init(SpiceSmartcardChannel *channel)
static void spice_smartcard_channel_finalize(GObject *obj)
{
- SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(obj);
+ SpiceSmartcardChannelPrivate *c = SPICE_SMARTCARD_CHANNEL_GET_PRIVATE(obj);
- if (channel->priv->pending_card_insertions != NULL) {
- g_hash_table_destroy(channel->priv->pending_card_insertions);
- channel->priv->pending_card_insertions = NULL;
+ if (c->pending_card_insertions != NULL) {
+ g_hash_table_destroy(c->pending_card_insertions);
+ c->pending_card_insertions = NULL;
}
- if (channel->priv->pending_reader_removals != NULL) {
- g_hash_table_destroy(channel->priv->pending_reader_removals);
- channel->priv->pending_reader_removals = NULL;
+ if (c->pending_reader_removals != NULL) {
+ g_hash_table_destroy(c->pending_reader_removals);
+ c->pending_reader_removals = NULL;
}
- if (channel->priv->message_queue != NULL) {
- g_queue_foreach(channel->priv->message_queue,
- (GFunc)smartcard_message_free, NULL);
- g_queue_free(channel->priv->message_queue);
- channel->priv->message_queue = NULL;
+ if (c->message_queue != NULL) {
+ g_queue_foreach(c->message_queue, (GFunc)smartcard_message_free, NULL);
+ g_queue_free(c->message_queue);
+ c->message_queue = NULL;
}
- if (channel->priv->in_flight_message != NULL) {
- smartcard_message_free(channel->priv->in_flight_message);
- channel->priv->in_flight_message = NULL;
+ if (c->in_flight_message != NULL) {
+ smartcard_message_free(c->in_flight_message);
+ c->in_flight_message = NULL;
}
+ g_list_free(c->pending_reader_additions);
+ c->pending_reader_additions = NULL;
+
if (G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->finalize)
G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->finalize(obj);
}
+static void spice_smartcard_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+ SpiceSmartcardChannelPrivate *c = SPICE_SMARTCARD_CHANNEL_GET_PRIVATE(channel);
+
+ g_hash_table_remove_all(c->pending_card_insertions);
+ g_hash_table_remove_all(c->pending_reader_removals);
+
+ if (c->message_queue != NULL) {
+ g_queue_foreach(c->message_queue, (GFunc)smartcard_message_free, NULL);
+ g_queue_clear(c->message_queue);
+ }
+
+ if (c->in_flight_message != NULL) {
+ smartcard_message_free(c->in_flight_message);
+ c->in_flight_message = NULL;
+ }
+
+ g_list_free(c->pending_reader_additions);
+ c->pending_reader_additions = NULL;
+
+ SPICE_CHANNEL_CLASS(spice_smartcard_channel_parent_class)->channel_reset(channel, migrating);
+}
+
static void spice_smartcard_channel_class_init(SpiceSmartcardChannelClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
@@ -177,6 +202,7 @@ static void spice_smartcard_channel_class_init(SpiceSmartcardChannelClass *klass
gobject_class->finalize = spice_smartcard_channel_finalize;
channel_class->handle_msg = spice_smartcard_handle_msg;
channel_class->channel_up = spice_smartcard_channel_up;
+ channel_class->channel_reset = spice_smartcard_channel_reset;
g_type_class_add_private(klass, sizeof(SpiceSmartcardChannelPrivate));
}
diff --git a/gtk/channel-usbredir.c b/gtk/channel-usbredir.c
index 06d80d5..d9cc03e 100644
--- a/gtk/channel-usbredir.c
+++ b/gtk/channel-usbredir.c
@@ -102,6 +102,15 @@ static void spice_usbredir_channel_init(SpiceUsbredirChannel *channel)
#endif
}
+#ifdef USE_USBREDIR
+static void spice_usbredir_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+ spice_usbredir_channel_disconnect(SPICE_USBREDIR_CHANNEL(channel));
+
+ SPICE_CHANNEL_CLASS(spice_usbredir_channel_parent_class)->channel_reset(channel, migrating);
+}
+#endif
+
static void spice_usbredir_channel_class_init(SpiceUsbredirChannelClass *klass)
{
#ifdef USE_USBREDIR
@@ -111,6 +120,7 @@ static void spice_usbredir_channel_class_init(SpiceUsbredirChannelClass *klass)
gobject_class->dispose = spice_usbredir_channel_dispose;
channel_class->handle_msg = spice_usbredir_handle_msg;
channel_class->channel_up = spice_usbredir_channel_up;
+ channel_class->channel_reset = spice_usbredir_channel_reset;
g_type_class_add_private(klass, sizeof(SpiceUsbredirChannelPrivate));
#endif
diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
index ed5f3a7..5585f13 100644
--- a/gtk/spice-channel-priv.h
+++ b/gtk/spice-channel-priv.h
@@ -166,6 +166,8 @@ void spice_channel_swap(SpiceChannel *channel, SpiceChannel *swap);
void spice_channel_set_common_capability(SpiceChannel *channel, guint32 cap);
gboolean spice_channel_get_read_only(SpiceChannel *channel);
+void spice_channel_reset(SpiceChannel *channel, gboolean migrating);
+
/* coroutine context */
#define emit_main_context(object, event, args...) \
G_STMT_START { \
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index b6779ba..6420c53 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -45,6 +45,7 @@ static void spice_channel_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg);
static void spice_channel_write_msg(SpiceChannel *channel, SpiceMsgOut *out);
static void spice_channel_send_link(SpiceChannel *channel);
static void channel_disconnect(SpiceChannel *channel);
+static void channel_reset(SpiceChannel *channel, gboolean migrating);
/**
* SECTION:spice-channel
@@ -249,6 +250,7 @@ static void spice_channel_class_init(SpiceChannelClass *klass)
klass->iterate_write = spice_channel_iterate_write;
klass->iterate_read = spice_channel_iterate_read;
klass->channel_disconnect = channel_disconnect;
+ klass->channel_reset = channel_reset;
gobject_class->constructed = spice_channel_constructed;
gobject_class->dispose = spice_channel_dispose;
@@ -2116,18 +2118,10 @@ gboolean spice_channel_open_fd(SpiceChannel *channel, int fd)
}
/* system or coroutine context */
-static void channel_disconnect(SpiceChannel *channel)
+static void channel_reset(SpiceChannel *channel, gboolean migrating)
{
SpiceChannelPrivate *c = SPICE_CHANNEL_GET_PRIVATE(channel);
- g_return_if_fail(c != NULL);
-
- if (c->state == SPICE_CHANNEL_STATE_UNCONNECTED) {
- return;
- }
-
- c->has_error = TRUE; /* break the loop */
-
if (c->connect_delayed_id) {
g_source_remove(c->connect_delayed_id);
c->connect_delayed_id = 0;
@@ -2174,9 +2168,32 @@ static void channel_disconnect(SpiceChannel *channel)
g_array_set_size(c->caps, 0);
/* Restore our default capabilities in case the channel gets re-used */
spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION);
+}
+
+/* system or coroutine context */
+G_GNUC_INTERNAL
+void spice_channel_reset(SpiceChannel *channel, gboolean migrating)
+{
+ SPICE_CHANNEL_GET_CLASS(channel)->channel_reset(channel, migrating);
+}
+
+/* system or coroutine context */
+static void channel_disconnect(SpiceChannel *channel)
+{
+ SpiceChannelPrivate *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+ g_return_if_fail(c != NULL);
+
+ if (c->state == SPICE_CHANNEL_STATE_UNCONNECTED)
+ return;
+
+ c->has_error = TRUE; /* break the loop */
+
+ spice_channel_reset(channel, FALSE);
if (c->state == SPICE_CHANNEL_STATE_READY)
emit_main_context(channel, SPICE_CHANNEL_EVENT, SPICE_CHANNEL_CLOSED);
+
g_return_if_fail(SPICE_IS_CHANNEL(channel));
c->state = SPICE_CHANNEL_STATE_UNCONNECTED;
}
diff --git a/gtk/spice-channel.h b/gtk/spice-channel.h
index 793c5d6..2a8e7e7 100644
--- a/gtk/spice-channel.h
+++ b/gtk/spice-channel.h
@@ -87,11 +87,13 @@ struct _SpiceChannelClass
/*< private >*/
/* virtual method, any context */
void (*channel_disconnect)(SpiceChannel *channel);
+
+ void (*channel_reset)(SpiceChannel *channel, gboolean migrating);
/*
* If adding fields to this struct, remove corresponding
* amount of padding to avoid changing overall struct size
*/
- gchar _spice_reserved[SPICE_RESERVED_PADDING - 4 * sizeof(void*)];
+ gchar _spice_reserved[SPICE_RESERVED_PADDING - 5 * sizeof(void*)];
};
GType spice_channel_get_type(void);
commit b64b432e72a8b72c8f551bb3e98b4ad422dcb83d
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Sun Dec 18 17:41:09 2011 +0100
gio-coroutine: add a few more run-time checks
diff --git a/gtk/gio-coroutine.c b/gtk/gio-coroutine.c
index ff37272..7470b43 100644
--- a/gtk/gio-coroutine.c
+++ b/gtk/gio-coroutine.c
@@ -158,6 +158,7 @@ struct signal_data
gpointer params;
GSignalEmitMainFunc func;
const char *debug_info;
+ gboolean notified;
};
static gboolean emit_main_context(gpointer opaque)
@@ -165,6 +166,8 @@ static gboolean emit_main_context(gpointer opaque)
struct signal_data *signal = opaque;
signal->func(signal->object, signal->signum, signal->params);
+ signal->notified = TRUE;
+
coroutine_yieldto(signal->caller, NULL);
return FALSE;
@@ -179,12 +182,15 @@ void g_signal_emit_main_context(GObject *object,
{
struct signal_data data;
+ g_return_if_fail(coroutine_self()->caller);
+
data.object = object;
data.caller = coroutine_self();
data.signum = signum;
data.params = params;
data.func = emit_main_func;
data.debug_info = debug_info;
+ data.notified = FALSE;
g_idle_add(emit_main_context, &data);
/* This switches to the system coroutine context, lets
@@ -194,6 +200,7 @@ void g_signal_emit_main_context(GObject *object,
* an idle function involved
*/
coroutine_yield(NULL);
+ g_warn_if_fail(data.notified);
}
static gboolean notify_main_context(gpointer opaque)
@@ -201,6 +208,8 @@ static gboolean notify_main_context(gpointer opaque)
struct signal_data *signal = opaque;
g_object_notify(signal->object, signal->params);
+ signal->notified = TRUE;
+
coroutine_yieldto(signal->caller, NULL);
return FALSE;
@@ -212,9 +221,12 @@ void g_object_notify_main_context(GObject *object,
{
struct signal_data data;
+ g_return_if_fail(coroutine_self()->caller);
+
data.object = object;
data.caller = coroutine_self();
data.params = (gpointer)property_name;
+ data.notified = FALSE;
g_idle_add(notify_main_context, &data);
@@ -225,4 +237,5 @@ void g_object_notify_main_context(GObject *object,
* an idle function involved
*/
coroutine_yield(NULL);
+ g_warn_if_fail(data.notified);
}
commit 234a8fcce2a339f86dd9247c78f04dea85e8ad87
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date: Sun Dec 18 17:39:06 2011 +0100
Update protocol and messages for semi-seamless migration
diff --git a/common/messages.h b/common/messages.h
index 556496b..4a69c35 100644
--- a/common/messages.h
+++ b/common/messages.h
@@ -73,6 +73,8 @@ typedef struct SpiceMsgMainMigrationBegin {
uint16_t pub_key_type;
uint32_t pub_key_size;
uint8_t *pub_key_data;
+ uint32_t cert_subject_size;
+ uint8_t *cert_subject_data;
} SpiceMsgMainMigrationBegin;
typedef struct SpiceMsgMainMigrationSwitchHost {
diff --git a/spice.proto b/spice.proto
index 748ae95..28a9435 100644
--- a/spice.proto
+++ b/spice.proto
@@ -167,9 +167,8 @@ channel MainChannel : BaseChannel {
uint16 sport;
uint32 host_size;
uint8 *host_data[host_size] @zero_terminated @marshall @nonnull;
- pubkey_type pub_key_type;
- uint32 pub_key_size;
- uint8 *pub_key_data[pub_key_size] @zero_terminated @marshall @nonnull;
+ uint32 cert_subject_size;
+ uint8 *cert_subject_data[cert_subject_size] @zero_terminated @marshall;
} @ctype(SpiceMsgMainMigrationBegin) migrate_begin = 101;
Empty migrate_cancel;
@@ -220,6 +219,8 @@ channel MainChannel : BaseChannel {
uint8 *cert_subject_data[cert_subject_size] @zero_terminated @marshall;
} @ctype(SpiceMsgMainMigrationSwitchHost) migrate_switch_host;
+ Empty migrate_end;
+
client:
message {
uint64 cache_size;
@@ -244,6 +245,8 @@ channel MainChannel : BaseChannel {
message {
uint32 num_tokens;
} @ctype(SpiceMsgcMainAgentTokens) agent_token;
+
+ Empty migrate_end;
};
enum8 clip_type {
commit 4b8102e9fedc598334f49edfecfe7ae3f035d425
Author: Marc-André Lureau <marcandre.lureau at gmail.com>
Date: Wed Dec 14 21:59:01 2011 +0100
windows: initialize co->ret to not randomly release fiber
diff --git a/gtk/coroutine_winfibers.c b/gtk/coroutine_winfibers.c
index a22da3b..a4cd14b 100644
--- a/gtk/coroutine_winfibers.c
+++ b/gtk/coroutine_winfibers.c
@@ -60,6 +60,7 @@ int coroutine_init(struct coroutine *co)
}
co->fiber = CreateFiber(0, &coroutine_trampoline, co);
+ co->ret = 0;
if (co->fiber == NULL)
return -1;
More information about the Spice-commits
mailing list