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 --- */ |
30 | typedef struct { |
31 | GObject object; |
32 | gint value; |
33 | gpointer test_pointer1; |
34 | gpointer test_pointer2; |
35 | } GTest; |
36 | typedef 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 | |
49 | static GType my_test_get_type (void); |
50 | G_DEFINE_TYPE (GTest, my_test, G_TYPE_OBJECT) |
51 | |
52 | /* Test state */ |
53 | typedef 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 --- */ |
66 | static void |
67 | my_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 | |
76 | typedef enum |
77 | { |
78 | PROP_TEST_PROP = 1, |
79 | } MyTestProperty; |
80 | |
81 | typedef enum |
82 | { |
83 | SIGNAL_TEST_SIGNAL1, |
84 | SIGNAL_TEST_SIGNAL2, |
85 | } MyTestSignal; |
86 | |
87 | static guint signals[SIGNAL_TEST_SIGNAL2 + 1] = { 0, }; |
88 | |
89 | static void |
90 | my_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 | |
107 | static void |
108 | my_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 | |
125 | static void |
126 | my_test_test_signal2 (GTest *test, |
127 | gint an_int) |
128 | { |
129 | } |
130 | |
131 | static void |
132 | my_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 | |
138 | static void |
139 | my_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 | |
145 | static void |
146 | my_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 | |
168 | static void |
169 | test_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 | |
180 | static gpointer |
181 | thread1_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 | |
199 | static gpointer |
200 | thread2_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 | |
218 | static void |
219 | test_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 | |
232 | static void |
233 | destroy_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 | |
243 | static void |
244 | test_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. */ |
254 | static void |
255 | test_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 | |
328 | int |
329 | main (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 | |