/* Test case for GNOME #651133 * * Copyright (C) 2008-2010 Red Hat, Inc. * Copyright (C) 2011 Nokia Corporation * * SPDX-License-Identifier: LGPL-2.1-or-later * * This library 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.1 of the License, or (at your option) any later version. * * This library 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 library; if not, see . * * Author: Simon McVittie */ #include #include #include #include #include "gdbus-tests.h" #ifdef HAVE_DBUS1 # include #else # define DBUS_INTERFACE_DBUS "org.freedesktop.DBus" # define DBUS_PATH_DBUS "/org/freedesktop/DBus" # define DBUS_SERVICE_DBUS "org.freedesktop.DBus" # define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 # define DBUS_RELEASE_NAME_REPLY_RELEASED 1 #endif #define MY_NAME "com.example.Test.Myself" /* This many threads create and destroy GDBusProxy instances, in addition * to the main thread processing their NameOwnerChanged signals. * N_THREADS_MAX is used with "-m slow", N_THREADS otherwise. */ #define N_THREADS_MAX 10 #define N_THREADS 2 /* This many GDBusProxy instances are created by each thread. */ #define N_REPEATS 100 /* The main thread requests/releases a name this many times as rapidly as * possible, before performing one "slow" cycle that waits for each method * call result (and therefore, due to D-Bus total ordering, all previous * method calls) to prevent requests from piling up infinitely. The more calls * are made rapidly, the better we reproduce bugs. */ #define N_RAPID_CYCLES 50 static GMainLoop *loop; static gpointer run_proxy_thread (gpointer data) { GDBusConnection *connection = data; int i; g_assert (g_main_context_get_thread_default () == NULL); for (i = 0; i < N_REPEATS; i++) { GDBusProxy *proxy; GError *error = NULL; GVariant *ret; if (g_test_verbose ()) g_printerr ("."); proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, MY_NAME, "/com/example/TestObject", "com.example.Frob", NULL, &error); g_assert_no_error (error); g_assert (proxy != NULL); g_dbus_proxy_set_default_timeout (proxy, G_MAXINT); ret = g_dbus_proxy_call_sync (proxy, "StupidMethod", NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, NULL); /* * we expect this to fail - if we have the name at the moment, we called * an unimplemented method, and if not, there was nothing to call */ g_assert (ret == NULL); /* * this races with the NameOwnerChanged signal being emitted in an * idle */ g_object_unref (proxy); } g_main_loop_quit (loop); return NULL; } static void release_name (GDBusConnection *connection, gboolean wait); static void request_name_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GDBusConnection *connection = G_DBUS_CONNECTION (source); GError *error = NULL; GVariant *var; GVariant *child; var = g_dbus_connection_call_finish (connection, res, &error); g_assert_no_error (error); child = g_variant_get_child_value (var, 0); g_assert_cmpuint (g_variant_get_uint32 (child), ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER); release_name (connection, TRUE); g_variant_unref (child); g_variant_unref (var); } static void request_name (GDBusConnection *connection, gboolean wait) { g_dbus_connection_call (connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "RequestName", g_variant_new ("(su)", MY_NAME, 0), G_VARIANT_TYPE ("(u)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, wait ? request_name_cb : NULL, NULL); } static void release_name_cb (GObject *source, GAsyncResult *res, gpointer user_data) { GDBusConnection *connection = G_DBUS_CONNECTION (source); GError *error = NULL; GVariant *var; GVariant *child; int i; var = g_dbus_connection_call_finish (connection, res, &error); g_assert_no_error (error); child = g_variant_get_child_value (var, 0); g_assert_cmpuint (g_variant_get_uint32 (child), ==, DBUS_RELEASE_NAME_REPLY_RELEASED); /* generate some rapid NameOwnerChanged signals to try to trigger crashes */ for (i = 0; i < N_RAPID_CYCLES; i++) { request_name (connection, FALSE); release_name (connection, FALSE); } /* wait for dbus-daemon to catch up */ request_name (connection, TRUE); g_variant_unref (child); g_variant_unref (var); } static void release_name (GDBusConnection *connection, gboolean wait) { g_dbus_connection_call (connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "ReleaseName", g_variant_new ("(s)", MY_NAME), G_VARIANT_TYPE ("(u)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, wait ? release_name_cb : NULL, NULL); } static void test_proxy (void) { GDBusConnection *connection; GError *error = NULL; GThread *proxy_threads[N_THREADS_MAX]; int i; int n_threads; if (g_test_slow ()) n_threads = N_THREADS_MAX; else n_threads = N_THREADS; session_bus_up (); loop = g_main_loop_new (NULL, TRUE); connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); g_assert_no_error (error); request_name (connection, TRUE); for (i = 0; i < n_threads; i++) { proxy_threads[i] = g_thread_new ("run-proxy", run_proxy_thread, connection); } g_main_loop_run (loop); for (i = 0; i < n_threads; i++) { g_thread_join (proxy_threads[i]); } g_object_unref (connection); g_main_loop_unref (loop); /* TODO: should call session_bus_down() but that requires waiting * for all the outstanding method calls to complete... */ if (g_test_verbose ()) g_printerr ("\n"); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); g_test_dbus_unset (); g_test_add_func ("/gdbus/proxy/vs-threads", test_proxy); return g_test_run(); }