1 | /* GLib testing framework examples and tests |
2 | * Copyright (C) 2008 Imendio AB |
3 | * Authors: Tim Janik |
4 | * |
5 | * This work is provided "as is"; redistribution and modification |
6 | * in whole or in part, in any medium, physical or electronic is |
7 | * permitted without restriction. |
8 | * |
9 | * This work is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
12 | * |
13 | * In no event shall the authors or contributors be liable for any |
14 | * direct, indirect, incidental, special, exemplary, or consequential |
15 | * damages (including, but not limited to, procurement of substitute |
16 | * goods or services; loss of use, data, or profits; or business |
17 | * interruption) however caused and on any theory of liability, whether |
18 | * in contract, strict liability, or tort (including negligence or |
19 | * otherwise) arising in any way out of the use of this software, even |
20 | * if advised of the possibility of such damage. |
21 | */ |
22 | |
23 | #ifndef GLIB_DISABLE_DEPRECATION_WARNINGS |
24 | #define GLIB_DISABLE_DEPRECATION_WARNINGS |
25 | #endif |
26 | |
27 | #include <glib.h> |
28 | #include <glib-object.h> |
29 | |
30 | static int mtsafe_call_counter = 0; /* multi thread safe call counter, must be accessed atomically */ |
31 | static int unsafe_call_counter = 0; /* single-threaded call counter */ |
32 | static GCond sync_cond; |
33 | static GMutex sync_mutex; |
34 | |
35 | #define NUM_COUNTER_INCREMENTS 100000 |
36 | |
37 | static void |
38 | call_counter_init (gpointer tclass) |
39 | { |
40 | int i; |
41 | for (i = 0; i < NUM_COUNTER_INCREMENTS; i++) |
42 | { |
43 | int saved_unsafe_call_counter = unsafe_call_counter; |
44 | g_atomic_int_add (&mtsafe_call_counter, 1); /* real call count update */ |
45 | g_thread_yield(); /* let concurrent threads corrupt the unsafe_call_counter state */ |
46 | unsafe_call_counter = 1 + saved_unsafe_call_counter; /* non-atomic counter update */ |
47 | } |
48 | } |
49 | |
50 | static void interface_per_class_init (void) { call_counter_init (NULL); } |
51 | |
52 | /* define 3 test interfaces */ |
53 | typedef GTypeInterface MyFace0Interface; |
54 | static GType my_face0_get_type (void); |
55 | G_DEFINE_INTERFACE (MyFace0, my_face0, G_TYPE_OBJECT) |
56 | static void my_face0_default_init (MyFace0Interface *iface) { call_counter_init (tclass: iface); } |
57 | typedef GTypeInterface MyFace1Interface; |
58 | static GType my_face1_get_type (void); |
59 | G_DEFINE_INTERFACE (MyFace1, my_face1, G_TYPE_OBJECT) |
60 | static void my_face1_default_init (MyFace1Interface *iface) { call_counter_init (tclass: iface); } |
61 | |
62 | /* define 3 test objects, adding interfaces 0 & 1, and adding interface 2 after class initialization */ |
63 | typedef GObject MyTester0; |
64 | typedef GObjectClass MyTester0Class; |
65 | static GType my_tester0_get_type (void); |
66 | G_DEFINE_TYPE_WITH_CODE (MyTester0, my_tester0, G_TYPE_OBJECT, |
67 | G_IMPLEMENT_INTERFACE (my_face0_get_type(), interface_per_class_init) |
68 | G_IMPLEMENT_INTERFACE (my_face1_get_type(), interface_per_class_init)) |
69 | static void my_tester0_init (MyTester0*t) {} |
70 | static void my_tester0_class_init (MyTester0Class*c) { call_counter_init (tclass: c); } |
71 | typedef GObject MyTester1; |
72 | typedef GObjectClass MyTester1Class; |
73 | |
74 | /* Disabled for now (see https://bugzilla.gnome.org/show_bug.cgi?id=687659) */ |
75 | #if 0 |
76 | typedef GTypeInterface MyFace2Interface; |
77 | static GType my_face2_get_type (void); |
78 | G_DEFINE_INTERFACE (MyFace2, my_face2, G_TYPE_OBJECT) |
79 | static void my_face2_default_init (MyFace2Interface *iface) { call_counter_init (iface); } |
80 | |
81 | static GType my_tester1_get_type (void); |
82 | G_DEFINE_TYPE_WITH_CODE (MyTester1, my_tester1, G_TYPE_OBJECT, |
83 | G_IMPLEMENT_INTERFACE (my_face0_get_type(), interface_per_class_init) |
84 | G_IMPLEMENT_INTERFACE (my_face1_get_type(), interface_per_class_init)) |
85 | static void my_tester1_init (MyTester1*t) {} |
86 | static void my_tester1_class_init (MyTester1Class*c) { call_counter_init (c); } |
87 | typedef GObject MyTester2; |
88 | typedef GObjectClass MyTester2Class; |
89 | static GType my_tester2_get_type (void); |
90 | G_DEFINE_TYPE_WITH_CODE (MyTester2, my_tester2, G_TYPE_OBJECT, |
91 | G_IMPLEMENT_INTERFACE (my_face0_get_type(), interface_per_class_init) |
92 | G_IMPLEMENT_INTERFACE (my_face1_get_type(), interface_per_class_init)) |
93 | static void my_tester2_init (MyTester2*t) {} |
94 | static void my_tester2_class_init (MyTester2Class*c) { call_counter_init (c); } |
95 | |
96 | static gpointer |
97 | tester_init_thread (gpointer data) |
98 | { |
99 | const GInterfaceInfo face2_interface_info = { (GInterfaceInitFunc) interface_per_class_init, NULL, NULL }; |
100 | gpointer klass; |
101 | /* first, synchronize with other threads, |
102 | * then run interface and class initializers, |
103 | * using unsafe_call_counter concurrently |
104 | */ |
105 | g_mutex_lock (&sync_mutex); |
106 | g_mutex_unlock (&sync_mutex); |
107 | /* test default interface initialization for face0 */ |
108 | g_type_default_interface_unref (g_type_default_interface_ref (my_face0_get_type())); |
109 | /* test class initialization, face0 per-class initializer, face1 default and per-class initializer */ |
110 | klass = g_type_class_ref ((GType) data); |
111 | /* test face2 default and per-class initializer, after class_init */ |
112 | g_type_add_interface_static (G_TYPE_FROM_CLASS (klass), my_face2_get_type(), &face2_interface_info); |
113 | /* cleanups */ |
114 | g_type_class_unref (klass); |
115 | return NULL; |
116 | } |
117 | |
118 | static void |
119 | test_threaded_class_init (void) |
120 | { |
121 | GThread *t1, *t2, *t3; |
122 | |
123 | /* pause newly created threads */ |
124 | g_mutex_lock (&sync_mutex); |
125 | |
126 | /* create threads */ |
127 | t1 = g_thread_create (tester_init_thread, (gpointer) my_tester0_get_type(), TRUE, NULL); |
128 | t2 = g_thread_create (tester_init_thread, (gpointer) my_tester1_get_type(), TRUE, NULL); |
129 | t3 = g_thread_create (tester_init_thread, (gpointer) my_tester2_get_type(), TRUE, NULL); |
130 | |
131 | /* execute threads */ |
132 | g_mutex_unlock (&sync_mutex); |
133 | while (g_atomic_int_get (&mtsafe_call_counter) < (3 + 3 + 3 * 3) * NUM_COUNTER_INCREMENTS) |
134 | { |
135 | if (g_test_verbose()) |
136 | g_printerr ("Initializers counted: %u\n" , g_atomic_int_get (&mtsafe_call_counter)); |
137 | g_usleep (50 * 1000); /* wait for threads to complete */ |
138 | } |
139 | if (g_test_verbose()) |
140 | g_printerr ("Total initializers: %u\n" , g_atomic_int_get (&mtsafe_call_counter)); |
141 | /* ensure non-corrupted counter updates */ |
142 | g_assert_cmpint (g_atomic_int_get (&mtsafe_call_counter), ==, unsafe_call_counter); |
143 | |
144 | g_thread_join (t1); |
145 | g_thread_join (t2); |
146 | g_thread_join (t3); |
147 | } |
148 | #endif |
149 | |
150 | typedef struct { |
151 | GObject parent; |
152 | char *name; |
153 | } PropTester; |
154 | typedef GObjectClass PropTesterClass; |
155 | static GType prop_tester_get_type (void); |
156 | G_DEFINE_TYPE (PropTester, prop_tester, G_TYPE_OBJECT) |
157 | #define PROP_NAME 1 |
158 | static void |
159 | prop_tester_init (PropTester* t) |
160 | { |
161 | if (t->name == NULL) |
162 | { } /* needs unit test framework initialization: g_test_bug ("race initializing properties"); */ |
163 | } |
164 | static void |
165 | prop_tester_set_property (GObject *object, |
166 | guint property_id, |
167 | const GValue *value, |
168 | GParamSpec *pspec) |
169 | {} |
170 | static void |
171 | prop_tester_class_init (PropTesterClass *c) |
172 | { |
173 | int i; |
174 | GParamSpec *param; |
175 | GObjectClass *gobject_class = G_OBJECT_CLASS (c); |
176 | |
177 | gobject_class->set_property = prop_tester_set_property; /* silence GObject checks */ |
178 | |
179 | g_mutex_lock (mutex: &sync_mutex); |
180 | g_cond_signal (cond: &sync_cond); |
181 | g_mutex_unlock (mutex: &sync_mutex); |
182 | |
183 | for (i = 0; i < 100; i++) /* wait a bit. */ |
184 | g_thread_yield(); |
185 | |
186 | call_counter_init (tclass: c); |
187 | param = g_param_spec_string (name: "name" , nick: "name_i18n" , |
188 | blurb: "yet-more-wasteful-i18n" , |
189 | NULL, |
190 | flags: G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | |
191 | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | |
192 | G_PARAM_STATIC_NICK); |
193 | g_object_class_install_property (oclass: gobject_class, PROP_NAME, pspec: param); |
194 | } |
195 | |
196 | static gpointer |
197 | object_create (gpointer data) |
198 | { |
199 | GObject *obj = g_object_new (object_type: prop_tester_get_type(), first_property_name: "name" , "fish" , NULL); |
200 | g_object_unref (object: obj); |
201 | return NULL; |
202 | } |
203 | |
204 | static void |
205 | test_threaded_object_init (void) |
206 | { |
207 | GThread *creator; |
208 | g_mutex_lock (mutex: &sync_mutex); |
209 | |
210 | creator = g_thread_create (func: object_create, NULL, TRUE, NULL); |
211 | /* really provoke the race */ |
212 | g_cond_wait (cond: &sync_cond, mutex: &sync_mutex); |
213 | |
214 | object_create (NULL); |
215 | g_mutex_unlock (mutex: &sync_mutex); |
216 | |
217 | g_thread_join (thread: creator); |
218 | } |
219 | |
220 | typedef struct { |
221 | MyTester0 *strong; |
222 | guint unref_delay; |
223 | } UnrefInThreadData; |
224 | |
225 | static gpointer |
226 | unref_in_thread (gpointer p) |
227 | { |
228 | UnrefInThreadData *data = p; |
229 | |
230 | g_usleep (microseconds: data->unref_delay); |
231 | g_object_unref (object: data->strong); |
232 | |
233 | return NULL; |
234 | } |
235 | |
236 | /* undefine to see this test fail without GWeakRef */ |
237 | #define HAVE_G_WEAK_REF |
238 | |
239 | #define SLEEP_MIN_USEC 1 |
240 | #define SLEEP_MAX_USEC 10 |
241 | |
242 | static void |
243 | test_threaded_weak_ref (void) |
244 | { |
245 | guint i; |
246 | guint get_wins = 0, unref_wins = 0; |
247 | guint n; |
248 | |
249 | if (g_test_thorough ()) |
250 | n = NUM_COUNTER_INCREMENTS; |
251 | else |
252 | n = NUM_COUNTER_INCREMENTS / 20; |
253 | |
254 | #ifdef G_OS_WIN32 |
255 | /* On Windows usleep has millisecond resolution and gets rounded up |
256 | * leading to the test running for a long time. */ |
257 | n /= 10; |
258 | #endif |
259 | |
260 | for (i = 0; i < n; i++) |
261 | { |
262 | UnrefInThreadData data; |
263 | #ifdef HAVE_G_WEAK_REF |
264 | /* GWeakRef<MyTester0> in C++ terms */ |
265 | GWeakRef weak; |
266 | #else |
267 | gpointer weak; |
268 | #endif |
269 | MyTester0 *strengthened; |
270 | guint get_delay; |
271 | GThread *thread; |
272 | GError *error = NULL; |
273 | |
274 | if (g_test_verbose () && (i % (n/20)) == 0) |
275 | g_printerr (format: "%u%%\n" , ((i * 100) / n)); |
276 | |
277 | /* Have an object and a weak ref to it */ |
278 | data.strong = g_object_new (object_type: my_tester0_get_type (), NULL); |
279 | |
280 | #ifdef HAVE_G_WEAK_REF |
281 | g_weak_ref_init (weak_ref: &weak, object: data.strong); |
282 | #else |
283 | weak = data.strong; |
284 | g_object_add_weak_pointer ((GObject *) weak, &weak); |
285 | #endif |
286 | |
287 | /* Delay for a random time on each side of the race, to perturb the |
288 | * timing. Ideally, we want each side to win half the races; on |
289 | * smcv's laptop, these timings are about right. |
290 | */ |
291 | data.unref_delay = g_random_int_range (SLEEP_MIN_USEC / 2, SLEEP_MAX_USEC / 2); |
292 | get_delay = g_random_int_range (SLEEP_MIN_USEC, SLEEP_MAX_USEC); |
293 | |
294 | /* One half of the race is to unref the shared object */ |
295 | thread = g_thread_create (func: unref_in_thread, data: &data, TRUE, error: &error); |
296 | g_assert_no_error (error); |
297 | |
298 | /* The other half of the race is to get the object from the "global |
299 | * singleton" |
300 | */ |
301 | g_usleep (microseconds: get_delay); |
302 | |
303 | #ifdef HAVE_G_WEAK_REF |
304 | strengthened = g_weak_ref_get (weak_ref: &weak); |
305 | #else |
306 | /* Spot the unsafe pointer access! In GDBusConnection this is rather |
307 | * better-hidden, but ends up with essentially the same thing, albeit |
308 | * cleared in dispose() rather than by a traditional weak pointer |
309 | */ |
310 | strengthened = weak; |
311 | |
312 | if (strengthened != NULL) |
313 | g_object_ref (strengthened); |
314 | #endif |
315 | |
316 | if (strengthened != NULL) |
317 | g_assert (G_IS_OBJECT (strengthened)); |
318 | |
319 | /* Wait for the thread to run */ |
320 | g_thread_join (thread); |
321 | |
322 | if (strengthened != NULL) |
323 | { |
324 | get_wins++; |
325 | g_assert (G_IS_OBJECT (strengthened)); |
326 | g_object_unref (object: strengthened); |
327 | } |
328 | else |
329 | { |
330 | unref_wins++; |
331 | } |
332 | |
333 | #ifdef HAVE_G_WEAK_REF |
334 | g_weak_ref_clear (weak_ref: &weak); |
335 | #else |
336 | if (weak != NULL) |
337 | g_object_remove_weak_pointer (weak, &weak); |
338 | #endif |
339 | } |
340 | |
341 | if (g_test_verbose ()) |
342 | g_printerr (format: "Race won by get %u times, unref %u times\n" , |
343 | get_wins, unref_wins); |
344 | } |
345 | |
346 | int |
347 | main (int argc, |
348 | char *argv[]) |
349 | { |
350 | g_test_init (argc: &argc, argv: &argv, NULL); |
351 | |
352 | /* g_test_add_func ("/GObject/threaded-class-init", test_threaded_class_init); */ |
353 | g_test_add_func (testpath: "/GObject/threaded-object-init" , test_func: test_threaded_object_init); |
354 | g_test_add_func (testpath: "/GObject/threaded-weak-ref" , test_func: test_threaded_weak_ref); |
355 | |
356 | return g_test_run(); |
357 | } |
358 | |