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