Home | History | Annotate | Download | only in internals
      1 /* Regression test for thread-safe reference-counting
      2  *
      3  * Author: Simon McVittie <simon.mcvittie (at) 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 
     41 static void
     42 assert_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
     50 G_STATIC_ASSERT (((unsigned) N_THREADS * (unsigned) N_REFS) < G_MAXINT32);
     51 
     52 static dbus_int32_t connection_slot = -1;
     53 static dbus_int32_t server_slot = -1;
     54 static dbus_int32_t message_slot = -1;
     55 static dbus_int32_t pending_call_slot = -1;
     56 
     57 typedef 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 
     68 typedef void *(*RefFunc) (void *);
     69 typedef void (*VoidFunc) (void *);
     70 
     71 typedef 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 
     86 static gpointer
     87 ref_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 
    115 static gpointer
    116 cycle_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 
    146 static gpointer
    147 unref_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 
    166 static void
    167 last_unref (void *data)
    168 {
    169   Fixture *f = data;
    170 
    171   g_assert (!f->last_unref);
    172   f->last_unref = TRUE;
    173 }
    174 
    175 static void
    176 wait_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 
    184 static void
    185 new_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 
    197 static void
    198 setup (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 
    226 static void
    227 setup_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 
    257 static void
    258 test_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 
    326 static void
    327 server_lock (void *server)
    328 {
    329   SERVER_LOCK (((DBusServer *) server));
    330 }
    331 
    332 static void
    333 server_unlock (void *server)
    334 {
    335   SERVER_UNLOCK (((DBusServer *) server));
    336 }
    337 
    338 static void
    339 test_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 
    405 static void
    406 test_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 
    453 static void
    454 test_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 
    554 static void
    555 teardown (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 
    585 int
    586 main (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