[Spice-devel] [PATCH spice-gtk] spice-channel: Allow calling spice_msg_out_send from any context

Hans de Goede hdegoede at redhat.com
Tue Jan 17 04:35:21 PST 2012


spice_msg_out can be not only called from system context and usb event
handling thread context, but also from co-routine context. Calling from
co-routine context happens when a response gets send synchronously from
the handle_msg handler for a certain received packet. This happens with
certain usbredir commands.

This triggers the following assert in the coroutine code:
"GSpice-CRITICAL **: g_coroutine_wakeup: assertion `coroutine !=
 g_coroutine_self()' failed"

This patch fixes this by making spice_msg_out_send callable from any
context and add the same time changing the code to not do unnecessary
wakeups.

Signed-off-by: Hans de Goede <hdegoede at redhat.com>
---
 gtk/spice-channel-priv.h |    2 +-
 gtk/spice-channel.c      |   52 ++++++++++++++++++++++++++++++++++-----------
 2 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
index ebdc5ce..5cd7ddb 100644
--- a/gtk/spice-channel-priv.h
+++ b/gtk/spice-channel-priv.h
@@ -102,7 +102,7 @@ struct _SpiceChannelPrivate {
     GQueue                      xmit_queue;
     gboolean                    xmit_queue_blocked;
     GStaticMutex                xmit_queue_lock;
-    GThread                     *main_thread;
+    guint                       xmit_queue_wakeup_id;
 
     char                        name[16];
     enum spice_channel_state    state;
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
index 83cd344..bdfb02b 100644
--- a/gtk/spice-channel.c
+++ b/gtk/spice-channel.c
@@ -110,7 +110,6 @@ static void spice_channel_init(SpiceChannel *channel)
     spice_channel_set_common_capability(channel, SPICE_COMMON_CAP_MINI_HEADER);
     g_queue_init(&c->xmit_queue);
     g_static_mutex_init(&c->xmit_queue_lock);
-    c->main_thread = g_thread_self();
 }
 
 static void spice_channel_constructed(GObject *gobject)
@@ -649,14 +648,32 @@ void spice_msg_out_unref(SpiceMsgOut *out)
 static gboolean spice_channel_idle_wakeup(gpointer user_data)
 {
     SpiceChannel *channel = SPICE_CHANNEL(user_data);
+    SpiceChannelPrivate *c = channel->priv;
+
+    /*
+     * Note:
+     *
+     * - This must be done before the wakeup as that may eventually
+     *   call channel_reset() which checks this.
+     * - The lock calls are really necessary, this fixes the following race:
+     *   1) usb-event-thread calls spice_msg_out_send()
+     *   2) spice_msg_out_send calls g_timeout_add_full(...)
+     *   3) we run, set xmit_queue_wakeup_id to 0
+     *   4) spice_msg_out_send stores the result of g_timeout_add_full() in
+     *      xmit_queue_wakeup_id, overwriting the 0 we just stored
+     *   5) xmit_queue_wakeup_id now says there is a wakeup pending which is
+     *      false
+     */
+    g_static_mutex_lock(&c->xmit_queue_lock);
+    c->xmit_queue_wakeup_id = 0;
+    g_static_mutex_unlock(&c->xmit_queue_lock);
 
     spice_channel_wakeup(channel, FALSE);
-    g_object_unref(channel);
 
     return FALSE;
 }
 
-/* system context */
+/* any context (system/co-routine/usb-event-thread) */
 G_GNUC_INTERNAL
 void spice_msg_out_send(SpiceMsgOut *out)
 {
@@ -664,17 +681,23 @@ void spice_msg_out_send(SpiceMsgOut *out)
     g_return_if_fail(out->channel != NULL);
 
     g_static_mutex_lock(&out->channel->priv->xmit_queue_lock);
-    if (!out->channel->priv->xmit_queue_blocked)
+    if (!out->channel->priv->xmit_queue_blocked) {
+        gboolean was_empty;
+
+        was_empty = g_queue_is_empty(&out->channel->priv->xmit_queue);
         g_queue_push_tail(&out->channel->priv->xmit_queue, out);
-    g_static_mutex_unlock(&out->channel->priv->xmit_queue_lock);
 
-    /* TODO: we currently flush/wakeup immediately all buffered messages */
-    if (g_thread_self() != out->channel->priv->main_thread)
-        /* We use g_timeout_add_full so that can specify the priority */
-        g_timeout_add_full(G_PRIORITY_HIGH, 0, spice_channel_idle_wakeup,
-                           g_object_ref(out->channel), NULL);
-    else
-        spice_channel_wakeup(out->channel, FALSE);
+        /* One wakeup is enough to empty the entire queue -> only do a wakeup
+           if the queue was empty, and there isn't one pending already. */
+        if (was_empty && !out->channel->priv->xmit_queue_wakeup_id) {
+            out->channel->priv->xmit_queue_wakeup_id =
+                /* Use g_timeout_add_full so that can specify the priority */
+                g_timeout_add_full(G_PRIORITY_HIGH, 0,
+                                   spice_channel_idle_wakeup,
+                                   out->channel, NULL);
+        }
+    }
+    g_static_mutex_unlock(&out->channel->priv->xmit_queue_lock);
 }
 
 /* coroutine context */
@@ -1688,7 +1711,6 @@ error:
 }
 
 /* system context */
-/* TODO: we currently flush/wakeup immediately all buffered messages */
 G_GNUC_INTERNAL
 void spice_channel_wakeup(SpiceChannel *channel, gboolean cancel)
 {
@@ -2344,6 +2366,10 @@ static void channel_reset(SpiceChannel *channel, gboolean migrating)
     c->xmit_queue_blocked = TRUE; /* Disallow queuing new messages */
     g_queue_foreach(&c->xmit_queue, (GFunc)spice_msg_out_unref, NULL);
     g_queue_clear(&c->xmit_queue);
+    if (c->xmit_queue_wakeup_id) {
+        g_source_remove(c->xmit_queue_wakeup_id);
+        c->xmit_queue_wakeup_id = 0;
+    }
     g_static_mutex_unlock(&c->xmit_queue_lock);
 
     g_array_set_size(c->remote_common_caps, 0);
-- 
1.7.7.4



More information about the Spice-devel mailing list