1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
3 * Regression test for fd.o #102839: blocking on pending calls in threads
5 * Copyright © 2018 KPIT Technologies Ltd.
6 * Copyright © 2018 Manish Narang <manrock007@gmail.com>
7 * Copyright © 2018 Collabora Ltd.
9 * Licensed under the Academic Free License version 2.1
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
24 #include <dbus/dbus.h>
29 #include "test-utils-glib.h"
38 DBusConnection *service_conn;
39 GThread *service_thread;
40 const gchar *service_name;
42 GThread *client_dispatcher_thread;
43 GThread **client_caller_threads;
44 DBusConnection *client_conn;
45 GMutex callers_remaining_mutex;
46 GCond callers_remaining_cond;
47 gsize callers_remaining;
49 gsize n_caller_threads;
50 gsize calls_per_thread;
53 static DBusHandlerResult
54 echo_filter (DBusConnection *connection,
60 if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
61 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
63 reply = dbus_message_new_method_return (message);
66 !dbus_connection_send (connection, reply, NULL))
69 dbus_clear_message (&reply);
71 return DBUS_HANDLER_RESULT_HANDLED;
75 * Thread function to dispatch the service connection. This function runs
76 * in its own thread, dispatching the service connection and replying to
77 * method calls from the callers, until the service connection is
78 * disconnected by the main thread.
81 service_thread_cb (gpointer user_data)
83 Fixture *f = user_data;
85 /* In principle we should be able to wait indefinitely (-1) but that
86 * seems to prevent closing the connection from the main thread from
87 * having its desired effect */
88 while (dbus_connection_read_write_dispatch (f->service_conn, 1000))
90 /* Disconnected message not received; keep processing */
97 * Much like service_thread_cb, but for the client connection. In a real
98 * program this would often be the main thread, running an event loop
99 * like the one provided by GLib.
102 client_dispatcher_thread_cb (gpointer user_data)
104 Fixture *f = user_data;
106 /* This thread needs to yield occasionally, otherwise the caller
107 * threads won't ever get a chance to send their messages.
108 * This is not a recommended I/O pattern, but in principle it was
109 * always meant to work... */
110 while (dbus_connection_read_write_dispatch (f->client_conn, 10))
112 /* Disconnected message not received; keep processing */
119 * Worker thread representing some background task. Some programs
120 * dispatch a DBusConnection in one thread (in this test that's the
121 * "client dispatcher thread"), and expect to be able to block on
122 * pending calls in other threads. This represents one of those other
126 client_caller_thread_cb (gpointer user_data)
128 Fixture *f = user_data;
131 for (i = 0; i < f->calls_per_thread; i++)
133 DBusMessage *call, *reply;
134 DBusError error = DBUS_ERROR_INIT;
135 gint64 time_before, time_after;
137 /* This deliberately isn't g_test_message() to avoid buffering
138 * issues: stderr is line-buffered */
140 g_printerr ("# thread %p: %" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT "\n",
141 g_thread_self (), i, f->calls_per_thread);
143 call = dbus_message_new_method_call (f->service_name, "/",
144 "com.example.Echo", "Echo");
145 g_assert_nonnull (call);
147 time_before = g_get_monotonic_time ();
148 reply = dbus_connection_send_with_reply_and_block (f->client_conn, call,
150 time_after = g_get_monotonic_time ();
151 test_assert_no_error (&error);
152 g_assert_nonnull (reply);
153 /* We don't expect it to have taken long - a few seconds max, even
154 * with all the other threads contending with us. If we were
155 * anywhere near the 30 second timeout then that's a failure. */
156 g_assert_cmpint (time_after - time_before, <=, 10 * G_USEC_PER_SEC);
157 dbus_clear_message (&reply);
158 dbus_clear_message (&call);
161 g_printerr ("# thread %p: finishing\n", g_thread_self ());
162 g_mutex_lock (&f->callers_remaining_mutex);
163 f->callers_remaining--;
164 g_cond_signal (&f->callers_remaining_cond);
165 g_mutex_unlock (&f->callers_remaining_mutex);
172 gconstpointer context)
175 dbus_error_init (&f->e);
177 f->address = test_get_dbus_daemon (NULL, TEST_USER_ME, NULL, &f->daemon_pid);
178 g_assert_nonnull (f->address);
180 f->service_conn = test_connect_to_bus (NULL, f->address);
181 f->service_name = dbus_bus_get_unique_name (f->service_conn);
182 f->client_conn = test_connect_to_bus (NULL, f->address);
184 if (!dbus_connection_add_filter (f->service_conn, echo_filter, f, NULL))
187 f->service_thread = g_thread_new ("service", service_thread_cb, f);
188 f->client_dispatcher_thread = g_thread_new ("client dispatcher",
189 client_dispatcher_thread_cb, f);
193 * Assert that in the following situation:
195 * - one thread dispatches the server connection (in real life this would
196 * normally be a separate process, of course)
197 * - one thread dispatches the client connection
198 * - many threads make blocking method calls on the same client connection
200 * the caller threads are regularly dispatched, and never get blocked
201 * until their method call timeout.
204 test_threads (Fixture *f,
205 gconstpointer context)
209 g_test_bug ("102839");
213 test_timeout_reset (10);
214 f->n_caller_threads = 20;
215 f->calls_per_thread = 10000;
219 test_timeout_reset (1);
220 f->n_caller_threads = 4;
221 f->calls_per_thread = 1000;
224 f->client_caller_threads = g_new0 (GThread *, f->n_caller_threads);
225 f->callers_remaining = f->n_caller_threads;
227 /* Start the caller threads off */
229 for (i = 0; i < f->n_caller_threads; i++)
231 gchar *name = g_strdup_printf ("client %" G_GSIZE_FORMAT, i);
233 f->client_caller_threads[i] = g_thread_new (name,
234 client_caller_thread_cb,
238 /* Wait for all caller threads to exit */
240 g_mutex_lock (&f->callers_remaining_mutex);
242 while (f->callers_remaining > 0)
243 g_cond_wait (&f->callers_remaining_cond, &f->callers_remaining_mutex);
245 g_mutex_unlock (&f->callers_remaining_mutex);
247 for (i = 0; i < f->n_caller_threads; i++)
248 g_thread_join (g_steal_pointer (&f->client_caller_threads[i]));
250 /* If we haven't crashed out yet, then we're good! */
254 teardown (Fixture *f,
255 gconstpointer context G_GNUC_UNUSED)
257 dbus_error_free (&f->e);
258 g_clear_error (&f->ge);
260 if (f->client_conn != NULL)
261 dbus_connection_close (f->client_conn);
263 if (f->service_conn != NULL)
265 dbus_connection_remove_filter (f->service_conn, echo_filter, f);
266 dbus_connection_close (f->service_conn);
269 /* Now that the connections have been closed, the threads will exit */
270 if (f->service_thread != NULL)
271 g_thread_join (g_steal_pointer (&f->service_thread));
273 if (f->client_dispatcher_thread != NULL)
274 g_thread_join (g_steal_pointer (&f->client_dispatcher_thread));
276 dbus_clear_connection (&f->service_conn);
277 dbus_clear_connection (&f->client_conn);
279 if (f->daemon_pid != 0)
281 test_kill_pid (f->daemon_pid);
282 g_spawn_close_pid (f->daemon_pid);
293 test_init (&argc, &argv);
295 g_test_add ("/thread-blocking", Fixture, NULL, setup, test_threads,
298 return g_test_run ();