[libsoup/carlosgc/thread-safe: 10/21] tests: add test to check requests from multiple threads




commit 30bc2f015433b65aea4c86495f4e1e627edf0fec
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Wed Apr 20 15:03:58 2022 +0200

    tests: add test to check requests from multiple threads

 tests/meson.build        |   1 +
 tests/multithread-test.c | 551 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 552 insertions(+)
---
diff --git a/tests/meson.build b/tests/meson.build
index 7851e571..bd76bab9 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -84,6 +84,7 @@ tests = [
   {'name': 'logger'},
   {'name': 'misc'},
   {'name': 'multipart'},
+  {'name': 'multithread'},
   {'name': 'no-ssl'},
   {'name': 'ntlm'},
   {'name': 'redirect'},
diff --git a/tests/multithread-test.c b/tests/multithread-test.c
new file mode 100644
index 00000000..b6936391
--- /dev/null
+++ b/tests/multithread-test.c
@@ -0,0 +1,551 @@
+/*
+ * Copyright 2022 Igalia S.L.
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+#include "test-utils.h"
+
+#include "soup-connection.h"
+#include "soup-message-private.h"
+
+static GUri *base_uri;
+
+typedef enum {
+        BASIC_SYNC = 1 << 0,
+        BASIC_SSL = 1 << 1,
+        BASIC_PROXY = 1 << 2,
+        BASIC_HTTP2 = 1 << 3,
+        BASIC_MAX_CONNS = 1 << 4,
+        BASIC_NO_MAIN_THREAD = 1 << 5
+} BasicTestFlags;
+
+typedef struct {
+        SoupSession *session;
+        BasicTestFlags flags;
+} Test;
+
+#define HTTPS_SERVER "https://127.0.0.1:47525";
+#define HTTP_PROXY   "http://127.0.0.1:47526";
+
+static void
+test_setup (Test *test, gconstpointer data)
+{
+        test->flags = GPOINTER_TO_UINT (data);
+        if (test->flags & BASIC_MAX_CONNS)
+                test->session = soup_test_session_new ("max-conns", 1, NULL);
+        else
+                test->session = soup_test_session_new (NULL);
+}
+
+static void
+test_teardown (Test *test, gconstpointer data)
+{
+        soup_test_session_abort_unref (test->session);
+        while (g_main_context_pending (NULL))
+                g_main_context_iteration (NULL, FALSE);
+}
+
+static void
+msg_signal_check_context (SoupMessage  *msg,
+                          GMainContext *context)
+{
+        g_assert_true (g_object_get_data (G_OBJECT (msg), "thread-context") == context);
+}
+
+static void
+connect_message_signals_to_check_context (SoupMessage  *msg,
+                                          GMainContext *context)
+{
+        g_object_set_data (G_OBJECT (msg), "thread-context", context);
+        g_signal_connect (msg, "starting", G_CALLBACK (msg_signal_check_context), context);
+        g_signal_connect (msg, "wrote-headers", G_CALLBACK (msg_signal_check_context), context);
+        g_signal_connect (msg, "wrote-body", G_CALLBACK (msg_signal_check_context), context);
+        g_signal_connect (msg, "got-headers", G_CALLBACK (msg_signal_check_context), context);
+        g_signal_connect (msg, "got-body", G_CALLBACK (msg_signal_check_context), context);
+        g_signal_connect (msg, "finished", G_CALLBACK (msg_signal_check_context), context);
+}
+
+static void
+msg_signal_check_thread (SoupMessage *msg,
+                         GThread     *thread)
+{
+        g_assert_true (g_object_get_data (G_OBJECT (msg), "thread-id") == thread);
+}
+
+static void
+connect_message_signals_to_check_thread (SoupMessage *msg,
+                                         GThread     *thread)
+{
+        g_object_set_data (G_OBJECT (msg), "thread-id", thread);
+        g_signal_connect (msg, "starting", G_CALLBACK (msg_signal_check_thread), thread);
+        g_signal_connect (msg, "wrote-headers", G_CALLBACK (msg_signal_check_thread), thread);
+        g_signal_connect (msg, "wrote-body", G_CALLBACK (msg_signal_check_thread), thread);
+        g_signal_connect (msg, "got-headers", G_CALLBACK (msg_signal_check_thread), thread);
+        g_signal_connect (msg, "got-body", G_CALLBACK (msg_signal_check_thread), thread);
+        g_signal_connect (msg, "finished", G_CALLBACK (msg_signal_check_thread), thread);
+}
+
+static void
+message_send_and_read_ready_cb (SoupSession  *session,
+                                GAsyncResult *result,
+                                GMainLoop    *loop)
+{
+        GBytes *body;
+        GBytes *index = soup_test_get_index ();
+
+        if (loop)
+                g_assert_true (g_main_loop_get_context (loop) == g_main_context_get_thread_default ());
+
+        body = soup_session_send_and_read_finish (session, result, NULL);
+        g_assert_nonnull (body);
+        g_assert_cmpmem (g_bytes_get_data (body, NULL), g_bytes_get_size (body), g_bytes_get_data (index, 
NULL), g_bytes_get_size (index));
+        g_bytes_unref (body);
+
+        if (loop)
+                g_main_loop_quit (loop);
+}
+
+static void
+task_async_function (GTask        *task,
+                     GObject      *source,
+                     Test         *test,
+                     GCancellable *cancellable)
+{
+        GMainContext *context;
+        GMainLoop *loop;
+        SoupMessage *msg;
+
+        context = g_main_context_new ();
+        g_main_context_push_thread_default (context);
+
+        loop = g_main_loop_new (context, FALSE);
+
+        if (test->flags & BASIC_SSL)
+                msg = soup_message_new ("GET", HTTPS_SERVER);
+        else
+                msg = soup_message_new_from_uri ("GET", base_uri);
+        if (test->flags & BASIC_HTTP2)
+                soup_message_set_force_http_version (msg, SOUP_HTTP_2_0);
+        connect_message_signals_to_check_context (msg, context);
+        soup_session_send_and_read_async (test->session, msg, G_PRIORITY_DEFAULT, NULL,
+                                          (GAsyncReadyCallback)message_send_and_read_ready_cb,
+                                          loop);
+        g_object_unref (msg);
+
+        g_main_loop_run (loop);
+        g_main_loop_unref (loop);
+
+        g_task_return_boolean (task, TRUE);
+
+        g_main_context_pop_thread_default (context);
+        g_main_context_unref (context);
+}
+
+static void
+task_sync_function (GTask        *task,
+                    GObject      *source,
+                    Test         *test,
+                    GCancellable *cancellable)
+{
+        SoupMessage *msg;
+        GBytes *body;
+        GBytes *index = soup_test_get_index ();
+
+        if (test->flags & BASIC_SSL)
+                msg = soup_message_new ("GET", HTTPS_SERVER);
+        else
+                msg = soup_message_new_from_uri ("GET", base_uri);
+        if (test->flags & BASIC_HTTP2)
+                soup_message_set_force_http_version (msg, SOUP_HTTP_2_0);
+        connect_message_signals_to_check_thread (msg, g_thread_self ());
+        body = soup_session_send_and_read (test->session, msg, NULL, NULL);
+        g_assert_nonnull (body);
+        g_assert_cmpmem (g_bytes_get_data (body, NULL), g_bytes_get_size (body), g_bytes_get_data (index, 
NULL), g_bytes_get_size (index));
+        g_bytes_unref (body);
+        g_object_unref (msg);
+
+        g_task_return_boolean (task, TRUE);
+}
+
+static void
+task_finished_cb (GObject      *source,
+                  GAsyncResult *result,
+                  guint        *finished_count)
+{
+        g_assert_true (g_task_propagate_boolean (G_TASK (result), NULL));
+        g_atomic_int_inc (finished_count);
+}
+
+static void
+message_finished_cb (SoupMessage *msg,
+                     guint       *finished_count)
+{
+        g_atomic_int_inc (finished_count);
+}
+
+static void
+do_multithread_basic_test (Test         *test,
+                           gconstpointer data)
+{
+        SoupMessage *msg = NULL;
+        guint n_msgs = 6;
+        guint n_main_thread_msgs;
+        guint i;
+        guint finished_count = 0;
+
+        if (test->flags & BASIC_PROXY) {
+                GProxyResolver *resolver;
+
+                resolver = g_simple_proxy_resolver_new (HTTP_PROXY, NULL);
+                soup_session_set_proxy_resolver (test->session, resolver);
+                g_object_unref (resolver);
+        }
+
+        n_main_thread_msgs = test->flags & BASIC_NO_MAIN_THREAD ? 0 : 1;
+
+        if (n_main_thread_msgs) {
+                if (test->flags & BASIC_SSL)
+                        msg = soup_message_new ("GET", HTTPS_SERVER);
+                else
+                        msg = soup_message_new_from_uri ("GET", base_uri);
+                if (test->flags & BASIC_HTTP2)
+                        soup_message_set_force_http_version (msg, SOUP_HTTP_2_0);
+                if (test->flags & BASIC_SYNC)
+                        connect_message_signals_to_check_thread (msg, g_thread_self ());
+                else
+                        connect_message_signals_to_check_context (msg, g_main_context_get_thread_default ());
+                g_signal_connect (msg, "finished",
+                                  G_CALLBACK (message_finished_cb),
+                                  &finished_count);
+                soup_session_send_and_read_async (test->session, msg, G_PRIORITY_DEFAULT, NULL,
+                                                  (GAsyncReadyCallback)message_send_and_read_ready_cb,
+                                                  NULL);
+        }
+
+        for (i = 0; i < n_msgs - n_main_thread_msgs; i++) {
+                GTask *task;
+
+                task = g_task_new (NULL, NULL, (GAsyncReadyCallback)task_finished_cb, &finished_count);
+                g_task_set_task_data (task, test, NULL);
+                g_task_run_in_thread (task, (GTaskThreadFunc)(test->flags & BASIC_SYNC ? task_sync_function 
: task_async_function));
+                g_object_unref (task);
+        }
+
+        while (g_atomic_int_get (&finished_count) != n_msgs)
+                g_main_context_iteration (NULL, TRUE);
+
+        g_clear_object (&msg);
+
+        while (g_main_context_pending (NULL))
+                g_main_context_iteration (NULL, FALSE);
+}
+
+static void
+do_multithread_basic_ssl_test (Test         *test,
+                               gconstpointer data)
+{
+        SOUP_TEST_SKIP_IF_NO_TLS;
+        SOUP_TEST_SKIP_IF_NO_APACHE;
+
+        do_multithread_basic_test (test, data);
+}
+
+static void
+connections_test_msg_starting (SoupMessage     *msg,
+                               SoupConnection **conn)
+{
+        *conn = g_object_ref (soup_message_get_connection (msg));
+}
+
+static void
+connections_test_task_async_function (GTask        *task,
+                                      GObject      *source,
+                                      Test         *test,
+                                      GCancellable *cancellable)
+{
+        GMainContext *context;
+        SoupMessage *msg;
+        GBytes *body;
+        SoupConnection *conn = NULL;
+
+        context = g_main_context_new ();
+        g_main_context_push_thread_default (context);
+
+        if (test->flags & BASIC_SSL)
+                msg = soup_message_new ("GET", HTTPS_SERVER);
+        else
+                msg = soup_message_new_from_uri ("GET", base_uri);
+        if (test->flags & BASIC_HTTP2)
+                soup_message_set_force_http_version (msg, SOUP_HTTP_2_0);
+        g_signal_connect (msg, "starting",
+                          G_CALLBACK (connections_test_msg_starting),
+                          &conn);
+        body = soup_test_session_async_send (test->session, msg, NULL, NULL);
+        g_bytes_unref (body);
+        g_object_unref (msg);
+
+        g_task_return_pointer (task, conn, g_object_unref);
+
+        g_main_context_pop_thread_default (context);
+        g_main_context_unref (context);
+}
+
+static void
+connections_test_task_sync_function (GTask        *task,
+                                     GObject      *source,
+                                     Test         *test,
+                                     GCancellable *cancellable)
+{
+        SoupMessage *msg;
+        GBytes *body;
+        SoupConnection *conn = NULL;
+
+        if (test->flags & BASIC_SSL)
+                msg = soup_message_new ("GET", HTTPS_SERVER);
+        else
+                msg = soup_message_new_from_uri ("GET", base_uri);
+        if (test->flags & BASIC_HTTP2)
+                soup_message_set_force_http_version (msg, SOUP_HTTP_2_0);
+        g_signal_connect (msg, "starting",
+                          G_CALLBACK (connections_test_msg_starting),
+                          &conn);
+        body = soup_session_send_and_read (test->session, msg, NULL, NULL);
+        g_bytes_unref (body);
+        g_object_unref (msg);
+
+        g_task_return_pointer (task, conn, g_object_unref);
+}
+
+static void
+do_multithread_connections_test (Test         *test,
+                                 gconstpointer data)
+{
+        SoupMessage *msg;
+        SoupConnection *conn = NULL;
+        SoupConnection *thread_conn;
+        GBytes *body;
+        GTask *task;
+
+        if (test->flags & BASIC_SSL)
+                msg = soup_message_new ("GET", HTTPS_SERVER);
+        else
+                msg = soup_message_new_from_uri ("GET", base_uri);
+        if (test->flags & BASIC_HTTP2)
+                soup_message_set_force_http_version (msg, SOUP_HTTP_2_0);
+        g_signal_connect (msg, "starting",
+                          G_CALLBACK (connections_test_msg_starting),
+                          &conn);
+        body = soup_test_session_async_send (test->session, msg, NULL, NULL);
+        g_bytes_unref (body);
+
+        while (g_main_context_pending (NULL))
+                g_main_context_iteration (NULL, FALSE);
+
+        g_assert_nonnull (conn);
+        g_assert_cmpuint (soup_connection_get_state (conn), ==, SOUP_CONNECTION_IDLE);
+
+        /* An idle connection can be reused by another thread */
+        task = g_task_new (NULL, NULL, NULL, NULL);
+        g_task_set_task_data (task, test, NULL);
+        g_task_run_in_thread_sync (task, (GTaskThreadFunc)(test->flags & BASIC_SYNC ? 
connections_test_task_sync_function : connections_test_task_async_function));
+        thread_conn = g_task_propagate_pointer (task, NULL);
+        g_object_unref (task);
+        g_assert_true (conn == thread_conn);
+        g_object_unref (thread_conn);
+
+        g_object_unref (conn);
+        g_object_unref (msg);
+}
+
+static void
+do_multithread_connections_http2_test (Test         *test,
+                                       gconstpointer data)
+{
+        SOUP_TEST_SKIP_IF_NO_TLS;
+        SOUP_TEST_SKIP_IF_NO_APACHE;
+
+        do_multithread_connections_test (test, data);
+}
+
+static void
+do_multithread_no_main_context_test (void)
+{
+        SoupSession *session;
+        SoupMessage *msg;
+        GBytes *body;
+        GBytes *index = soup_test_get_index ();
+        guint i;
+
+        SOUP_TEST_SKIP_IF_NO_TLS;
+        SOUP_TEST_SKIP_IF_NO_APACHE;
+
+        session = soup_test_session_new (NULL);
+
+        for (i = 0; i < 2; i++) {
+                msg = soup_message_new ("GET", HTTPS_SERVER);
+                if (i > 0)
+                        soup_message_set_force_http_version (msg, SOUP_HTTP_2_0);
+                body = soup_session_send_and_read (session, msg, NULL, NULL);
+                g_assert_nonnull (body);
+                g_assert_cmpmem (g_bytes_get_data (body, NULL), g_bytes_get_size (body), g_bytes_get_data 
(index, NULL), g_bytes_get_size (index));
+                g_bytes_unref (body);
+                g_object_unref (msg);
+        }
+
+        soup_test_session_abort_unref (session);
+}
+
+static void
+server_callback (SoupServer        *server,
+                 SoupServerMessage *msg,
+                 const char        *path,
+                 GHashTable        *query,
+                 gpointer           data)
+{
+        GBytes *index = soup_test_get_index ();
+
+        soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
+        soup_server_message_set_response (msg, "text/plain",
+                                          SOUP_MEMORY_STATIC,
+                                          g_bytes_get_data (index, NULL),
+                                          g_bytes_get_size (index));
+}
+
+int
+main (int argc, char **argv)
+{
+        int ret;
+        SoupServer *server;
+
+        test_init (argc, argv, NULL);
+        apache_init ();
+
+        server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
+        soup_server_add_handler (server, NULL, server_callback, "http", NULL);
+        base_uri = soup_test_server_get_uri (server, "http", NULL);
+
+        g_test_add ("/multithread/basic/async", Test,
+                    GUINT_TO_POINTER (0),
+                    test_setup,
+                    do_multithread_basic_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic/sync", Test,
+                    GUINT_TO_POINTER (BASIC_SYNC),
+                    test_setup,
+                    do_multithread_basic_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-ssl/async", Test,
+                    GUINT_TO_POINTER (BASIC_SSL),
+                    test_setup,
+                    do_multithread_basic_ssl_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-ssl/sync", Test,
+                    GUINT_TO_POINTER (BASIC_SSL | BASIC_SYNC),
+                    test_setup,
+                    do_multithread_basic_ssl_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-proxy/async", Test,
+                    GUINT_TO_POINTER (BASIC_PROXY),
+                    test_setup,
+                    do_multithread_basic_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-proxy/sync", Test,
+                    GUINT_TO_POINTER (BASIC_PROXY | BASIC_SYNC),
+                    test_setup,
+                    do_multithread_basic_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-no-main-thread/async", Test,
+                    GUINT_TO_POINTER (BASIC_NO_MAIN_THREAD),
+                    test_setup,
+                    do_multithread_basic_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-no-main-thread/sync", Test,
+                    GUINT_TO_POINTER (BASIC_NO_MAIN_THREAD | BASIC_SYNC),
+                    test_setup,
+                    do_multithread_basic_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-ssl-proxy/async", Test,
+                    GUINT_TO_POINTER (BASIC_SSL | BASIC_PROXY),
+                    test_setup,
+                    do_multithread_basic_ssl_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-ssl-proxy/sync", Test,
+                    GUINT_TO_POINTER (BASIC_SSL | BASIC_PROXY | BASIC_SYNC),
+                    test_setup,
+                    do_multithread_basic_ssl_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-http2/async", Test,
+                    GUINT_TO_POINTER (BASIC_HTTP2 | BASIC_SSL),
+                    test_setup,
+                    do_multithread_basic_ssl_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-http2/sync", Test,
+                    GUINT_TO_POINTER (BASIC_HTTP2 | BASIC_SSL | BASIC_SYNC),
+                    test_setup,
+                    do_multithread_basic_ssl_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-no-main-thread-http2/async", Test,
+                    GUINT_TO_POINTER (BASIC_NO_MAIN_THREAD | BASIC_HTTP2 | BASIC_SSL),
+                    test_setup,
+                    do_multithread_basic_ssl_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-no-main-thread-http2/sync", Test,
+                    GUINT_TO_POINTER (BASIC_NO_MAIN_THREAD | BASIC_HTTP2 | BASIC_SSL | BASIC_SYNC),
+                    test_setup,
+                    do_multithread_basic_ssl_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-max-conns/async", Test,
+                    GUINT_TO_POINTER (BASIC_MAX_CONNS),
+                    test_setup,
+                    do_multithread_basic_test,
+                    test_teardown);
+        g_test_add ("/multithread/basic-max-conns/sync", Test,
+                    GUINT_TO_POINTER (BASIC_MAX_CONNS | BASIC_SYNC),
+                    test_setup,
+                    do_multithread_basic_test,
+                    test_teardown);
+        g_test_add ("/multithread/connections/async", Test,
+                    GUINT_TO_POINTER (0),
+                    test_setup,
+                    do_multithread_connections_test,
+                    test_teardown);
+        g_test_add ("/multithread/connections/sync", Test,
+                    GUINT_TO_POINTER (BASIC_SYNC),
+                    test_setup,
+                    do_multithread_connections_test,
+                    test_teardown);
+        g_test_add ("/multithread/connections-http2/async", Test,
+                    GUINT_TO_POINTER (BASIC_HTTP2 | BASIC_SSL),
+                    test_setup,
+                    do_multithread_connections_http2_test,
+                    test_teardown);
+        g_test_add ("/multithread/connections-http2/sync", Test,
+                    GUINT_TO_POINTER (BASIC_HTTP2 | BASIC_SSL | BASIC_SYNC),
+                    test_setup,
+                    do_multithread_connections_http2_test,
+                    test_teardown);
+        g_test_add_func ("/multithread/no-main-context",
+                         do_multithread_no_main_context_test);
+
+        ret = g_test_run ();
+
+        g_uri_unref (base_uri);
+        soup_test_server_quit_unref (server);
+        test_cleanup ();
+
+        return ret;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]