/* 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();
}