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