1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* dbus-threads.h D-Bus threads handling
3 *
4 * Copyright (C) 2002, 2003, 2006 Red Hat Inc.
5 *
6 * Licensed under the Academic Free License version 2.1
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 *
22 */
23 #include <config.h>
24 #include "dbus-threads.h"
25 #include "dbus-internals.h"
26 #include "dbus-threads-internal.h"
27 #include "dbus-list.h"
28
29 static int thread_init_generation = 0;
30
31 static DBusList *uninitialized_rmutex_list = NULL;
32 static DBusList *uninitialized_cmutex_list = NULL;
33 static DBusList *uninitialized_condvar_list = NULL;
34
35 /** This is used for the no-op default mutex pointer, just to be distinct from #NULL */
36 #define _DBUS_DUMMY_MUTEX ((DBusMutex*)0xABCDEF)
37 #define _DBUS_DUMMY_RMUTEX ((DBusRMutex *) _DBUS_DUMMY_MUTEX)
38 #define _DBUS_DUMMY_CMUTEX ((DBusCMutex *) _DBUS_DUMMY_MUTEX)
39
40 /** This is used for the no-op default mutex pointer, just to be distinct from #NULL */
41 #define _DBUS_DUMMY_CONDVAR ((DBusCondVar*)0xABCDEF2)
42
43 /**
44 * @defgroup DBusThreadsInternals Thread functions
45 * @ingroup DBusInternals
46 * @brief _dbus_rmutex_lock(), etc.
47 *
48 * Functions and macros related to threads and thread locks.
49 *
50 * @{
51 */
52
53 /**
54 * Creates a new mutex
55 * or creates a no-op mutex if threads are not initialized.
56 * May return #NULL even if threads are initialized, indicating
57 * out-of-memory.
58 *
59 * If possible, the mutex returned by this function is recursive, to
60 * avoid deadlocks. However, that cannot be relied on.
61 *
62 * The extra level of indirection given by allocating a pointer
63 * to point to the mutex location allows the threading
64 * module to swap out dummy mutexes for a real mutex so libraries
65 * can initialize threads even after the D-Bus API has been used.
66 *
67 * @param location_p the location of the new mutex, can return #NULL on OOM
68 */
69 void
_dbus_rmutex_new_at_location(DBusRMutex ** location_p)70 _dbus_rmutex_new_at_location (DBusRMutex **location_p)
71 {
72 _dbus_assert (location_p != NULL);
73
74 if (thread_init_generation == _dbus_current_generation)
75 {
76 *location_p = _dbus_platform_rmutex_new ();
77 }
78 else
79 {
80 *location_p = _DBUS_DUMMY_RMUTEX;
81
82 if (!_dbus_list_append (&uninitialized_rmutex_list, location_p))
83 *location_p = NULL;
84 }
85 }
86
87 /**
88 * Creates a new mutex
89 * or creates a no-op mutex if threads are not initialized.
90 * May return #NULL even if threads are initialized, indicating
91 * out-of-memory.
92 *
93 * The returned mutex is suitable for use with condition variables.
94 *
95 * The extra level of indirection given by allocating a pointer
96 * to point to the mutex location allows the threading
97 * module to swap out dummy mutexes for a real mutex so libraries
98 * can initialize threads even after the D-Bus API has been used.
99 *
100 * @param location_p the location of the new mutex, can return #NULL on OOM
101 */
102 void
_dbus_cmutex_new_at_location(DBusCMutex ** location_p)103 _dbus_cmutex_new_at_location (DBusCMutex **location_p)
104 {
105 _dbus_assert (location_p != NULL);
106
107 if (thread_init_generation == _dbus_current_generation)
108 {
109 *location_p = _dbus_platform_cmutex_new ();
110 }
111 else
112 {
113 *location_p = _DBUS_DUMMY_CMUTEX;
114
115 if (!_dbus_list_append (&uninitialized_cmutex_list, location_p))
116 *location_p = NULL;
117 }
118 }
119
120 /**
121 * Frees a DBusRMutex or removes it from the uninitialized mutex list;
122 * does nothing if passed a #NULL pointer.
123 */
124 void
_dbus_rmutex_free_at_location(DBusRMutex ** location_p)125 _dbus_rmutex_free_at_location (DBusRMutex **location_p)
126 {
127 if (location_p == NULL)
128 return;
129
130 if (thread_init_generation == _dbus_current_generation)
131 {
132 if (*location_p != NULL)
133 _dbus_platform_rmutex_free (*location_p);
134 }
135 else
136 {
137 _dbus_assert (*location_p == NULL || *location_p == _DBUS_DUMMY_RMUTEX);
138
139 _dbus_list_remove (&uninitialized_rmutex_list, location_p);
140 }
141 }
142
143 /**
144 * Frees a DBusCMutex and removes it from the
145 * uninitialized mutex list;
146 * does nothing if passed a #NULL pointer.
147 */
148 void
_dbus_cmutex_free_at_location(DBusCMutex ** location_p)149 _dbus_cmutex_free_at_location (DBusCMutex **location_p)
150 {
151 if (location_p == NULL)
152 return;
153
154 if (thread_init_generation == _dbus_current_generation)
155 {
156 if (*location_p != NULL)
157 _dbus_platform_cmutex_free (*location_p);
158 }
159 else
160 {
161 _dbus_assert (*location_p == NULL || *location_p == _DBUS_DUMMY_CMUTEX);
162
163 _dbus_list_remove (&uninitialized_cmutex_list, location_p);
164 }
165 }
166
167 /**
168 * Locks a mutex. Does nothing if passed a #NULL pointer.
169 * Locks may be recursive if threading implementation initialized
170 * recursive locks.
171 */
172 void
_dbus_rmutex_lock(DBusRMutex * mutex)173 _dbus_rmutex_lock (DBusRMutex *mutex)
174 {
175 if (mutex && thread_init_generation == _dbus_current_generation)
176 _dbus_platform_rmutex_lock (mutex);
177 }
178
179 /**
180 * Locks a mutex. Does nothing if passed a #NULL pointer.
181 * Locks may be recursive if threading implementation initialized
182 * recursive locks.
183 */
184 void
_dbus_cmutex_lock(DBusCMutex * mutex)185 _dbus_cmutex_lock (DBusCMutex *mutex)
186 {
187 if (mutex && thread_init_generation == _dbus_current_generation)
188 _dbus_platform_cmutex_lock (mutex);
189 }
190
191 /**
192 * Unlocks a mutex. Does nothing if passed a #NULL pointer.
193 *
194 * @returns #TRUE on success
195 */
196 void
_dbus_rmutex_unlock(DBusRMutex * mutex)197 _dbus_rmutex_unlock (DBusRMutex *mutex)
198 {
199 if (mutex && thread_init_generation == _dbus_current_generation)
200 _dbus_platform_rmutex_unlock (mutex);
201 }
202
203 /**
204 * Unlocks a mutex. Does nothing if passed a #NULL pointer.
205 *
206 * @returns #TRUE on success
207 */
208 void
_dbus_cmutex_unlock(DBusCMutex * mutex)209 _dbus_cmutex_unlock (DBusCMutex *mutex)
210 {
211 if (mutex && thread_init_generation == _dbus_current_generation)
212 _dbus_platform_cmutex_unlock (mutex);
213 }
214
215 /**
216 * Creates a new condition variable using the function supplied
217 * to dbus_threads_init(), or creates a no-op condition variable
218 * if threads are not initialized. May return #NULL even if
219 * threads are initialized, indicating out-of-memory.
220 *
221 * @returns new mutex or #NULL
222 */
223 DBusCondVar *
_dbus_condvar_new(void)224 _dbus_condvar_new (void)
225 {
226 if (thread_init_generation == _dbus_current_generation)
227 return _dbus_platform_condvar_new ();
228 else
229 return _DBUS_DUMMY_CONDVAR;
230 }
231
232
233 /**
234 * This does the same thing as _dbus_condvar_new. It however
235 * gives another level of indirection by allocating a pointer
236 * to point to the condvar location. This allows the threading
237 * module to swap out dummy condvars for a real condvar so libraries
238 * can initialize threads even after the D-Bus API has been used.
239 *
240 * @returns the location of a new condvar or #NULL on OOM
241 */
242
243 void
_dbus_condvar_new_at_location(DBusCondVar ** location_p)244 _dbus_condvar_new_at_location (DBusCondVar **location_p)
245 {
246 _dbus_assert (location_p != NULL);
247
248 if (thread_init_generation == _dbus_current_generation)
249 {
250 *location_p = _dbus_condvar_new();
251 }
252 else
253 {
254 *location_p = _DBUS_DUMMY_CONDVAR;
255
256 if (!_dbus_list_append (&uninitialized_condvar_list, location_p))
257 *location_p = NULL;
258 }
259 }
260
261
262 /**
263 * Frees a conditional variable created with dbus_condvar_new(); does
264 * nothing if passed a #NULL pointer.
265 */
266 void
_dbus_condvar_free(DBusCondVar * cond)267 _dbus_condvar_free (DBusCondVar *cond)
268 {
269 if (cond && thread_init_generation == _dbus_current_generation)
270 _dbus_platform_condvar_free (cond);
271 }
272
273 /**
274 * Frees a conditional variable and removes it from the
275 * uninitialized_condvar_list;
276 * does nothing if passed a #NULL pointer.
277 */
278 void
_dbus_condvar_free_at_location(DBusCondVar ** location_p)279 _dbus_condvar_free_at_location (DBusCondVar **location_p)
280 {
281 if (location_p == NULL)
282 return;
283
284 if (thread_init_generation == _dbus_current_generation)
285 {
286 if (*location_p != NULL)
287 _dbus_platform_condvar_free (*location_p);
288 }
289 else
290 {
291 _dbus_assert (*location_p == NULL || *location_p == _DBUS_DUMMY_CONDVAR);
292
293 _dbus_list_remove (&uninitialized_condvar_list, location_p);
294 }
295 }
296
297 /**
298 * Atomically unlocks the mutex and waits for the conditions
299 * variable to be signalled. Locks the mutex again before
300 * returning.
301 * Does nothing if passed a #NULL pointer.
302 */
303 void
_dbus_condvar_wait(DBusCondVar * cond,DBusCMutex * mutex)304 _dbus_condvar_wait (DBusCondVar *cond,
305 DBusCMutex *mutex)
306 {
307 if (cond && mutex && thread_init_generation == _dbus_current_generation)
308 _dbus_platform_condvar_wait (cond, mutex);
309 }
310
311 /**
312 * Atomically unlocks the mutex and waits for the conditions variable
313 * to be signalled, or for a timeout. Locks the mutex again before
314 * returning. Does nothing if passed a #NULL pointer. Return value
315 * is #FALSE if we timed out, #TRUE otherwise.
316 *
317 * @param cond the condition variable
318 * @param mutex the mutex
319 * @param timeout_milliseconds the maximum time to wait
320 * @returns #FALSE if the timeout occurred, #TRUE if not
321 */
322 dbus_bool_t
_dbus_condvar_wait_timeout(DBusCondVar * cond,DBusCMutex * mutex,int timeout_milliseconds)323 _dbus_condvar_wait_timeout (DBusCondVar *cond,
324 DBusCMutex *mutex,
325 int timeout_milliseconds)
326 {
327 if (cond && mutex && thread_init_generation == _dbus_current_generation)
328 return _dbus_platform_condvar_wait_timeout (cond, mutex,
329 timeout_milliseconds);
330 else
331 return TRUE;
332 }
333
334 /**
335 * If there are threads waiting on the condition variable, wake
336 * up exactly one.
337 * Does nothing if passed a #NULL pointer.
338 */
339 void
_dbus_condvar_wake_one(DBusCondVar * cond)340 _dbus_condvar_wake_one (DBusCondVar *cond)
341 {
342 if (cond && thread_init_generation == _dbus_current_generation)
343 _dbus_platform_condvar_wake_one (cond);
344 }
345
346 static void
shutdown_global_locks(void * data)347 shutdown_global_locks (void *data)
348 {
349 DBusRMutex ***locks = data;
350 int i;
351
352 i = 0;
353 while (i < _DBUS_N_GLOBAL_LOCKS)
354 {
355 if (*(locks[i]) != NULL)
356 _dbus_platform_rmutex_free (*(locks[i]));
357
358 *(locks[i]) = NULL;
359 ++i;
360 }
361
362 dbus_free (locks);
363 }
364
365 static void
shutdown_uninitialized_locks(void * data)366 shutdown_uninitialized_locks (void *data)
367 {
368 _dbus_list_clear (&uninitialized_rmutex_list);
369 _dbus_list_clear (&uninitialized_cmutex_list);
370 _dbus_list_clear (&uninitialized_condvar_list);
371 }
372
373 static dbus_bool_t
init_uninitialized_locks(void)374 init_uninitialized_locks (void)
375 {
376 DBusList *link;
377
378 _dbus_assert (thread_init_generation != _dbus_current_generation);
379
380 link = uninitialized_rmutex_list;
381 while (link != NULL)
382 {
383 DBusRMutex **mp;
384
385 mp = link->data;
386 _dbus_assert (*mp == _DBUS_DUMMY_RMUTEX);
387
388 *mp = _dbus_platform_rmutex_new ();
389 if (*mp == NULL)
390 goto fail_mutex;
391
392 link = _dbus_list_get_next_link (&uninitialized_rmutex_list, link);
393 }
394
395 link = uninitialized_cmutex_list;
396 while (link != NULL)
397 {
398 DBusCMutex **mp;
399
400 mp = link->data;
401 _dbus_assert (*mp == _DBUS_DUMMY_CMUTEX);
402
403 *mp = _dbus_platform_cmutex_new ();
404 if (*mp == NULL)
405 goto fail_mutex;
406
407 link = _dbus_list_get_next_link (&uninitialized_cmutex_list, link);
408 }
409
410 link = uninitialized_condvar_list;
411 while (link != NULL)
412 {
413 DBusCondVar **cp;
414
415 cp = (DBusCondVar **)link->data;
416 _dbus_assert (*cp == _DBUS_DUMMY_CONDVAR);
417
418 *cp = _dbus_platform_condvar_new ();
419 if (*cp == NULL)
420 goto fail_condvar;
421
422 link = _dbus_list_get_next_link (&uninitialized_condvar_list, link);
423 }
424
425 _dbus_list_clear (&uninitialized_rmutex_list);
426 _dbus_list_clear (&uninitialized_cmutex_list);
427 _dbus_list_clear (&uninitialized_condvar_list);
428
429 if (!_dbus_register_shutdown_func (shutdown_uninitialized_locks,
430 NULL))
431 goto fail_condvar;
432
433 return TRUE;
434
435 fail_condvar:
436 link = uninitialized_condvar_list;
437 while (link != NULL)
438 {
439 DBusCondVar **cp;
440
441 cp = link->data;
442
443 if (*cp != _DBUS_DUMMY_CONDVAR && *cp != NULL)
444 _dbus_platform_condvar_free (*cp);
445
446 *cp = _DBUS_DUMMY_CONDVAR;
447
448 link = _dbus_list_get_next_link (&uninitialized_condvar_list, link);
449 }
450
451 fail_mutex:
452 link = uninitialized_rmutex_list;
453 while (link != NULL)
454 {
455 DBusRMutex **mp;
456
457 mp = link->data;
458
459 if (*mp != _DBUS_DUMMY_RMUTEX && *mp != NULL)
460 _dbus_platform_rmutex_free (*mp);
461
462 *mp = _DBUS_DUMMY_RMUTEX;
463
464 link = _dbus_list_get_next_link (&uninitialized_rmutex_list, link);
465 }
466
467 link = uninitialized_cmutex_list;
468 while (link != NULL)
469 {
470 DBusCMutex **mp;
471
472 mp = link->data;
473
474 if (*mp != _DBUS_DUMMY_CMUTEX && *mp != NULL)
475 _dbus_platform_cmutex_free (*mp);
476
477 *mp = _DBUS_DUMMY_CMUTEX;
478
479 link = _dbus_list_get_next_link (&uninitialized_cmutex_list, link);
480 }
481
482 return FALSE;
483 }
484
485 static dbus_bool_t
init_locks(void)486 init_locks (void)
487 {
488 int i;
489 DBusRMutex ***dynamic_global_locks;
490 DBusRMutex **global_locks[] = {
491 #define LOCK_ADDR(name) (& _dbus_lock_##name)
492 LOCK_ADDR (win_fds),
493 LOCK_ADDR (sid_atom_cache),
494 LOCK_ADDR (list),
495 LOCK_ADDR (connection_slots),
496 LOCK_ADDR (pending_call_slots),
497 LOCK_ADDR (server_slots),
498 LOCK_ADDR (message_slots),
499 #if !DBUS_USE_SYNC
500 LOCK_ADDR (atomic),
501 #endif
502 LOCK_ADDR (bus),
503 LOCK_ADDR (bus_datas),
504 LOCK_ADDR (shutdown_funcs),
505 LOCK_ADDR (system_users),
506 LOCK_ADDR (message_cache),
507 LOCK_ADDR (shared_connections),
508 LOCK_ADDR (machine_uuid)
509 #undef LOCK_ADDR
510 };
511
512 _dbus_assert (_DBUS_N_ELEMENTS (global_locks) ==
513 _DBUS_N_GLOBAL_LOCKS);
514
515 i = 0;
516
517 dynamic_global_locks = dbus_new (DBusRMutex**, _DBUS_N_GLOBAL_LOCKS);
518 if (dynamic_global_locks == NULL)
519 goto failed;
520
521 while (i < _DBUS_N_ELEMENTS (global_locks))
522 {
523 *global_locks[i] = _dbus_platform_rmutex_new ();
524
525 if (*global_locks[i] == NULL)
526 goto failed;
527
528 dynamic_global_locks[i] = global_locks[i];
529
530 ++i;
531 }
532
533 if (!_dbus_register_shutdown_func (shutdown_global_locks,
534 dynamic_global_locks))
535 goto failed;
536
537 if (!init_uninitialized_locks ())
538 goto failed;
539
540 return TRUE;
541
542 failed:
543 dbus_free (dynamic_global_locks);
544
545 for (i = i - 1; i >= 0; i--)
546 {
547 _dbus_platform_rmutex_free (*global_locks[i]);
548 *global_locks[i] = NULL;
549 }
550 return FALSE;
551 }
552
553 /** @} */ /* end of internals */
554
555 /**
556 * @defgroup DBusThreads Thread functions
557 * @ingroup DBus
558 * @brief dbus_threads_init() and dbus_threads_init_default()
559 *
560 * Functions and macros related to threads and thread locks.
561 *
562 * If threads are initialized, the D-Bus library has locks on all
563 * global data structures. In addition, each #DBusConnection has a
564 * lock, so only one thread at a time can touch the connection. (See
565 * @ref DBusConnection for more on connection locking.)
566 *
567 * Most other objects, however, do not have locks - they can only be
568 * used from a single thread at a time, unless you lock them yourself.
569 * For example, a #DBusMessage can't be modified from two threads
570 * at once.
571 *
572 * @{
573 */
574
575 /**
576 * Initializes threads, like dbus_threads_init_default().
577 * This version previously allowed user-specified threading
578 * primitives, but since D-Bus 1.6 it ignores them and behaves
579 * exactly like dbus_threads_init_default().
580 *
581 * @param functions ignored, formerly functions for using threads
582 * @returns #TRUE on success, #FALSE if no memory
583 */
584 dbus_bool_t
dbus_threads_init(const DBusThreadFunctions * functions)585 dbus_threads_init (const DBusThreadFunctions *functions)
586 {
587 if (thread_init_generation == _dbus_current_generation)
588 return TRUE;
589
590 if (!init_locks ())
591 return FALSE;
592
593 thread_init_generation = _dbus_current_generation;
594
595 return TRUE;
596 }
597
598
599
600 /* Default thread implemenation */
601
602 /**
603 * Initializes threads. If this function is not called, the D-Bus
604 * library will not lock any data structures. If it is called, D-Bus
605 * will do locking, at some cost in efficiency. Note that this
606 * function must be called BEFORE the second thread is started.
607 *
608 * It's safe to call dbus_threads_init_default() as many times as you
609 * want, but only the first time will have an effect.
610 *
611 * dbus_shutdown() reverses the effects of this function when it
612 * resets all global state in libdbus.
613 *
614 * @returns #TRUE on success, #FALSE if not enough memory
615 */
616 dbus_bool_t
dbus_threads_init_default(void)617 dbus_threads_init_default (void)
618 {
619 return _dbus_threads_init_platform_specific ();
620 }
621
622
623 /** @} */
624
625 #ifdef DBUS_BUILD_TESTS
626
627 dbus_bool_t
_dbus_threads_init_debug(void)628 _dbus_threads_init_debug (void)
629 {
630 return _dbus_threads_init_platform_specific();
631 }
632
633 #endif /* DBUS_BUILD_TESTS */
634