1/* Regression test for thread-safe reference-counting
2 *
3 * Author: Simon McVittie <simon.mcvittie@collabora.co.uk>
4 * Copyright © 2011 Nokia Corporation
5 *
6 * Permission is hereby granted, free of charge, to any person
7 * obtaining a copy of this software and associated documentation files
8 * (the "Software"), to deal in the Software without restriction,
9 * including without limitation the rights to use, copy, modify, merge,
10 * publish, distribute, sublicense, and/or sell copies of the Software,
11 * and to permit persons to whom the Software is furnished to do so,
12 * subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26
27#include <config.h>
28
29#include <glib.h>
30#include <glib-object.h>
31
32#define DBUS_COMPILATION    /* this test uses libdbus-internal */
33#include <dbus/dbus.h>
34#include <dbus/dbus-connection-internal.h>
35#include <dbus/dbus-mainloop.h>
36#include <dbus/dbus-message-internal.h>
37#include <dbus/dbus-pending-call-internal.h>
38#include <dbus/dbus-server-protected.h>
39#include "test-utils.h"
40
41static void
42assert_no_error (const DBusError *e)
43{
44  if (G_UNLIKELY (dbus_error_is_set (e)))
45    g_error ("expected success but got error: %s: %s", e->name, e->message);
46}
47
48#define N_THREADS 200
49#define N_REFS 10000
50G_STATIC_ASSERT (((unsigned) N_THREADS * (unsigned) N_REFS) < G_MAXINT32);
51
52static dbus_int32_t connection_slot = -1;
53static dbus_int32_t server_slot = -1;
54static dbus_int32_t message_slot = -1;
55static dbus_int32_t pending_call_slot = -1;
56
57typedef struct {
58  DBusError e;
59  DBusLoop *loop;
60  DBusServer *server;
61  DBusConnection *connection;
62  DBusConnection *server_connection;
63  DBusMessage *message;
64  GThread *threads[N_THREADS];
65  gboolean last_unref;
66} Fixture;
67
68typedef void *(*RefFunc) (void *);
69typedef void (*VoidFunc) (void *);
70
71typedef struct {
72  void *thing;
73  RefFunc ref;
74  VoidFunc ref_void;
75  VoidFunc unref;
76  void *mutex;
77  VoidFunc lock;
78  VoidFunc unlock;
79} Thread;
80
81/* provide backwards compatibility shim when building with a glib <= 2.30.x */
82#if !GLIB_CHECK_VERSION(2,31,0)
83#define g_thread_new(name,func,data) g_thread_create(func,data,TRUE,NULL)
84#endif
85
86static gpointer
87ref_thread (gpointer data)
88{
89  Thread *thread = data;
90  int i;
91
92  for (i = 0; i < N_REFS; i++)
93    {
94      if (thread->lock != NULL)
95        (thread->lock) (thread->mutex);
96
97      if (thread->ref != NULL)
98        {
99          gpointer ret = (thread->ref) (thread->thing);
100
101          g_assert (ret == thread->thing);
102        }
103      else
104        {
105          (thread->ref_void) (thread->thing);
106        }
107
108      if (thread->unlock != NULL)
109        (thread->unlock) (thread->mutex);
110    }
111
112  return NULL;
113}
114
115static gpointer
116cycle_thread (gpointer data)
117{
118  Thread *thread = data;
119  int i;
120
121  for (i = 0; i < N_REFS; i++)
122    {
123      if (thread->lock != NULL)
124        (thread->lock) (thread->mutex);
125
126      if (thread->ref != NULL)
127        {
128          gpointer ret = (thread->ref) (thread->thing);
129
130          g_assert (ret == thread->thing);
131        }
132      else
133        {
134          (thread->ref_void) (thread->thing);
135        }
136
137      (thread->unref) (thread->thing);
138
139      if (thread->unlock != NULL)
140        (thread->unlock) (thread->mutex);
141    }
142
143  return NULL;
144}
145
146static gpointer
147unref_thread (gpointer data)
148{
149  Thread *thread = data;
150  int i;
151
152  for (i = 0; i < N_REFS; i++)
153    {
154      if (thread->lock != NULL)
155        (thread->lock) (thread->mutex);
156
157      (thread->unref) (thread->thing);
158
159      if (thread->unlock != NULL)
160        (thread->unlock) (thread->mutex);
161    }
162
163  return NULL;
164}
165
166static void
167last_unref (void *data)
168{
169  Fixture *f = data;
170
171  g_assert (!f->last_unref);
172  f->last_unref = TRUE;
173}
174
175static void
176wait_for_all_threads (Fixture *f)
177{
178  int i;
179
180  for (i = 0; i < N_THREADS; i++)
181    g_thread_join (f->threads[i]);
182}
183
184static void
185new_conn_cb (DBusServer *server,
186    DBusConnection *server_connection,
187    void *data)
188{
189  Fixture *f = data;
190
191  g_assert (f->server_connection == NULL);
192  f->server_connection = dbus_connection_ref (server_connection);
193
194  test_connection_setup (f->loop, f->server_connection);
195}
196
197static void
198setup (Fixture *f,
199    gconstpointer data)
200{
201  if (!dbus_threads_init_default ())
202    g_error ("OOM");
203
204  f->loop = _dbus_loop_new ();
205  g_assert (f->loop != NULL);
206
207  dbus_error_init (&f->e);
208
209  f->server = dbus_server_listen ("tcp:host=127.0.0.1", &f->e);
210  assert_no_error (&f->e);
211  g_assert (f->server != NULL);
212
213  if (!dbus_connection_allocate_data_slot (&connection_slot))
214    g_error ("OOM");
215
216  if (!dbus_server_allocate_data_slot (&server_slot))
217    g_error ("OOM");
218
219  if (!dbus_message_allocate_data_slot (&message_slot))
220    g_error ("OOM");
221
222  if (!dbus_pending_call_allocate_data_slot (&pending_call_slot))
223    g_error ("OOM");
224}
225
226static void
227setup_connection (Fixture *f,
228    gconstpointer data)
229{
230  char *address;
231
232  setup (f, data);
233
234  dbus_server_set_new_connection_function (f->server,
235      new_conn_cb, f, NULL);
236
237  if (!test_server_setup (f->loop, f->server))
238    g_error ("failed to set up server");
239
240  address = dbus_server_get_address (f->server);
241  g_assert (address != NULL);
242  f->connection = dbus_connection_open_private (address, &f->e);
243  assert_no_error (&f->e);
244  g_assert (f->connection != NULL);
245  dbus_free (address);
246
247  if (!test_connection_setup (f->loop, f->connection))
248    g_error ("failed to set up connection");
249
250  while (f->server_connection == NULL)
251    _dbus_loop_iterate (f->loop, TRUE);
252
253  test_connection_shutdown (f->loop, f->connection);
254  test_server_shutdown (f->loop, f->server);
255}
256
257static void
258test_connection (Fixture *f,
259    gconstpointer data)
260{
261  Thread public_api = { f->connection,
262    (RefFunc) dbus_connection_ref,
263    NULL,
264    (VoidFunc) dbus_connection_unref,
265    NULL,
266    NULL,
267    NULL };
268  Thread internal_api = { f->connection,
269    (RefFunc) _dbus_connection_ref_unlocked,
270    NULL,
271    (VoidFunc) _dbus_connection_unref_unlocked,
272    f->connection,
273    (VoidFunc) _dbus_connection_lock,
274    (VoidFunc) _dbus_connection_unlock };
275  int i;
276
277  /* Use a slot as a pseudo-weakref */
278  if (!dbus_connection_set_data (f->connection, connection_slot, f,
279        last_unref))
280    g_error ("OOM");
281
282  for (i = 0; i < N_THREADS; i++)
283    {
284      if ((i % 2) == 0)
285        f->threads[i] = g_thread_new (NULL, ref_thread, &public_api);
286      else
287        f->threads[i] = g_thread_new (NULL, ref_thread, &internal_api);
288
289      g_assert (f->threads[i] != NULL);
290    }
291
292  wait_for_all_threads (f);
293
294  for (i = 0; i < N_THREADS; i++)
295    {
296      if ((i % 2) == 0)
297        f->threads[i] = g_thread_new (NULL, cycle_thread, &public_api);
298      else
299        f->threads[i] = g_thread_new (NULL, cycle_thread, &internal_api);
300
301      g_assert (f->threads[i] != NULL);
302    }
303
304  wait_for_all_threads (f);
305
306  for (i = 0; i < N_THREADS; i++)
307    {
308      if ((i % 2) == 0)
309        f->threads[i] = g_thread_new (NULL, unref_thread, &public_api);
310      else
311        f->threads[i] = g_thread_new (NULL, unref_thread, &internal_api);
312
313      g_assert (f->threads[i] != NULL);
314    }
315
316  wait_for_all_threads (f);
317
318  /* Destroy the connection. This should be the last-unref. */
319  g_assert (!f->last_unref);
320  dbus_connection_close (f->connection);
321  dbus_connection_unref (f->connection);
322  f->connection = NULL;
323  g_assert (f->last_unref);
324}
325
326static void
327server_lock (void *server)
328{
329  SERVER_LOCK (((DBusServer *) server));
330}
331
332static void
333server_unlock (void *server)
334{
335  SERVER_UNLOCK (((DBusServer *) server));
336}
337
338static void
339test_server (Fixture *f,
340    gconstpointer data)
341{
342  Thread public_api = { f->server,
343    (RefFunc) dbus_server_ref,
344    NULL,
345    (VoidFunc) dbus_server_unref,
346    NULL,
347    NULL,
348    NULL };
349  Thread internal_api = { f->server,
350    NULL,
351    (VoidFunc) _dbus_server_ref_unlocked,
352    (VoidFunc) _dbus_server_unref_unlocked,
353    f->server,
354    server_lock,
355    server_unlock };
356  int i;
357
358  if (!dbus_server_set_data (f->server, server_slot, f, last_unref))
359    g_error ("OOM");
360
361  for (i = 0; i < N_THREADS; i++)
362    {
363      if ((i % 2) == 0)
364        f->threads[i] = g_thread_new (NULL, ref_thread, &public_api);
365      else
366        f->threads[i] = g_thread_new (NULL, ref_thread, &internal_api);
367
368      g_assert (f->threads[i] != NULL);
369    }
370
371  wait_for_all_threads (f);
372
373  for (i = 0; i < N_THREADS; i++)
374    {
375      if ((i % 2) == 0)
376        f->threads[i] = g_thread_new (NULL, cycle_thread, &public_api);
377      else
378        f->threads[i] = g_thread_new (NULL, cycle_thread, &internal_api);
379
380      g_assert (f->threads[i] != NULL);
381    }
382
383  wait_for_all_threads (f);
384
385  for (i = 0; i < N_THREADS; i++)
386    {
387      if ((i % 2) == 0)
388        f->threads[i] = g_thread_new (NULL, unref_thread, &public_api);
389      else
390        f->threads[i] = g_thread_new (NULL, unref_thread, &internal_api);
391
392      g_assert (f->threads[i] != NULL);
393    }
394
395  wait_for_all_threads (f);
396
397  /* Destroy the server. This should be the last-unref. */
398  g_assert (!f->last_unref);
399  dbus_server_disconnect (f->server);
400  dbus_server_unref (f->server);
401  f->server = NULL;
402  g_assert (f->last_unref);
403}
404
405static void
406test_message (Fixture *f,
407    gconstpointer data)
408{
409  DBusMessage *message = dbus_message_new_signal ("/foo", "foo.bar.baz",
410      "Foo");
411  Thread public_api = { message,
412    (RefFunc) dbus_message_ref,
413    NULL,
414    (VoidFunc) dbus_message_unref,
415    NULL,
416    NULL,
417    NULL };
418  int i;
419
420  if (!dbus_message_set_data (message, message_slot, f, last_unref))
421    g_error ("OOM");
422
423  for (i = 0; i < N_THREADS; i++)
424    {
425      f->threads[i] = g_thread_new (NULL, ref_thread, &public_api);
426      g_assert (f->threads[i] != NULL);
427    }
428
429  wait_for_all_threads (f);
430
431  for (i = 0; i < N_THREADS; i++)
432    {
433      f->threads[i] = g_thread_new (NULL, cycle_thread, &public_api);
434      g_assert (f->threads[i] != NULL);
435    }
436
437  wait_for_all_threads (f);
438
439  for (i = 0; i < N_THREADS; i++)
440    {
441      f->threads[i] = g_thread_new (NULL, unref_thread, &public_api);
442      g_assert (f->threads[i] != NULL);
443    }
444
445  wait_for_all_threads (f);
446
447  /* Destroy the server. This should be the last-unref. */
448  g_assert (!f->last_unref);
449  dbus_message_unref (message);
450  g_assert (f->last_unref);
451}
452
453static void
454test_pending_call (Fixture *f,
455    gconstpointer data)
456{
457  Thread public_api = { NULL,
458    (RefFunc) dbus_pending_call_ref,
459    NULL,
460    (VoidFunc) dbus_pending_call_unref,
461    NULL,
462    NULL,
463    NULL };
464  Thread internal_api = { NULL,
465    (RefFunc) _dbus_pending_call_ref_unlocked,
466    NULL,
467    (VoidFunc) dbus_pending_call_unref,
468    f->connection,
469    (VoidFunc) _dbus_connection_lock,
470    (VoidFunc) _dbus_connection_unlock };
471  /* This one can't be used to ref, only to cycle or unref. */
472  Thread unref_and_unlock_api = { NULL,
473    (RefFunc) _dbus_pending_call_ref_unlocked,
474    NULL,
475    (VoidFunc) _dbus_pending_call_unref_and_unlock,
476    f->connection,
477    (VoidFunc) _dbus_connection_lock,
478    NULL };
479  int i;
480  DBusPendingCall *pending_call;
481
482  _dbus_connection_lock (f->connection);
483  pending_call = _dbus_pending_call_new_unlocked (f->connection,
484      DBUS_TIMEOUT_INFINITE, NULL);
485  g_assert (pending_call != NULL);
486  _dbus_connection_unlock (f->connection);
487
488  public_api.thing = pending_call;
489  internal_api.thing = pending_call;
490  unref_and_unlock_api.thing = pending_call;
491
492  if (!dbus_pending_call_set_data (pending_call, pending_call_slot, f,
493        last_unref))
494    g_error ("OOM");
495
496  for (i = 0; i < N_THREADS; i++)
497    {
498      if ((i % 2) == 0)
499        f->threads[i] = g_thread_new (NULL, ref_thread, &public_api);
500      else
501        f->threads[i] = g_thread_new (NULL, ref_thread, &internal_api);
502
503      g_assert (f->threads[i] != NULL);
504    }
505
506  wait_for_all_threads (f);
507
508  for (i = 0; i < N_THREADS; i++)
509    {
510      switch (i % 3)
511        {
512          case 0:
513            f->threads[i] = g_thread_new (NULL, cycle_thread, &public_api);
514            break;
515          case 1:
516            f->threads[i] = g_thread_new (NULL, cycle_thread, &internal_api);
517            break;
518          default:
519            f->threads[i] = g_thread_new (NULL, cycle_thread,
520                &unref_and_unlock_api);
521        }
522
523      g_assert (f->threads[i] != NULL);
524    }
525
526  wait_for_all_threads (f);
527
528  for (i = 0; i < N_THREADS; i++)
529    {
530      switch (i % 3)
531        {
532          case 0:
533            f->threads[i] = g_thread_new (NULL, unref_thread, &public_api);
534            break;
535          case 1:
536            f->threads[i] = g_thread_new (NULL, unref_thread, &internal_api);
537            break;
538          default:
539            f->threads[i] = g_thread_new (NULL, unref_thread,
540                &unref_and_unlock_api);
541        }
542
543      g_assert (f->threads[i] != NULL);
544    }
545
546  wait_for_all_threads (f);
547
548  /* Destroy the pending call. This should be the last-unref. */
549  g_assert (!f->last_unref);
550  dbus_pending_call_unref (pending_call);
551  g_assert (f->last_unref);
552}
553
554static void
555teardown (Fixture *f,
556    gconstpointer data)
557{
558  if (f->server_connection != NULL)
559    {
560      dbus_connection_close (f->server_connection);
561      dbus_connection_unref (f->server_connection);
562    }
563
564  if (f->connection != NULL)
565    {
566      dbus_connection_close (f->connection);
567      dbus_connection_unref (f->connection);
568    }
569
570  if (f->server != NULL)
571    {
572      dbus_server_disconnect (f->server);
573      dbus_server_unref (f->server);
574    }
575
576  dbus_connection_free_data_slot (&connection_slot);
577  dbus_server_free_data_slot (&server_slot);
578  dbus_message_free_data_slot (&message_slot);
579  dbus_pending_call_free_data_slot (&pending_call_slot);
580
581  _dbus_loop_unref (f->loop);
582  dbus_error_free (&f->e);
583}
584
585int
586main (int argc,
587    char **argv)
588{
589  /* In GLib >= 2.24, < 2.31 this acts like g_thread_init() but avoids
590   * the deprecation of that function. In GLib >= 2.32 this is not
591   * necessary at all.
592   */
593  g_type_init ();
594
595  g_test_init (&argc, &argv, NULL);
596  g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id=");
597
598  g_test_add ("/refs/connection", Fixture, NULL, setup_connection,
599      test_connection, teardown);
600  g_test_add ("/refs/message", Fixture, NULL, setup,
601      test_message, teardown);
602  g_test_add ("/refs/pending-call", Fixture, NULL, setup_connection,
603      test_pending_call, teardown);
604  g_test_add ("/refs/server", Fixture, NULL, setup,
605      test_server, teardown);
606
607  return g_test_run ();
608}
609