1 | /* Test case for GNOME #651133 |
2 | * |
3 | * Copyright (C) 2008-2010 Red Hat, Inc. |
4 | * Copyright (C) 2011 Nokia Corporation |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2.1 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General |
17 | * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
18 | * |
19 | * Author: Simon McVittie <simon.mcvittie@collabora.co.uk> |
20 | */ |
21 | |
22 | #include <config.h> |
23 | |
24 | #include <unistd.h> |
25 | #include <string.h> |
26 | |
27 | #include <gio/gio.h> |
28 | |
29 | #include "gdbus-tests.h" |
30 | |
31 | #ifdef HAVE_DBUS1 |
32 | # include <dbus/dbus-shared.h> |
33 | #else |
34 | # define DBUS_INTERFACE_DBUS "org.freedesktop.DBus" |
35 | # define DBUS_PATH_DBUS "/org/freedesktop/DBus" |
36 | # define DBUS_SERVICE_DBUS "org.freedesktop.DBus" |
37 | # define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1 |
38 | # define DBUS_RELEASE_NAME_REPLY_RELEASED 1 |
39 | #endif |
40 | |
41 | #define MY_NAME "com.example.Test.Myself" |
42 | /* This many threads create and destroy GDBusProxy instances, in addition |
43 | * to the main thread processing their NameOwnerChanged signals. |
44 | * N_THREADS_MAX is used with "-m slow", N_THREADS otherwise. |
45 | */ |
46 | #define N_THREADS_MAX 10 |
47 | #define N_THREADS 2 |
48 | /* This many GDBusProxy instances are created by each thread. */ |
49 | #define N_REPEATS 100 |
50 | /* The main thread requests/releases a name this many times as rapidly as |
51 | * possible, before performing one "slow" cycle that waits for each method |
52 | * call result (and therefore, due to D-Bus total ordering, all previous |
53 | * method calls) to prevent requests from piling up infinitely. The more calls |
54 | * are made rapidly, the better we reproduce bugs. |
55 | */ |
56 | #define N_RAPID_CYCLES 50 |
57 | |
58 | static GMainLoop *loop; |
59 | |
60 | static gpointer |
61 | run_proxy_thread (gpointer data) |
62 | { |
63 | GDBusConnection *connection = data; |
64 | int i; |
65 | |
66 | g_assert (g_main_context_get_thread_default () == NULL); |
67 | |
68 | for (i = 0; i < N_REPEATS; i++) |
69 | { |
70 | GDBusProxy *proxy; |
71 | GError *error = NULL; |
72 | GVariant *ret; |
73 | |
74 | if (g_test_verbose ()) |
75 | g_printerr (format: "." ); |
76 | |
77 | proxy = g_dbus_proxy_new_sync (connection, |
78 | flags: G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START | |
79 | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, |
80 | NULL, |
81 | MY_NAME, |
82 | object_path: "/com/example/TestObject" , |
83 | interface_name: "com.example.Frob" , |
84 | NULL, |
85 | error: &error); |
86 | g_assert_no_error (error); |
87 | g_assert (proxy != NULL); |
88 | g_dbus_proxy_set_default_timeout (proxy, G_MAXINT); |
89 | |
90 | ret = g_dbus_proxy_call_sync (proxy, method_name: "StupidMethod" , NULL, |
91 | flags: G_DBUS_CALL_FLAGS_NO_AUTO_START, timeout_msec: -1, |
92 | NULL, NULL); |
93 | /* |
94 | * we expect this to fail - if we have the name at the moment, we called |
95 | * an unimplemented method, and if not, there was nothing to call |
96 | */ |
97 | g_assert (ret == NULL); |
98 | |
99 | /* |
100 | * this races with the NameOwnerChanged signal being emitted in an |
101 | * idle |
102 | */ |
103 | g_object_unref (object: proxy); |
104 | } |
105 | |
106 | g_main_loop_quit (loop); |
107 | return NULL; |
108 | } |
109 | |
110 | static void release_name (GDBusConnection *connection, gboolean wait); |
111 | |
112 | static void |
113 | request_name_cb (GObject *source, |
114 | GAsyncResult *res, |
115 | gpointer user_data) |
116 | { |
117 | GDBusConnection *connection = G_DBUS_CONNECTION (source); |
118 | GError *error = NULL; |
119 | GVariant *var; |
120 | |
121 | var = g_dbus_connection_call_finish (connection, res, error: &error); |
122 | g_assert_no_error (error); |
123 | g_assert_cmpuint (g_variant_get_uint32 (g_variant_get_child_value (var, 0)), |
124 | ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER); |
125 | |
126 | release_name (connection, TRUE); |
127 | } |
128 | |
129 | static void |
130 | request_name (GDBusConnection *connection, |
131 | gboolean wait) |
132 | { |
133 | g_dbus_connection_call (connection, |
134 | DBUS_SERVICE_DBUS, |
135 | DBUS_PATH_DBUS, |
136 | DBUS_INTERFACE_DBUS, |
137 | method_name: "RequestName" , |
138 | parameters: g_variant_new (format_string: "(su)" , MY_NAME, 0), |
139 | G_VARIANT_TYPE ("(u)" ), |
140 | flags: G_DBUS_CALL_FLAGS_NONE, |
141 | timeout_msec: -1, |
142 | NULL, |
143 | callback: wait ? request_name_cb : NULL, |
144 | NULL); |
145 | } |
146 | |
147 | static void |
148 | release_name_cb (GObject *source, |
149 | GAsyncResult *res, |
150 | gpointer user_data) |
151 | { |
152 | GDBusConnection *connection = G_DBUS_CONNECTION (source); |
153 | GError *error = NULL; |
154 | GVariant *var; |
155 | int i; |
156 | |
157 | var = g_dbus_connection_call_finish (connection, res, error: &error); |
158 | g_assert_no_error (error); |
159 | g_assert_cmpuint (g_variant_get_uint32 (g_variant_get_child_value (var, 0)), |
160 | ==, DBUS_RELEASE_NAME_REPLY_RELEASED); |
161 | |
162 | /* generate some rapid NameOwnerChanged signals to try to trigger crashes */ |
163 | for (i = 0; i < N_RAPID_CYCLES; i++) |
164 | { |
165 | request_name (connection, FALSE); |
166 | release_name (connection, FALSE); |
167 | } |
168 | |
169 | /* wait for dbus-daemon to catch up */ |
170 | request_name (connection, TRUE); |
171 | } |
172 | |
173 | static void |
174 | release_name (GDBusConnection *connection, |
175 | gboolean wait) |
176 | { |
177 | g_dbus_connection_call (connection, |
178 | DBUS_SERVICE_DBUS, |
179 | DBUS_PATH_DBUS, |
180 | DBUS_INTERFACE_DBUS, |
181 | method_name: "ReleaseName" , |
182 | parameters: g_variant_new (format_string: "(s)" , MY_NAME), |
183 | G_VARIANT_TYPE ("(u)" ), |
184 | flags: G_DBUS_CALL_FLAGS_NONE, |
185 | timeout_msec: -1, |
186 | NULL, |
187 | callback: wait ? release_name_cb : NULL, |
188 | NULL); |
189 | } |
190 | |
191 | static void |
192 | test_proxy (void) |
193 | { |
194 | GDBusConnection *connection; |
195 | GError *error = NULL; |
196 | GThread *proxy_threads[N_THREADS_MAX]; |
197 | int i; |
198 | int n_threads; |
199 | |
200 | if (g_test_slow ()) |
201 | n_threads = N_THREADS_MAX; |
202 | else |
203 | n_threads = N_THREADS; |
204 | |
205 | session_bus_up (); |
206 | |
207 | loop = g_main_loop_new (NULL, TRUE); |
208 | |
209 | connection = g_bus_get_sync (bus_type: G_BUS_TYPE_SESSION, |
210 | NULL, |
211 | error: &error); |
212 | g_assert_no_error (error); |
213 | |
214 | request_name (connection, TRUE); |
215 | |
216 | for (i = 0; i < n_threads; i++) |
217 | { |
218 | proxy_threads[i] = g_thread_new (name: "run-proxy" , |
219 | func: run_proxy_thread, data: connection); |
220 | } |
221 | |
222 | g_main_loop_run (loop); |
223 | |
224 | for (i = 0; i < n_threads; i++) |
225 | { |
226 | g_thread_join (thread: proxy_threads[i]); |
227 | } |
228 | |
229 | g_object_unref (object: connection); |
230 | g_main_loop_unref (loop); |
231 | |
232 | /* TODO: should call session_bus_down() but that requires waiting |
233 | * for all the outstanding method calls to complete... |
234 | */ |
235 | if (g_test_verbose ()) |
236 | g_printerr (format: "\n" ); |
237 | } |
238 | |
239 | int |
240 | main (int argc, |
241 | char *argv[]) |
242 | { |
243 | g_test_init (argc: &argc, argv: &argv, NULL); |
244 | |
245 | g_test_dbus_unset (); |
246 | |
247 | g_test_add_func (testpath: "/gdbus/proxy/vs-threads" , test_func: test_proxy); |
248 | |
249 | return g_test_run(); |
250 | } |
251 | |