• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright (C) 2011 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Stef Walter <stefw@collabora.co.uk>
19  */
20 
21 #include <locale.h>
22 
23 #include <gio/gio.h>
24 
25 #include "glib/glib-private.h"
26 
27 /* How long to wait in ms for each iteration */
28 #define WAIT_ITERATION (10)
29 
30 static gint num_async_operations = 0;
31 
32 typedef struct
33 {
34   guint iterations_requested;  /* construct-only */
35   guint iterations_done;  /* (atomic) */
36 } MockOperationData;
37 
38 static void
mock_operation_free(gpointer user_data)39 mock_operation_free (gpointer user_data)
40 {
41   MockOperationData *data = user_data;
42   g_free (data);
43 }
44 
45 static void
mock_operation_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)46 mock_operation_thread (GTask        *task,
47                        gpointer      source_object,
48                        gpointer      task_data,
49                        GCancellable *cancellable)
50 {
51   MockOperationData *data = task_data;
52   guint i;
53 
54   for (i = 0; i < data->iterations_requested; i++)
55     {
56       if (g_cancellable_is_cancelled (cancellable))
57         break;
58       if (g_test_verbose ())
59         g_test_message ("THRD: %u iteration %u", data->iterations_requested, i);
60       g_usleep (WAIT_ITERATION * 1000);
61     }
62 
63   if (g_test_verbose ())
64     g_test_message ("THRD: %u stopped at %u", data->iterations_requested, i);
65   g_atomic_int_add (&data->iterations_done, i);
66 
67   g_task_return_boolean (task, TRUE);
68 }
69 
70 static gboolean
mock_operation_timeout(gpointer user_data)71 mock_operation_timeout (gpointer user_data)
72 {
73   GTask *task;
74   MockOperationData *data;
75   gboolean done = FALSE;
76   guint iterations_done;
77 
78   task = G_TASK (user_data);
79   data = g_task_get_task_data (task);
80   iterations_done = g_atomic_int_get (&data->iterations_done);
81 
82   if (iterations_done >= data->iterations_requested)
83       done = TRUE;
84 
85   if (g_cancellable_is_cancelled (g_task_get_cancellable (task)))
86       done = TRUE;
87 
88   if (done)
89     {
90       if (g_test_verbose ())
91         g_test_message ("LOOP: %u stopped at %u",
92                         data->iterations_requested, iterations_done);
93       g_task_return_boolean (task, TRUE);
94       return G_SOURCE_REMOVE;
95     }
96   else
97     {
98       g_atomic_int_inc (&data->iterations_done);
99       if (g_test_verbose ())
100         g_test_message ("LOOP: %u iteration %u",
101                         data->iterations_requested, iterations_done + 1);
102       return G_SOURCE_CONTINUE;
103     }
104 }
105 
106 static void
mock_operation_async(guint wait_iterations,gboolean run_in_thread,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)107 mock_operation_async (guint                wait_iterations,
108                       gboolean             run_in_thread,
109                       GCancellable        *cancellable,
110                       GAsyncReadyCallback  callback,
111                       gpointer             user_data)
112 {
113   GTask *task;
114   MockOperationData *data;
115 
116   task = g_task_new (NULL, cancellable, callback, user_data);
117   data = g_new0 (MockOperationData, 1);
118   data->iterations_requested = wait_iterations;
119   g_task_set_task_data (task, data, mock_operation_free);
120 
121   if (run_in_thread)
122     {
123       g_task_run_in_thread (task, mock_operation_thread);
124       if (g_test_verbose ())
125         g_test_message ("THRD: %d started", wait_iterations);
126     }
127   else
128     {
129       g_timeout_add_full (G_PRIORITY_DEFAULT, WAIT_ITERATION, mock_operation_timeout,
130                           g_object_ref (task), g_object_unref);
131       if (g_test_verbose ())
132         g_test_message ("LOOP: %d started", wait_iterations);
133     }
134 
135   g_object_unref (task);
136 }
137 
138 static guint
mock_operation_finish(GAsyncResult * result,GError ** error)139 mock_operation_finish (GAsyncResult  *result,
140                        GError       **error)
141 {
142   MockOperationData *data;
143   GTask *task;
144 
145   g_assert_true (g_task_is_valid (result, NULL));
146 
147   /* This test expects the return value to be iterations_done even
148    * when an error is set.
149    */
150   task = G_TASK (result);
151   data = g_task_get_task_data (task);
152 
153   g_task_propagate_boolean (task, error);
154   return g_atomic_int_get (&data->iterations_done);
155 }
156 
157 static void
on_mock_operation_ready(GObject * source,GAsyncResult * result,gpointer user_data)158 on_mock_operation_ready (GObject      *source,
159                          GAsyncResult *result,
160                          gpointer      user_data)
161 {
162   guint iterations_requested;
163   guint iterations_done;
164   GError *error = NULL;
165 
166   iterations_requested = GPOINTER_TO_UINT (user_data);
167   iterations_done = mock_operation_finish (result, &error);
168 
169   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
170   g_error_free (error);
171 
172   g_assert_cmpint (iterations_requested, >, iterations_done);
173   num_async_operations--;
174   g_main_context_wakeup (NULL);
175 }
176 
177 static void
test_cancel_multiple_concurrent(void)178 test_cancel_multiple_concurrent (void)
179 {
180   GCancellable *cancellable;
181   guint i, iterations;
182 
183   if (!g_test_thorough ())
184     {
185       g_test_skip ("Not running timing heavy test");
186       return;
187     }
188 
189   cancellable = g_cancellable_new ();
190 
191   for (i = 0; i < 45; i++)
192     {
193       iterations = i + 10;
194       mock_operation_async (iterations, g_random_boolean (), cancellable,
195                             on_mock_operation_ready, GUINT_TO_POINTER (iterations));
196       num_async_operations++;
197     }
198 
199   /* Wait for the threads to start up */
200   while (num_async_operations != 45)
201     g_main_context_iteration (NULL, TRUE);
202   g_assert_cmpint (num_async_operations, ==, 45);\
203 
204   if (g_test_verbose ())
205     g_test_message ("CANCEL: %d operations", num_async_operations);
206   g_cancellable_cancel (cancellable);
207   g_assert_true (g_cancellable_is_cancelled (cancellable));
208 
209   /* Wait for all operations to be cancelled */
210   while (num_async_operations != 0)
211     g_main_context_iteration (NULL, TRUE);
212   g_assert_cmpint (num_async_operations, ==, 0);
213 
214   g_object_unref (cancellable);
215 }
216 
217 static void
test_cancel_null(void)218 test_cancel_null (void)
219 {
220   g_cancellable_cancel (NULL);
221 }
222 
223 typedef struct
224 {
225   GCond cond;
226   GMutex mutex;
227   gboolean thread_ready;
228   GAsyncQueue *cancellable_source_queue;  /* (owned) (element-type GCancellableSource) */
229 } ThreadedDisposeData;
230 
231 static gboolean
cancelled_cb(GCancellable * cancellable,gpointer user_data)232 cancelled_cb (GCancellable *cancellable,
233               gpointer      user_data)
234 {
235   /* Nothing needs to be done here. */
236   return G_SOURCE_CONTINUE;
237 }
238 
239 static gpointer
threaded_dispose_thread_cb(gpointer user_data)240 threaded_dispose_thread_cb (gpointer user_data)
241 {
242   ThreadedDisposeData *data = user_data;
243   GSource *cancellable_source;
244 
245   g_mutex_lock (&data->mutex);
246   data->thread_ready = TRUE;
247   g_cond_broadcast (&data->cond);
248   g_mutex_unlock (&data->mutex);
249 
250   while ((cancellable_source = g_async_queue_pop (data->cancellable_source_queue)) != (gpointer) 1)
251     {
252       /* Race with cancellation of the cancellable. */
253       g_source_unref (cancellable_source);
254     }
255 
256   return NULL;
257 }
258 
259 static void
test_cancellable_source_threaded_dispose(void)260 test_cancellable_source_threaded_dispose (void)
261 {
262 #ifdef _GLIB_ADDRESS_SANITIZER
263   g_test_incomplete ("FIXME: Leaks lots of GCancellableSource objects, see glib#2309");
264   (void) cancelled_cb;
265   (void) threaded_dispose_thread_cb;
266 #else
267   ThreadedDisposeData data;
268   GThread *thread = NULL;
269   guint i;
270   GPtrArray *cancellables_pending_unref = g_ptr_array_new_with_free_func (g_object_unref);
271 
272   g_test_summary ("Test a thread race between disposing of a GCancellableSource "
273                   "(in one thread) and cancelling the GCancellable it refers "
274                   "to (in another thread)");
275   g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/1841");
276 
277   /* Create a new thread and wait until it’s ready to execute. Each iteration of
278    * the test will pass it a new #GCancellableSource. */
279   g_cond_init (&data.cond);
280   g_mutex_init (&data.mutex);
281   data.cancellable_source_queue = g_async_queue_new_full ((GDestroyNotify) g_source_unref);
282   data.thread_ready = FALSE;
283 
284   g_mutex_lock (&data.mutex);
285   thread = g_thread_new ("/cancellable-source/threaded-dispose",
286                          threaded_dispose_thread_cb, &data);
287 
288   while (!data.thread_ready)
289     g_cond_wait (&data.cond, &data.mutex);
290   g_mutex_unlock (&data.mutex);
291 
292   for (i = 0; i < 100000; i++)
293     {
294       GCancellable *cancellable = NULL;
295       GSource *cancellable_source = NULL;
296 
297       /* Create a cancellable and a cancellable source for it. For this test,
298        * there’s no need to attach the source to a #GMainContext. */
299       cancellable = g_cancellable_new ();
300       cancellable_source = g_cancellable_source_new (cancellable);
301       g_source_set_callback (cancellable_source, G_SOURCE_FUNC (cancelled_cb), NULL, NULL);
302 
303       /* Send it to the thread and wait until it’s ready to execute before
304        * cancelling our cancellable. */
305       g_async_queue_push (data.cancellable_source_queue, g_steal_pointer (&cancellable_source));
306 
307       /* Race with disposal of the cancellable source. */
308       g_cancellable_cancel (cancellable);
309 
310       /* This thread can’t drop its reference to the #GCancellable here, as it
311        * might not be the final reference (depending on how the race is
312        * resolved: #GCancellableSource holds a strong ref on the #GCancellable),
313        * and at this point we can’t guarantee to support disposing of a
314        * #GCancellable in a different thread from where it’s created, especially
315        * when signal handlers are connected to it.
316        *
317        * So this is a workaround for a disposal-in-another-thread bug for
318        * #GCancellable, but there’s no hope of debugging and resolving it with
319        * this test setup, and the bug is orthogonal to what’s being tested here
320        * (a race between #GCancellable and #GCancellableSource). */
321       g_ptr_array_add (cancellables_pending_unref, g_steal_pointer (&cancellable));
322     }
323 
324   /* Indicate that the test has finished. Can’t use %NULL as #GAsyncQueue
325    * doesn’t allow that.*/
326   g_async_queue_push (data.cancellable_source_queue, (gpointer) 1);
327 
328   g_thread_join (g_steal_pointer (&thread));
329 
330   g_assert (g_async_queue_length (data.cancellable_source_queue) == 0);
331   g_async_queue_unref (data.cancellable_source_queue);
332   g_mutex_clear (&data.mutex);
333   g_cond_clear (&data.cond);
334 
335   g_ptr_array_unref (cancellables_pending_unref);
336 #endif
337 }
338 
339 int
main(int argc,char * argv[])340 main (int argc, char *argv[])
341 {
342   g_test_init (&argc, &argv, NULL);
343 
344   g_test_add_func ("/cancellable/multiple-concurrent", test_cancel_multiple_concurrent);
345   g_test_add_func ("/cancellable/null", test_cancel_null);
346   g_test_add_func ("/cancellable-source/threaded-dispose", test_cancellable_source_threaded_dispose);
347 
348   return g_test_run ();
349 }
350