Add SoupSession:use-thread-context
authorDan Winship <danw@gnome.org>
Mon, 3 Oct 2011 18:58:06 +0000 (14:58 -0400)
committerDan Winship <danw@gnome.org>
Tue, 8 Nov 2011 17:49:32 +0000 (12:49 -0500)
Add a SoupSession flag telling it to use
g_main_context_get_thread_default() on a per-message basis, rather
than using a single GMainContext for everything. In the simple case,
this is just more glib-like API. In the more complicated case, it
allows synchronously sending one or more messages on a
SoupSessionAsync without running the main GMainLoop.

https://bugs.webkit.org/show_bug.cgi?id=68238

libsoup/soup-connection.c
libsoup/soup-connection.h
libsoup/soup-message-queue.c
libsoup/soup-message-queue.h
libsoup/soup-session-async.c
libsoup/soup-session.c
libsoup/soup-session.h
libsoup/soup-socket.c
libsoup/soup-socket.h
tests/context-test.c

index d82832f..3e7f0ee 100644 (file)
@@ -37,7 +37,8 @@ typedef struct {
        GTlsDatabase *tlsdb;
        gboolean     ssl, ssl_strict, ssl_fallback;
 
-       GMainContext      *async_context;
+       GMainContext *async_context;
+       gboolean      use_thread_context;
 
        SoupMessageQueueItem *cur_item;
        SoupConnectionState state;
@@ -67,6 +68,7 @@ enum {
        PROP_SSL_STRICT,
        PROP_SSL_FALLBACK,
        PROP_ASYNC_CONTEXT,
+       PROP_USE_THREAD_CONTEXT,
        PROP_TIMEOUT,
        PROP_IDLE_TIMEOUT,
        PROP_STATE,
@@ -214,6 +216,13 @@ soup_connection_class_init (SoupConnectionClass *connection_class)
                                      "GMainContext to dispatch this connection's async I/O in",
                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        g_object_class_install_property (
+               object_class, PROP_USE_THREAD_CONTEXT,
+               g_param_spec_boolean (SOUP_CONNECTION_USE_THREAD_CONTEXT,
+                                     "Use thread context",
+                                     "Use g_main_context_get_thread_default",
+                                     FALSE,
+                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (
                object_class, PROP_TIMEOUT,
                g_param_spec_uint (SOUP_CONNECTION_TIMEOUT,
                                   "Timeout value",
@@ -295,6 +304,9 @@ set_property (GObject *object, guint prop_id,
                if (priv->async_context)
                        g_main_context_ref (priv->async_context);
                break;
+       case PROP_USE_THREAD_CONTEXT:
+               priv->use_thread_context = g_value_get_boolean (value);
+               break;
        case PROP_TIMEOUT:
                priv->io_timeout = g_value_get_uint (value);
                break;
@@ -341,6 +353,9 @@ get_property (GObject *object, guint prop_id,
        case PROP_ASYNC_CONTEXT:
                g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL);
                break;
+       case PROP_USE_THREAD_CONTEXT:
+               g_value_set_boolean (value, priv->use_thread_context);
+               break;
        case PROP_TIMEOUT:
                g_value_set_uint (value, priv->io_timeout);
                break;
@@ -533,6 +548,7 @@ soup_connection_connect_async (SoupConnection *conn,
                                 SOUP_SOCKET_SSL_STRICT, priv->ssl_strict,
                                 SOUP_SOCKET_SSL_FALLBACK, priv->ssl_fallback,
                                 SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context,
+                                SOUP_SOCKET_USE_THREAD_CONTEXT, priv->use_thread_context,
                                 SOUP_SOCKET_TIMEOUT, priv->io_timeout,
                                 "clean-dispose", TRUE,
                                 NULL);
@@ -672,6 +688,7 @@ soup_connection_start_ssl_async (SoupConnection   *conn,
        SoupConnectionPrivate *priv;
        const char *server_name;
        SoupConnectionAsyncConnectData *data;
+       GMainContext *async_context;
 
        g_return_if_fail (SOUP_IS_CONNECTION (conn));
        priv = SOUP_CONNECTION_GET_PRIVATE (conn);
@@ -681,12 +698,17 @@ soup_connection_start_ssl_async (SoupConnection   *conn,
        data->callback = callback;
        data->callback_data = user_data;
 
+       if (priv->use_thread_context)
+               async_context = g_main_context_get_thread_default ();
+       else
+               async_context = priv->async_context;
+
        server_name = soup_address_get_name (priv->tunnel_addr ?
                                             priv->tunnel_addr :
                                             priv->remote_addr);
        if (!soup_socket_start_proxy_ssl (priv->socket, server_name,
                                          cancellable)) {
-               soup_add_completion (priv->async_context,
+               soup_add_completion (async_context,
                                     idle_start_ssl_completed, data);
                return;
        }
index cd663a8..72e6106 100644 (file)
@@ -47,6 +47,7 @@ typedef void  (*SoupConnectionCallback)        (SoupConnection   *conn,
 #define SOUP_CONNECTION_SSL_STRICT      "ssl-strict"
 #define SOUP_CONNECTION_SSL_FALLBACK    "ssl-fallback"
 #define SOUP_CONNECTION_ASYNC_CONTEXT   "async-context"
+#define SOUP_CONNECTION_USE_THREAD_CONTEXT "use-thread-context"
 #define SOUP_CONNECTION_TIMEOUT         "timeout"
 #define SOUP_CONNECTION_IDLE_TIMEOUT    "idle-timeout"
 #define SOUP_CONNECTION_STATE           "state"
index 59c5c5c..d324597 100644 (file)
@@ -103,6 +103,7 @@ soup_message_queue_append (SoupMessageQueue *queue, SoupMessage *msg,
 
        item = g_slice_new0 (SoupMessageQueueItem);
        item->session = queue->session;
+       item->async_context = soup_session_get_async_context (item->session);
        item->queue = queue;
        item->msg = g_object_ref (msg);
        item->callback = callback;
index 43cb0ae..5fb14c4 100644 (file)
@@ -39,6 +39,7 @@ struct _SoupMessageQueueItem {
        SoupMessage *msg;
        SoupSessionCallback callback;
        gpointer callback_data;
+       GMainContext *async_context;
 
        GCancellable *cancellable;
        SoupAddress *proxy_addr;
index c813931..edb6239 100644 (file)
@@ -47,13 +47,25 @@ static void  auth_required   (SoupSession *session, SoupMessage *msg,
 G_DEFINE_TYPE (SoupSessionAsync, soup_session_async, SOUP_TYPE_SESSION)
 
 typedef struct {
-       GSource *idle_run_queue_source;
+       GHashTable *idle_run_queue_sources;
+
 } SoupSessionAsyncPrivate;
 #define SOUP_SESSION_ASYNC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION_ASYNC, SoupSessionAsyncPrivate))
 
 static void
+destroy_unref_source (gpointer source)
+{
+       g_source_destroy (source);
+       g_source_unref (source);
+}
+
+static void
 soup_session_async_init (SoupSessionAsync *sa)
 {
+       SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (sa);
+
+       priv->idle_run_queue_sources =
+               g_hash_table_new_full (NULL, NULL, NULL, destroy_unref_source);
 }
 
 static void
@@ -61,8 +73,7 @@ finalize (GObject *object)
 {
        SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (object);
 
-       if (priv->idle_run_queue_source)
-               g_source_destroy (priv->idle_run_queue_source);
+       g_hash_table_destroy (priv->idle_run_queue_sources);
 
        G_OBJECT_CLASS (soup_session_async_parent_class)->finalize (object);
 }
@@ -364,6 +375,9 @@ process_queue_item (SoupMessageQueueItem *item,
        SoupSession *session = item->session;
        SoupProxyURIResolver *proxy_resolver;
 
+       if (item->async_context != soup_session_get_async_context (session))
+               return;
+
        do {
                if (item->paused)
                        return;
@@ -470,7 +484,8 @@ idle_run_queue (gpointer sa)
 {
        SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (sa);
 
-       priv->idle_run_queue_source = NULL;
+       g_hash_table_remove (priv->idle_run_queue_sources,
+                            soup_session_get_async_context (sa));
        run_queue (sa);
        return FALSE;
 }
@@ -480,10 +495,13 @@ do_idle_run_queue (SoupSession *session)
 {
        SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (session);
 
-       if (!priv->idle_run_queue_source) {
-               priv->idle_run_queue_source = soup_add_completion (
-                       soup_session_get_async_context (session),
-                       idle_run_queue, session);
+       if (!g_hash_table_lookup (priv->idle_run_queue_sources,
+                                 soup_session_get_async_context (session))) {
+               GMainContext *async_context = soup_session_get_async_context (session);
+               GSource *source = soup_add_completion (async_context, idle_run_queue, session);
+
+               g_hash_table_insert (priv->idle_run_queue_sources,
+                                    async_context, g_source_ref (source));
        }
 }
 
index 7f5ea0e..16b150a 100644 (file)
@@ -101,6 +101,7 @@ typedef struct {
        GMutex *host_lock;
 
        GMainContext *async_context;
+       gboolean use_thread_context;
 
        GResolver *resolver;
 
@@ -156,6 +157,7 @@ enum {
        PROP_TLS_DATABASE,
        PROP_SSL_STRICT,
        PROP_ASYNC_CONTEXT,
+       PROP_USE_THREAD_CONTEXT,
        PROP_TIMEOUT,
        PROP_USER_AGENT,
        PROP_ACCEPT_LANGUAGE,
@@ -653,7 +655,7 @@ soup_session_class_init (SoupSessionClass *session_class)
         *
         * Alias for the #SoupSession:async-context property. (The
         * session's #GMainContext.)
-        **/
+        */
        g_object_class_install_property (
                object_class, PROP_ASYNC_CONTEXT,
                g_param_spec_pointer (SOUP_SESSION_ASYNC_CONTEXT,
@@ -661,6 +663,33 @@ soup_session_class_init (SoupSessionClass *session_class)
                                      "The GMainContext to dispatch async I/O in",
                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        /**
+        * SOUP_SESSION_USE_THREAD_CONTEXT:
+        *
+        * Alias for the #SoupSession:use-thread-context property, qv.
+        *
+        * Since: 2.38
+        */
+       /**
+        * SoupSession:use-thread-context:
+        *
+        * If set, asynchronous operations in this session will run in
+        * whatever the thread-default #GMainContext is at the time
+        * they are started, rather than always occurring in a context
+        * fixed at the session's construction time. "Bookkeeping"
+        * tasks (like expiring idle connections) will happen in the
+        * context that was thread-default at the time the session was
+        * created.
+        *
+        * Since: 2.38
+        */
+       g_object_class_install_property (
+               object_class, PROP_USE_THREAD_CONTEXT,
+               g_param_spec_boolean (SOUP_SESSION_USE_THREAD_CONTEXT,
+                                     "Use thread-default GMainContext",
+                                     "Whether to use thread-default main contexts",
+                                     FALSE,
+                                     G_PARAM_READWRITE));
+       /**
         * SOUP_SESSION_TIMEOUT:
         *
         * Alias for the #SoupSession:timeout property. (The timeout
@@ -1109,6 +1138,16 @@ set_property (GObject *object, guint prop_id,
                if (priv->async_context)
                        g_main_context_ref (priv->async_context);
                break;
+       case PROP_USE_THREAD_CONTEXT:
+               priv->use_thread_context = g_value_get_boolean (value);
+               if (priv->use_thread_context) {
+                       if (priv->async_context)
+                               g_main_context_unref (priv->async_context);
+                       priv->async_context = g_main_context_get_thread_default ();
+                       if (priv->async_context)
+                               g_main_context_ref (priv->async_context);
+               }
+               break;
        case PROP_TIMEOUT:
                priv->io_timeout = g_value_get_uint (value);
                break;
@@ -1216,6 +1255,9 @@ get_property (GObject *object, guint prop_id,
        case PROP_ASYNC_CONTEXT:
                g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL);
                break;
+       case PROP_USE_THREAD_CONTEXT:
+               g_value_set_boolean (value, priv->use_thread_context);
+               break;
        case PROP_TIMEOUT:
                g_value_set_uint (value, priv->io_timeout);
                break;
@@ -1294,6 +1336,9 @@ uri_is_https (SoupSessionPrivate *priv, SoupURI *uri)
  * context, so you will need to ref it yourself if you want it to
  * outlive its session.
  *
+ * If #SoupSession:use-thread-context is true, this will return the
+ * current thread-default main context.
+ *
  * Return value: (transfer none): @session's #GMainContext, which may
  * be %NULL
  **/
@@ -1305,7 +1350,10 @@ soup_session_get_async_context (SoupSession *session)
        g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
        priv = SOUP_SESSION_GET_PRIVATE (session);
 
-       return priv->async_context;
+       if (priv->use_thread_context)
+               return g_main_context_get_thread_default ();
+       else
+               return priv->async_context;
 }
 
 /* Hosts */
@@ -1784,6 +1832,7 @@ soup_session_get_connection (SoupSession *session,
                SOUP_CONNECTION_SSL_CREDENTIALS, priv->tlsdb,
                SOUP_CONNECTION_SSL_STRICT, (priv->tlsdb != NULL) && priv->ssl_strict,
                SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context,
+               SOUP_CONNECTION_USE_THREAD_CONTEXT, priv->use_thread_context,
                SOUP_CONNECTION_TIMEOUT, priv->io_timeout,
                SOUP_CONNECTION_IDLE_TIMEOUT, priv->idle_timeout,
                SOUP_CONNECTION_SSL_FALLBACK, host->ssl_fallback,
@@ -2234,7 +2283,8 @@ soup_session_prepare_for_uri (SoupSession *session, SoupURI *uri)
        addr = g_object_ref (host->addr);
        g_mutex_unlock (priv->host_lock);
 
-       soup_address_resolve_async (addr, priv->async_context,
+       soup_address_resolve_async (addr,
+                                   soup_session_get_async_context (session),
                                    NULL, NULL, NULL);
        g_object_unref (addr);
 }
index 349cfdb..dd09599 100644 (file)
@@ -68,6 +68,7 @@ GType soup_session_get_type (void);
 #define SOUP_SESSION_TLS_DATABASE           "tls-database"
 #define SOUP_SESSION_SSL_STRICT             "ssl-strict"
 #define SOUP_SESSION_ASYNC_CONTEXT          "async-context"
+#define SOUP_SESSION_USE_THREAD_CONTEXT     "use-thread-context"
 #define SOUP_SESSION_TIMEOUT                "timeout"
 #define SOUP_SESSION_USER_AGENT             "user-agent"
 #define SOUP_SESSION_ACCEPT_LANGUAGE        "accept-language"
index ee83a3f..e1f7434 100644 (file)
@@ -55,6 +55,7 @@ enum {
        PROP_SSL_STRICT,
        PROP_SSL_FALLBACK,
        PROP_ASYNC_CONTEXT,
+       PROP_USE_THREAD_CONTEXT,
        PROP_TIMEOUT,
        PROP_TRUSTED_CERTIFICATE,
        PROP_CLEAN_DISPOSE,
@@ -78,6 +79,7 @@ typedef struct {
        guint ssl_strict:1;
        guint ssl_fallback:1;
        guint clean_dispose:1;
+       guint use_thread_context:1;
        gpointer ssl_creds;
 
        GMainContext   *async_context;
@@ -406,6 +408,29 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
        /**
+        * SOUP_SOCKET_USE_THREAD_CONTEXT:
+        *
+        * Alias for the #SoupSocket:use-thread-context property. (Use
+        * g_main_context_get_thread_default())
+        *
+        * Since: 2.36.1
+        */
+       /**
+        * SoupSocket:use-thread-context:
+        *
+        * Use g_main_context_get_thread_default().
+        *
+        * Since: 2.36.1
+        */
+       g_object_class_install_property (
+               object_class, PROP_USE_THREAD_CONTEXT,
+               g_param_spec_boolean (SOUP_SOCKET_USE_THREAD_CONTEXT,
+                                     "Use thread context",
+                                     "Use g_main_context_get_thread_default",
+                                     FALSE,
+                                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+       /**
         * SOUP_SOCKET_TIMEOUT:
         *
         * Alias for the #SoupSocket:timeout property. (The timeout
@@ -509,6 +534,9 @@ set_property (GObject *object, guint prop_id,
                if (priv->async_context)
                        g_main_context_ref (priv->async_context);
                break;
+       case PROP_USE_THREAD_CONTEXT:
+               priv->use_thread_context = g_value_get_boolean (value);
+               break;
        case PROP_TIMEOUT:
                priv->timeout = g_value_get_uint (value);
                if (priv->conn)
@@ -557,6 +585,9 @@ get_property (GObject *object, guint prop_id,
        case PROP_ASYNC_CONTEXT:
                g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL);
                break;
+       case PROP_USE_THREAD_CONTEXT:
+               g_value_set_boolean (value, priv->use_thread_context);
+               break;
        case PROP_TIMEOUT:
                g_value_set_uint (value, priv->timeout);
                break;
@@ -648,7 +679,7 @@ async_connected (GObject *client, GAsyncResult *result, gpointer data)
        GSocketConnection *conn;
        guint status;
 
-       if (priv->async_context)
+       if (priv->async_context && !priv->use_thread_context)
                g_main_context_pop_thread_default (priv->async_context);
 
        conn = g_socket_client_connect_finish (G_SOCKET_CLIENT (client),
@@ -694,7 +725,7 @@ soup_socket_connect_async (SoupSocket *sock, GCancellable *cancellable,
 
        priv->connect_cancel = cancellable ? g_object_ref (cancellable) : g_cancellable_new ();
 
-       if (priv->async_context)
+       if (priv->async_context && !priv->use_thread_context)
                g_main_context_push_thread_default (priv->async_context);
 
        client = g_socket_client_new ();
@@ -765,13 +796,20 @@ soup_socket_create_watch (SoupSocketPrivate *priv, GIOCondition cond,
                          GCancellable *cancellable)
 {
        GSource *watch;
+       GMainContext *async_context;
 
        if (cond == G_IO_IN)
                watch = g_pollable_input_stream_create_source (priv->istream, cancellable);
        else
                watch = g_pollable_output_stream_create_source (priv->ostream, cancellable);
        g_source_set_callback (watch, (GSourceFunc)callback, user_data, NULL);
-       g_source_attach (watch, priv->async_context);
+
+       if (priv->use_thread_context)
+               async_context = g_main_context_get_thread_default ();
+       else
+               async_context = priv->async_context;
+
+       g_source_attach (watch, async_context);
        g_source_unref (watch);
 
        return watch;
@@ -793,6 +831,7 @@ listen_watch (GObject *pollable, gpointer data)
        new_priv->gsock = new_gsock;
        if (priv->async_context)
                new_priv->async_context = g_main_context_ref (priv->async_context);
+       new_priv->use_thread_context = priv->use_thread_context;
        new_priv->non_blocking = priv->non_blocking;
        new_priv->is_server = TRUE;
        new_priv->ssl = priv->ssl;
@@ -1018,7 +1057,7 @@ handshake_async_ready (GObject *source, GAsyncResult *result, gpointer user_data
        GError *error = NULL;
        guint status;
 
-       if (priv->async_context)
+       if (priv->async_context && !priv->use_thread_context)
                g_main_context_pop_thread_default (priv->async_context);
 
        if (g_tls_connection_handshake_finish (G_TLS_CONNECTION (priv->conn),
@@ -1052,7 +1091,7 @@ soup_socket_handshake_async (SoupSocket         *sock,
        data->callback = callback;
        data->user_data = user_data;
 
-       if (priv->async_context)
+       if (priv->async_context && !priv->use_thread_context)
                g_main_context_push_thread_default (priv->async_context);
        g_tls_connection_handshake_async (G_TLS_CONNECTION (priv->conn),
                                          G_PRIORITY_DEFAULT,
index 4d1550f..dc6b59c 100644 (file)
@@ -48,6 +48,7 @@ typedef struct {
 #define SOUP_SOCKET_SSL_FALLBACK        "ssl-fallback"
 #define SOUP_SOCKET_TRUSTED_CERTIFICATE "trusted-certificate"
 #define SOUP_SOCKET_ASYNC_CONTEXT       "async-context"
+#define SOUP_SOCKET_USE_THREAD_CONTEXT  "use-thread-context"
 #define SOUP_SOCKET_TIMEOUT             "timeout"
 #define SOUP_SOCKET_TLS_CERTIFICATE     "tls-certificate"
 #define SOUP_SOCKET_TLS_ERRORS          "tls-errors"
index fe9af31..b72d200 100644 (file)
@@ -99,34 +99,37 @@ static gpointer test1_thread (gpointer user_data);
 
 static GCond *test1_cond;
 static GMutex *test1_mutex;
+static GMainLoop *test1_loop;
 
 static void
-do_test1 (void)
+do_test1 (int n, gboolean use_thread_context)
 {
-       GMainLoop *loop;
-
-       debug_printf (1, "Test 1: blocking the main thread does not block other thread\n");
+       debug_printf (1, "\nTest %d: blocking the main thread does not block other thread\n", n);
+       if (use_thread_context)
+               debug_printf (1, "(Using g_main_context_push_thread_default())\n");
+       else
+               debug_printf (1, "(Using SOUP_SESSION_ASYNC_CONTEXT)\n");
 
        test1_cond = g_cond_new ();
        test1_mutex = g_mutex_new ();
 
-       loop = g_main_loop_new (NULL, FALSE);
-       g_idle_add (idle_start_test1_thread, loop);
-       g_main_loop_run (loop);
-       g_main_loop_unref (loop);
+       test1_loop = g_main_loop_new (NULL, FALSE);
+       g_idle_add (idle_start_test1_thread, GINT_TO_POINTER (use_thread_context));
+       g_main_loop_run (test1_loop);
+       g_main_loop_unref (test1_loop);
 
        g_mutex_free (test1_mutex);
        g_cond_free (test1_cond);
 }
 
 static gboolean
-idle_start_test1_thread (gpointer loop)
+idle_start_test1_thread (gpointer use_thread_context)
 {
        GTimeVal time;
        GThread *thread;
 
        g_mutex_lock (test1_mutex);
-       thread = g_thread_create (test1_thread, base_uri, TRUE, NULL);
+       thread = g_thread_create (test1_thread, use_thread_context, TRUE, NULL);
 
        g_get_current_time (&time);
        time.tv_sec += 5;
@@ -138,7 +141,7 @@ idle_start_test1_thread (gpointer loop)
        }
 
        g_mutex_unlock (test1_mutex);
-       g_main_loop_quit (loop);
+       g_main_loop_quit (test1_loop);
        return FALSE;
 }
 
@@ -149,7 +152,7 @@ test1_finished (SoupSession *session, SoupMessage *msg, gpointer loop)
 }
 
 static gpointer
-test1_thread (gpointer user_data)
+test1_thread (gpointer use_thread_context)
 {
        SoupSession *session;
        GMainContext *async_context;
@@ -162,10 +165,16 @@ test1_thread (gpointer user_data)
        g_mutex_unlock (test1_mutex);
 
        async_context = g_main_context_new ();
-       session = soup_test_session_new (
-               SOUP_TYPE_SESSION_ASYNC,
-               SOUP_SESSION_ASYNC_CONTEXT, async_context,
-               NULL);
+       if (use_thread_context) {
+               g_main_context_push_thread_default (async_context);
+               session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+                                                SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+                                                NULL);
+       } else {
+               session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+                                                SOUP_SESSION_ASYNC_CONTEXT, async_context,
+                                                NULL);
+       }
        g_main_context_unref (async_context);
 
        uri = g_build_filename (base_uri, "slow", NULL);
@@ -198,6 +207,9 @@ test1_thread (gpointer user_data)
        g_free (uri);
 
        g_cond_signal (test1_cond);
+
+       if (use_thread_context)
+               g_main_context_pop_thread_default (async_context);
        return NULL;
 }
 
@@ -208,7 +220,7 @@ test1_thread (gpointer user_data)
 static gboolean idle_test2_fail (gpointer user_data);
 
 static void
-do_test2 (void)
+do_test2 (int n, gboolean use_thread_context)
 {
        guint idle;
        GMainContext *async_context;
@@ -216,15 +228,25 @@ do_test2 (void)
        char *uri;
        SoupMessage *msg;
 
-       debug_printf (1, "Test 2: a session with its own context is independent of the main loop.\n");
+       debug_printf (1, "\nTest %d: a session with its own context is independent of the main loop.\n", n);
+       if (use_thread_context)
+               debug_printf (1, "(Using g_main_context_push_thread_default())\n");
+       else
+               debug_printf (1, "(Using SOUP_SESSION_ASYNC_CONTEXT)\n");
 
        idle = g_idle_add_full (G_PRIORITY_HIGH, idle_test2_fail, NULL, NULL);
 
        async_context = g_main_context_new ();
-       session = soup_test_session_new (
-               SOUP_TYPE_SESSION_ASYNC,
-               SOUP_SESSION_ASYNC_CONTEXT, async_context,
-               NULL);
+       if (use_thread_context) {
+               g_main_context_push_thread_default (async_context);
+               session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+                                                SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+                                                NULL);
+       } else {
+               session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+                                                SOUP_SESSION_ASYNC_CONTEXT, async_context,
+                                                NULL);
+       }
        g_main_context_unref (async_context);
 
        uri = g_build_filename (base_uri, "slow", NULL);
@@ -243,6 +265,9 @@ do_test2 (void)
        g_free (uri);
 
        g_source_remove (idle);
+
+       if (use_thread_context)
+               g_main_context_pop_thread_default (async_context);
 }
 
 static gboolean
@@ -253,6 +278,110 @@ idle_test2_fail (gpointer user_data)
        return FALSE;
 }
 
+static void
+multi_request_started (SoupSession *session, SoupMessage *msg,
+                      SoupSocket *socket, gpointer user_data)
+{
+       g_object_set_data (G_OBJECT (msg), "started", GUINT_TO_POINTER (TRUE));
+}
+
+static void
+msg1_got_headers (SoupMessage *msg, gpointer user_data)
+{
+       GMainLoop *loop = user_data;
+
+       g_main_loop_quit (loop);
+}
+
+static void
+multi_msg_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+       GMainLoop *loop = user_data;
+
+       g_object_set_data (G_OBJECT (msg), "finished", GUINT_TO_POINTER (TRUE));
+       g_main_loop_quit (loop);
+}
+
+static void
+do_multicontext_test (int n)
+{
+       SoupSession *session;
+       SoupMessage *msg1, *msg2;
+       GMainContext *context1, *context2;
+       GMainLoop *loop1, *loop2;
+
+       debug_printf (1, "\nTest %d: Using multiple async contexts\n", n);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+                                        SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+                                        NULL);
+       g_signal_connect (session, "request-started",
+                         G_CALLBACK (multi_request_started), NULL);
+
+       context1 = g_main_context_new ();
+       loop1 = g_main_loop_new (context1, FALSE);
+       context2 = g_main_context_new ();
+       loop2 = g_main_loop_new (context2, FALSE);
+
+       g_main_context_push_thread_default (context1);
+       msg1 = soup_message_new ("GET", base_uri);
+       g_object_ref (msg1);
+       soup_session_queue_message (session, msg1, multi_msg_finished, loop1);
+       g_signal_connect (msg1, "got-headers",
+                         G_CALLBACK (msg1_got_headers), loop1);
+       g_object_set_data (G_OBJECT (msg1), "session", session);
+       g_main_context_pop_thread_default (context1);
+
+       g_main_context_push_thread_default (context2);
+       msg2 = soup_message_new ("GET", base_uri);
+       g_object_ref (msg2);
+       soup_session_queue_message (session, msg2, multi_msg_finished, loop2);
+       g_main_context_pop_thread_default (context2);
+
+       g_main_context_push_thread_default (context1);
+       g_main_loop_run (loop1);
+       g_main_context_pop_thread_default (context1);
+
+       if (!g_object_get_data (G_OBJECT (msg1), "started")) {
+               debug_printf (1, "  msg1 not started??\n");
+               errors++;
+       }
+       if (g_object_get_data (G_OBJECT (msg2), "started")) {
+               debug_printf (1, "  msg2 started while loop1 was running!\n");
+               errors++;
+       }
+
+       g_main_context_push_thread_default (context2);
+       g_main_loop_run (loop2);
+       g_main_context_pop_thread_default (context2);
+
+       if (g_object_get_data (G_OBJECT (msg1), "finished")) {
+               debug_printf (1, "  msg1 finished while loop2 was running!\n");
+               errors++;
+       }
+       if (!g_object_get_data (G_OBJECT (msg2), "finished")) {
+               debug_printf (1, "  msg2 not finished??\n");
+               errors++;
+       }
+
+       g_main_context_push_thread_default (context1);
+       g_main_loop_run (loop1);
+       g_main_context_pop_thread_default (context1);
+
+       if (!g_object_get_data (G_OBJECT (msg1), "finished")) {
+               debug_printf (1, "  msg1 not finished??\n");
+               errors++;
+       }
+
+       g_main_loop_unref (loop1);
+       g_main_loop_unref (loop2);
+       g_main_context_unref (context1);
+       g_main_context_unref (context2);
+       g_object_unref (msg1);
+       g_object_unref (msg2);
+
+       soup_test_session_abort_unref (session);
+}
 
 int
 main (int argc, char **argv)
@@ -266,8 +395,11 @@ main (int argc, char **argv)
        base_uri = g_strdup_printf ("http://127.0.0.1:%u/",
                                    soup_server_get_port (server));
 
-       do_test1 ();
-       do_test2 ();
+       do_test1 (1, FALSE);
+       do_test1 (2, TRUE);
+       do_test2 (3, FALSE);
+       do_test2 (4, TRUE);
+       do_multicontext_test (5);
 
        g_free (base_uri);
        soup_test_server_quit_unref (server);