Branch data Line data Source code
1 : : #include "gcontextspecificgroup.c"
2 : : #include <gio/gio.h>
3 : : #include <stdlib.h>
4 : : #include <string.h>
5 : :
6 : : #define N_THREADS 10
7 : :
8 : : static gchar *test_file;
9 : :
10 : : char *test_file_buffer;
11 : : gsize test_file_size;
12 : : static char async_read_buffer[8192];
13 : :
14 : : static void
15 : 2 : read_data (GObject *source, GAsyncResult *result, gpointer loop)
16 : : {
17 : 2 : GInputStream *in = G_INPUT_STREAM (source);
18 : 2 : GError *error = NULL;
19 : : gssize nread;
20 : :
21 : 2 : nread = g_input_stream_read_finish (in, result, &error);
22 : 2 : g_assert_no_error (error);
23 : :
24 : 2 : g_assert_cmpint (nread, >, 0);
25 : 2 : g_assert_cmpint (nread, <=, MIN(sizeof (async_read_buffer), test_file_size));
26 : 2 : g_assert (memcmp (async_read_buffer, test_file_buffer, nread) == 0);
27 : :
28 : 2 : g_main_loop_quit (loop);
29 : 2 : }
30 : :
31 : : static void
32 : 2 : opened_for_read (GObject *source, GAsyncResult *result, gpointer loop)
33 : : {
34 : 2 : GFile *file = G_FILE (source);
35 : : GFileInputStream *in;
36 : 2 : GError *error = NULL;
37 : :
38 : 2 : in = g_file_read_finish (file, result, &error);
39 : 2 : g_assert_no_error (error);
40 : :
41 : 2 : memset (async_read_buffer, 0, sizeof (async_read_buffer));
42 : 2 : g_input_stream_read_async (G_INPUT_STREAM (in),
43 : : async_read_buffer, sizeof (async_read_buffer),
44 : : G_PRIORITY_DEFAULT, NULL,
45 : : read_data, loop);
46 : :
47 : 2 : g_object_unref (in);
48 : 2 : }
49 : :
50 : : /* Test 1: Async I/O started in a thread with a thread-default context
51 : : * will stick to that thread, and will complete even if the default
52 : : * main loop is blocked. (NB: the last part would not be true if we
53 : : * were testing GFileMonitor!)
54 : : */
55 : :
56 : : static gboolean idle_start_test1_thread (gpointer loop);
57 : : static gpointer test1_thread (gpointer user_data);
58 : :
59 : : static gboolean test1_done;
60 : : static GCond test1_cond;
61 : : static GMutex test1_mutex;
62 : :
63 : : static void
64 : 1 : test_thread_independence (void)
65 : : {
66 : : GMainLoop *loop;
67 : :
68 : 1 : loop = g_main_loop_new (NULL, FALSE);
69 : 1 : g_idle_add (idle_start_test1_thread, loop);
70 : 1 : g_main_loop_run (loop);
71 : 1 : g_main_loop_unref (loop);
72 : 1 : }
73 : :
74 : : static gboolean
75 : 1 : idle_start_test1_thread (gpointer loop)
76 : : {
77 : : gint64 time;
78 : : GThread *thread;
79 : : gboolean io_completed;
80 : :
81 : 1 : g_mutex_lock (&test1_mutex);
82 : 1 : thread = g_thread_new ("test1", test1_thread, NULL);
83 : :
84 : 1 : time = g_get_monotonic_time () + 20 * G_TIME_SPAN_SECOND;
85 : 2 : while (!test1_done)
86 : : {
87 : 1 : io_completed = g_cond_wait_until (&test1_cond, &test1_mutex, time);
88 : 1 : g_assert (io_completed);
89 : : }
90 : 1 : g_thread_join (thread);
91 : :
92 : 1 : g_mutex_unlock (&test1_mutex);
93 : 1 : g_main_loop_quit (loop);
94 : 1 : return G_SOURCE_REMOVE;
95 : : }
96 : :
97 : : static gpointer
98 : 1 : test1_thread (gpointer user_data)
99 : : {
100 : : GMainContext *context;
101 : : GMainLoop *loop;
102 : : GFile *file;
103 : :
104 : : /* Wait for main thread to be waiting on test1_cond */
105 : 1 : g_mutex_lock (&test1_mutex);
106 : :
107 : 1 : context = g_main_context_new ();
108 : 1 : g_assert (g_main_context_get_thread_default () == NULL);
109 : 1 : g_main_context_push_thread_default (context);
110 : 1 : g_assert (g_main_context_get_thread_default () == context);
111 : :
112 : 1 : file = g_file_new_for_path (test_file);
113 : 1 : g_assert (g_file_supports_thread_contexts (file));
114 : :
115 : 1 : loop = g_main_loop_new (context, FALSE);
116 : 1 : g_file_read_async (file, G_PRIORITY_DEFAULT, NULL,
117 : : opened_for_read, loop);
118 : 1 : g_object_unref (file);
119 : 1 : g_main_loop_run (loop);
120 : 1 : g_main_loop_unref (loop);
121 : :
122 : 1 : test1_done = TRUE;
123 : 1 : g_cond_signal (&test1_cond);
124 : 1 : g_mutex_unlock (&test1_mutex);
125 : :
126 : 1 : g_main_context_pop_thread_default (context);
127 : 1 : g_main_context_unref (context);
128 : :
129 : 1 : return NULL;
130 : : }
131 : :
132 : : /* Test 2: If we push a thread-default context in the main thread, we
133 : : * can run async ops in that context without running the default
134 : : * context.
135 : : */
136 : :
137 : : static gboolean test2_fail (gpointer user_data);
138 : :
139 : : static void
140 : 1 : test_context_independence (void)
141 : : {
142 : : GMainContext *context;
143 : : GMainLoop *loop;
144 : : GFile *file;
145 : : guint default_timeout;
146 : : GSource *thread_default_timeout;
147 : :
148 : 1 : context = g_main_context_new ();
149 : 1 : g_assert (g_main_context_get_thread_default () == NULL);
150 : 1 : g_main_context_push_thread_default (context);
151 : 1 : g_assert (g_main_context_get_thread_default () == context);
152 : :
153 : 1 : file = g_file_new_for_path (test_file);
154 : 1 : g_assert (g_file_supports_thread_contexts (file));
155 : :
156 : : /* Add a timeout to the main loop, to fail immediately if it gets run */
157 : 1 : default_timeout = g_timeout_add_full (G_PRIORITY_HIGH, 0,
158 : : test2_fail, NULL, NULL);
159 : : /* Add a timeout to the alternate loop, to fail if the I/O *doesn't* run */
160 : 1 : thread_default_timeout = g_timeout_source_new_seconds (2);
161 : 1 : g_source_set_callback (thread_default_timeout, test2_fail, NULL, NULL);
162 : 1 : g_source_attach (thread_default_timeout, context);
163 : :
164 : 1 : loop = g_main_loop_new (context, FALSE);
165 : 1 : g_file_read_async (file, G_PRIORITY_DEFAULT, NULL,
166 : : opened_for_read, loop);
167 : 1 : g_object_unref (file);
168 : 1 : g_main_loop_run (loop);
169 : 1 : g_main_loop_unref (loop);
170 : :
171 : 1 : g_source_remove (default_timeout);
172 : 1 : g_source_destroy (thread_default_timeout);
173 : 1 : g_source_unref (thread_default_timeout);
174 : :
175 : 1 : g_main_context_pop_thread_default (context);
176 : 1 : g_main_context_unref (context);
177 : 1 : }
178 : :
179 : : static gboolean
180 : 0 : test2_fail (gpointer user_data)
181 : : {
182 : : g_assert_not_reached ();
183 : : return FALSE;
184 : : }
185 : :
186 : :
187 : : typedef struct
188 : : {
189 : : GObject parent_instance;
190 : :
191 : : GMainContext *context;
192 : : } PerThreadThing;
193 : :
194 : : typedef GObjectClass PerThreadThingClass;
195 : :
196 : : static GType per_thread_thing_get_type (void);
197 : :
198 : 3238 : G_DEFINE_TYPE (PerThreadThing, per_thread_thing, G_TYPE_OBJECT)
199 : :
200 : : static GContextSpecificGroup group;
201 : : static gpointer instances[N_THREADS];
202 : : static gint is_running;
203 : : static gint current_value;
204 : : static gint observed_values[N_THREADS];
205 : :
206 : : static void
207 : 4 : start_func (void)
208 : : {
209 : 4 : g_assert (!is_running);
210 : 4 : g_atomic_int_set (&is_running, TRUE);
211 : 4 : }
212 : :
213 : : static void
214 : 4 : stop_func (void)
215 : : {
216 : 4 : g_assert (is_running);
217 : 4 : g_atomic_int_set (&is_running, FALSE);
218 : 4 : }
219 : :
220 : : static void
221 : 21 : per_thread_thing_finalize (GObject *object)
222 : : {
223 : 21 : PerThreadThing *thing = (PerThreadThing *) object;
224 : :
225 : 21 : g_context_specific_group_remove (&group, thing->context, thing, stop_func);
226 : :
227 : 21 : G_OBJECT_CLASS (per_thread_thing_parent_class)->finalize (object);
228 : 21 : }
229 : :
230 : : static void
231 : 21 : per_thread_thing_init (PerThreadThing *thing)
232 : : {
233 : 21 : }
234 : :
235 : : static void
236 : 1 : per_thread_thing_class_init (PerThreadThingClass *class)
237 : : {
238 : 1 : class->finalize = per_thread_thing_finalize;
239 : :
240 : 1 : g_signal_new ("changed", per_thread_thing_get_type (), G_SIGNAL_RUN_FIRST, 0,
241 : : NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
242 : 1 : }
243 : :
244 : : static gpointer
245 : 1021 : per_thread_thing_get (void)
246 : : {
247 : 1021 : return g_context_specific_group_get (&group, per_thread_thing_get_type (),
248 : : G_STRUCT_OFFSET (PerThreadThing, context),
249 : : start_func);
250 : : }
251 : :
252 : : static gpointer
253 : 10 : test_identity_thread (gpointer user_data)
254 : : {
255 : 10 : guint thread_nr = GPOINTER_TO_UINT (user_data);
256 : : GMainContext *my_context;
257 : : guint i, j;
258 : :
259 : 10 : my_context = g_main_context_new ();
260 : 10 : g_main_context_push_thread_default (my_context);
261 : :
262 : 10 : g_assert (!instances[thread_nr]);
263 : 10 : instances[thread_nr] = per_thread_thing_get ();
264 : 10 : g_assert (g_atomic_int_get (&is_running));
265 : :
266 : 1010 : for (i = 0; i < 100; i++)
267 : : {
268 : 1000 : gpointer instance = per_thread_thing_get ();
269 : :
270 : 11000 : for (j = 0; j < N_THREADS; j++)
271 : 10000 : g_assert ((instance == instances[j]) == (thread_nr == j));
272 : :
273 : 1000 : g_assert (g_atomic_int_get (&is_running));
274 : :
275 : 1000 : g_thread_yield ();
276 : :
277 : 1000 : g_assert (g_atomic_int_get (&is_running));
278 : : }
279 : :
280 : 1010 : for (i = 0; i < 100; i++)
281 : : {
282 : 1000 : g_object_unref (instances[thread_nr]);
283 : :
284 : 11000 : for (j = 0; j < N_THREADS; j++)
285 : 10000 : g_assert ((instances[thread_nr] == instances[j]) == (thread_nr == j));
286 : :
287 : 1000 : g_assert (g_atomic_int_get (&is_running));
288 : :
289 : 1000 : g_thread_yield ();
290 : : }
291 : :
292 : : /* drop the last ref */
293 : 10 : g_object_unref (instances[thread_nr]);
294 : 10 : instances[thread_nr] = NULL;
295 : :
296 : 10 : g_main_context_pop_thread_default (my_context);
297 : 10 : g_main_context_unref (my_context);
298 : :
299 : : /* at least one thread should see this cleared on exit */
300 : 10 : return GUINT_TO_POINTER (!group.requested_state);
301 : : }
302 : :
303 : : static void
304 : 1 : test_context_specific_identity (void)
305 : : {
306 : : GThread *threads[N_THREADS];
307 : 1 : gboolean exited = FALSE;
308 : : guint i;
309 : :
310 : 1 : g_assert (!g_atomic_int_get (&is_running));
311 : 11 : for (i = 0; i < N_THREADS; i++)
312 : 10 : threads[i] = g_thread_new ("test", test_identity_thread, GUINT_TO_POINTER (i));
313 : 11 : for (i = 0; i < N_THREADS; i++)
314 : 10 : exited |= GPOINTER_TO_UINT (g_thread_join (threads[i]));
315 : 1 : g_assert (exited);
316 : 1 : g_assert (!group.requested_state);
317 : 1 : }
318 : :
319 : : static void
320 : 14408 : changed_emitted (PerThreadThing *thing,
321 : : gpointer user_data)
322 : : {
323 : 14408 : gint *observed_value = user_data;
324 : :
325 : 14408 : g_atomic_int_set (observed_value, g_atomic_int_get (¤t_value));
326 : 14408 : }
327 : :
328 : : static gpointer
329 : 10 : test_emit_thread (gpointer user_data)
330 : : {
331 : 10 : gint *observed_value = user_data;
332 : : GMainContext *my_context;
333 : : gpointer instance;
334 : :
335 : 10 : my_context = g_main_context_new ();
336 : 10 : g_main_context_push_thread_default (my_context);
337 : :
338 : 10 : instance = per_thread_thing_get ();
339 : 10 : g_assert (g_atomic_int_get (&is_running));
340 : :
341 : 10 : g_signal_connect (instance, "changed", G_CALLBACK (changed_emitted), observed_value);
342 : :
343 : : /* observe after connection */
344 : 10 : g_atomic_int_set (observed_value, g_atomic_int_get (¤t_value));
345 : :
346 : 14427 : while (g_atomic_int_get (¤t_value) != -1)
347 : 14417 : g_main_context_iteration (my_context, TRUE);
348 : :
349 : 10 : g_object_unref (instance);
350 : :
351 : 10 : g_main_context_pop_thread_default (my_context);
352 : 10 : g_main_context_unref (my_context);
353 : :
354 : : /* at least one thread should see this cleared on exit */
355 : 10 : return GUINT_TO_POINTER (!group.requested_state);
356 : : }
357 : :
358 : : static void
359 : 1 : test_context_specific_emit (void)
360 : : {
361 : : GThread *threads[N_THREADS];
362 : 1 : gboolean exited = FALSE;
363 : : gsize i;
364 : : gint k, n;
365 : :
366 : 11 : for (i = 0; i < N_THREADS; i++)
367 : 10 : threads[i] = g_thread_new ("test", test_emit_thread, &observed_values[i]);
368 : :
369 : : /* make changes and ensure that they are observed */
370 : 1001 : for (n = 0; n < 1000; n++)
371 : : {
372 : : gint64 expiry;
373 : :
374 : : /* don't burn CPU forever */
375 : 1000 : expiry = g_get_monotonic_time () + 10 * G_TIME_SPAN_SECOND;
376 : :
377 : 1000 : g_atomic_int_set (¤t_value, n);
378 : :
379 : : /* wake them to notice */
380 : 3213 : for (k = 0; k < g_test_rand_int_range (1, 5); k++)
381 : 2213 : g_context_specific_group_emit (&group, g_signal_lookup ("changed", per_thread_thing_get_type ()));
382 : :
383 : 11000 : for (i = 0; i < N_THREADS; i++)
384 : 428584 : while (g_atomic_int_get (&observed_values[i]) != n)
385 : : {
386 : 418584 : g_thread_yield ();
387 : :
388 : 418584 : if (g_get_monotonic_time () > expiry)
389 : 0 : g_error ("timed out");
390 : : }
391 : : }
392 : :
393 : : /* tell them to quit */
394 : 1 : g_atomic_int_set (¤t_value, -1);
395 : 1 : g_context_specific_group_emit (&group, g_signal_lookup ("notify", G_TYPE_OBJECT));
396 : :
397 : 11 : for (i = 0; i < N_THREADS; i++)
398 : 10 : exited |= GPOINTER_TO_UINT (g_thread_join (threads[i]));
399 : 1 : g_assert (exited);
400 : 1 : g_assert (!group.requested_state);
401 : 1 : }
402 : :
403 : : static void
404 : 1 : test_context_specific_emit_and_unref (void)
405 : : {
406 : : gpointer obj;
407 : :
408 : 1 : obj = per_thread_thing_get ();
409 : 1 : g_context_specific_group_emit (&group, g_signal_lookup ("changed", per_thread_thing_get_type ()));
410 : 1 : g_object_unref (obj);
411 : :
412 : 1 : while (g_main_context_iteration (NULL, 0))
413 : : ;
414 : 1 : }
415 : :
416 : : int
417 : 1 : main (int argc, char **argv)
418 : : {
419 : 1 : GError *error = NULL;
420 : : int ret;
421 : :
422 : 1 : g_test_init (&argc, &argv, NULL);
423 : :
424 : 1 : test_file = g_test_build_filename (G_TEST_DIST, "contexts.c", NULL);
425 : 1 : g_file_get_contents (test_file, &test_file_buffer,
426 : : &test_file_size, &error);
427 : 1 : g_assert_no_error (error);
428 : :
429 : 1 : g_test_add_func ("/gio/contexts/thread-independence", test_thread_independence);
430 : 1 : g_test_add_func ("/gio/contexts/context-independence", test_context_independence);
431 : 1 : g_test_add_func ("/gio/contexts/context-specific/identity", test_context_specific_identity);
432 : 1 : g_test_add_func ("/gio/contexts/context-specific/emit", test_context_specific_emit);
433 : 1 : g_test_add_func ("/gio/contexts/context-specific/emit-and-unref", test_context_specific_emit_and_unref);
434 : :
435 : 1 : ret = g_test_run();
436 : :
437 : 1 : g_free (test_file_buffer);
438 : 1 : g_free (test_file);
439 : :
440 : 1 : return ret;
441 : : }
|