1/* Copyright (C) 2005 Imendio AB
2 *
3 * This software is provided "as is"; redistribution and modification
4 * is permitted, provided that the following disclaimer is retained.
5 *
6 * This software is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9 * In no event shall the authors or contributors be liable for any
10 * direct, indirect, incidental, special, exemplary, or consequential
11 * damages (including, but not limited to, procurement of substitute
12 * goods or services; loss of use, data, or profits; or business
13 * interruption) however caused and on any theory of liability, whether
14 * in contract, strict liability, or tort (including negligence or
15 * otherwise) arising in any way out of the use of this software, even
16 * if advised of the possibility of such damage.
17 */
18#include <glib-object.h>
19
20#ifdef G_OS_UNIX
21#include <unistd.h>
22#endif
23
24#define TEST_POINTER1 ((gpointer) 47)
25#define TEST_POINTER2 ((gpointer) 49)
26#define TEST_INT1 (-77)
27#define TEST_INT2 (78)
28
29/* --- GTest class --- */
30typedef struct {
31 GObject object;
32 gint value;
33 gpointer test_pointer1;
34 gpointer test_pointer2;
35} GTest;
36typedef struct {
37 GObjectClass parent_class;
38 void (*test_signal1) (GTest * test, gint an_int);
39 void (*test_signal2) (GTest * test, gint an_int);
40} GTestClass;
41
42#define G_TYPE_TEST (my_test_get_type ())
43#define MY_TEST(test) (G_TYPE_CHECK_INSTANCE_CAST ((test), G_TYPE_TEST, GTest))
44#define MY_IS_TEST(test) (G_TYPE_CHECK_INSTANCE_TYPE ((test), G_TYPE_TEST))
45#define MY_TEST_CLASS(tclass) (G_TYPE_CHECK_CLASS_CAST ((tclass), G_TYPE_TEST, GTestClass))
46#define MY_IS_TEST_CLASS(tclass) (G_TYPE_CHECK_CLASS_TYPE ((tclass), G_TYPE_TEST))
47#define MY_TEST_GET_CLASS(test) (G_TYPE_INSTANCE_GET_CLASS ((test), G_TYPE_TEST, GTestClass))
48
49static GType my_test_get_type (void);
50G_DEFINE_TYPE (GTest, my_test, G_TYPE_OBJECT)
51
52/* Test state */
53typedef struct
54{
55 GClosure *closure; /* (unowned) */
56 gboolean stopping;
57 gboolean seen_signal_handler;
58 gboolean seen_cleanup;
59 gboolean seen_test_int1;
60 gboolean seen_test_int2;
61 gboolean seen_thread1;
62 gboolean seen_thread2;
63} TestClosureRefcountData;
64
65/* --- functions --- */
66static void
67my_test_init (GTest * test)
68{
69 g_test_message (format: "Init %p", test);
70
71 test->value = 0;
72 test->test_pointer1 = TEST_POINTER1;
73 test->test_pointer2 = TEST_POINTER2;
74}
75
76typedef enum
77{
78 PROP_TEST_PROP = 1,
79} MyTestProperty;
80
81typedef enum
82{
83 SIGNAL_TEST_SIGNAL1,
84 SIGNAL_TEST_SIGNAL2,
85} MyTestSignal;
86
87static guint signals[SIGNAL_TEST_SIGNAL2 + 1] = { 0, };
88
89static void
90my_test_set_property (GObject *object,
91 guint prop_id,
92 const GValue *value,
93 GParamSpec *pspec)
94{
95 GTest *test = MY_TEST (object);
96 switch (prop_id)
97 {
98 case PROP_TEST_PROP:
99 test->value = g_value_get_int (value);
100 break;
101 default:
102 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
103 break;
104 }
105}
106
107static void
108my_test_get_property (GObject *object,
109 guint prop_id,
110 GValue *value,
111 GParamSpec *pspec)
112{
113 GTest *test = MY_TEST (object);
114 switch (prop_id)
115 {
116 case PROP_TEST_PROP:
117 g_value_set_int (value, v_int: test->value);
118 break;
119 default:
120 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
121 break;
122 }
123}
124
125static void
126my_test_test_signal2 (GTest *test,
127 gint an_int)
128{
129}
130
131static void
132my_test_emit_test_signal1 (GTest *test,
133 gint vint)
134{
135 g_signal_emit (G_OBJECT (test), signal_id: signals[SIGNAL_TEST_SIGNAL1], detail: 0, vint);
136}
137
138static void
139my_test_emit_test_signal2 (GTest *test,
140 gint vint)
141{
142 g_signal_emit (G_OBJECT (test), signal_id: signals[SIGNAL_TEST_SIGNAL2], detail: 0, vint);
143}
144
145static void
146my_test_class_init (GTestClass *klass)
147{
148 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
149
150 gobject_class->set_property = my_test_set_property;
151 gobject_class->get_property = my_test_get_property;
152
153 signals[SIGNAL_TEST_SIGNAL1] =
154 g_signal_new (signal_name: "test-signal1", G_TYPE_FROM_CLASS (klass), signal_flags: G_SIGNAL_RUN_LAST,
155 G_STRUCT_OFFSET (GTestClass, test_signal1), NULL, NULL,
156 c_marshaller: g_cclosure_marshal_VOID__INT, G_TYPE_NONE, n_params: 1, G_TYPE_INT);
157 signals[SIGNAL_TEST_SIGNAL2] =
158 g_signal_new (signal_name: "test-signal2", G_TYPE_FROM_CLASS (klass), signal_flags: G_SIGNAL_RUN_LAST,
159 G_STRUCT_OFFSET (GTestClass, test_signal2), NULL, NULL,
160 c_marshaller: g_cclosure_marshal_VOID__INT, G_TYPE_NONE, n_params: 1, G_TYPE_INT);
161
162 g_object_class_install_property (G_OBJECT_CLASS (klass), property_id: PROP_TEST_PROP,
163 pspec: g_param_spec_int (name: "test-prop", nick: "Test Prop", blurb: "Test property",
164 minimum: 0, maximum: 1, default_value: 0, flags: G_PARAM_READWRITE));
165 klass->test_signal2 = my_test_test_signal2;
166}
167
168static void
169test_closure (GClosure *closure)
170{
171 /* try to produce high contention in closure->ref_count */
172 guint i = 0, n = g_random_int () % 199;
173 for (i = 0; i < n; i++)
174 g_closure_ref (closure);
175 g_closure_sink (closure); /* NOP */
176 for (i = 0; i < n; i++)
177 g_closure_unref (closure);
178}
179
180static gpointer
181thread1_main (gpointer user_data)
182{
183 TestClosureRefcountData *data = user_data;
184 guint i = 0;
185
186 for (i = 1; !g_atomic_int_get (&data->stopping); i++)
187 {
188 test_closure (closure: data->closure);
189 if (i % 10000 == 0)
190 {
191 g_test_message (format: "Yielding from thread1");
192 g_thread_yield (); /* force context switch */
193 g_atomic_int_set (&data->seen_thread1, TRUE);
194 }
195 }
196 return NULL;
197}
198
199static gpointer
200thread2_main (gpointer user_data)
201{
202 TestClosureRefcountData *data = user_data;
203 guint i = 0;
204
205 for (i = 1; !g_atomic_int_get (&data->stopping); i++)
206 {
207 test_closure (closure: data->closure);
208 if (i % 10000 == 0)
209 {
210 g_test_message (format: "Yielding from thread2");
211 g_thread_yield (); /* force context switch */
212 g_atomic_int_set (&data->seen_thread2, TRUE);
213 }
214 }
215 return NULL;
216}
217
218static void
219test_signal_handler (GTest *test,
220 gint vint,
221 gpointer user_data)
222{
223 TestClosureRefcountData *data = user_data;
224
225 g_assert_true (test->test_pointer1 == TEST_POINTER1);
226
227 data->seen_signal_handler = TRUE;
228 data->seen_test_int1 |= vint == TEST_INT1;
229 data->seen_test_int2 |= vint == TEST_INT2;
230}
231
232static void
233destroy_data (gpointer user_data,
234 GClosure *closure)
235{
236 TestClosureRefcountData *data = user_data;
237
238 data->seen_cleanup = TRUE;
239 g_assert_true (data->closure == closure);
240 g_assert_cmpint (closure->ref_count, ==, 0);
241}
242
243static void
244test_emissions (GTest *test)
245{
246 my_test_emit_test_signal1 (test, TEST_INT1);
247 my_test_emit_test_signal2 (test, TEST_INT2);
248}
249
250/* Test that closure refcounting works even when high contested between three
251 * threads (the main thread, thread1 and thread2). Both child threads are
252 * contesting refs/unrefs, while the main thread periodically emits signals
253 * which also do refs/unrefs on closures. */
254static void
255test_closure_refcount (void)
256{
257 GThread *thread1, *thread2;
258 TestClosureRefcountData test_data = { 0, };
259 GClosure *closure;
260 GTest *object;
261 guint i, n_iterations;
262
263 object = g_object_new (G_TYPE_TEST, NULL);
264 closure = g_cclosure_new (G_CALLBACK (test_signal_handler), user_data: &test_data, destroy_data);
265
266 g_signal_connect_closure (instance: object, detailed_signal: "test-signal1", closure, FALSE);
267 g_signal_connect_closure (instance: object, detailed_signal: "test-signal2", closure, FALSE);
268
269 test_data.stopping = FALSE;
270 test_data.closure = closure;
271
272 thread1 = g_thread_new (name: "thread1", func: thread1_main, data: &test_data);
273 thread2 = g_thread_new (name: "thread2", func: thread2_main, data: &test_data);
274
275 /* The 16-bit compare-and-swap operations currently used for closure
276 * refcounts are really slow on some ARM CPUs, notably Cortex-A57.
277 * Reduce the number of iterations so that the test completes in a
278 * finite time, but don't reduce it so much that the main thread
279 * starves the other threads and causes a test failure.
280 *
281 * https://gitlab.gnome.org/GNOME/glib/issues/1316
282 * aka https://bugs.debian.org/880883 */
283#if defined(__aarch64__) || defined(__arm__)
284 n_iterations = 100000;
285#else
286 n_iterations = 1000000;
287#endif
288
289 /* Run the test for a reasonably high number of iterations, and ensure we
290 * don’t terminate until at least 10000 iterations have completed in both
291 * thread1 and thread2. Even though @n_iterations is high, we can’t guarantee
292 * that the scheduler allocates time fairly (or at all!) to thread1 or
293 * thread2. */
294 for (i = 1;
295 i < n_iterations ||
296 !g_atomic_int_get (&test_data.seen_thread1) ||
297 !g_atomic_int_get (&test_data.seen_thread2);
298 i++)
299 {
300 test_emissions (test: object);
301 if (i % 10000 == 0)
302 {
303 g_test_message (format: "Yielding from main thread");
304 g_thread_yield (); /* force context switch */
305 }
306 }
307
308 g_atomic_int_set (&test_data.stopping, TRUE);
309 g_test_message (format: "Stopping");
310
311 /* wait for thread shutdown */
312 g_thread_join (thread: thread1);
313 g_thread_join (thread: thread2);
314
315 /* finalize object, destroy signals, run cleanup code */
316 g_object_unref (object);
317
318 g_test_message (format: "Stopped");
319
320 g_assert_true (g_atomic_int_get (&test_data.seen_thread1));
321 g_assert_true (g_atomic_int_get (&test_data.seen_thread2));
322 g_assert_true (test_data.seen_test_int1);
323 g_assert_true (test_data.seen_test_int2);
324 g_assert_true (test_data.seen_signal_handler);
325 g_assert_true (test_data.seen_cleanup);
326}
327
328int
329main (int argc,
330 char *argv[])
331{
332 g_test_init (argc: &argc, argv: &argv, NULL);
333
334 g_test_add_func (testpath: "/closure/refcount", test_func: test_closure_refcount);
335
336 return g_test_run ();
337}
338

source code of gtk/subprojects/glib/gobject/tests/closure-refcount.c