2006-10-26 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / dbus / dbus-sysdeps-pthread.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* dbus-sysdeps-pthread.c Implements threads using pthreads (internal to libdbus)
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #include "dbus-internals.h"
25 #include "dbus-sysdeps.h"
26 #include "dbus-threads.h"
27
28 #include <sys/time.h>
29 #include <pthread.h>
30 #include <string.h>
31
32 typedef struct {
33   pthread_mutex_t lock;
34   int count;
35   pthread_t holder;
36 } DBusMutexPThread;
37
38 typedef struct {
39   pthread_cond_t cond;
40 } DBusCondVarPThread;
41
42 #define DBUS_MUTEX(m)         ((DBusMutex*) m)
43 #define DBUS_MUTEX_PTHREAD(m) ((DBusMutexPThread*) m)
44
45 #define DBUS_COND_VAR(c)         ((DBusCondVar*) c)
46 #define DBUS_COND_VAR_PTHREAD(c) ((DBusCondVarPThread*) c)
47
48
49 #define PTHREAD_CHECK(func_name, result_or_call) do {                                  \
50     int tmp = (result_or_call);                                                        \
51     if (tmp != 0) {                                                                    \
52       _dbus_warn_check_failed ("pthread function %s failed with %d %s in %s\n",        \
53                                func_name, tmp, strerror(tmp), _DBUS_FUNCTION_NAME);    \
54     }                                                                                  \
55 } while (0)
56             
57 static DBusMutex*
58 _dbus_pthread_mutex_new (void)
59 {
60   DBusMutexPThread *pmutex;
61   int result;
62   
63   pmutex = dbus_new (DBusMutexPThread, 1);
64   if (pmutex == NULL)
65     return NULL;
66
67   result = pthread_mutex_init (&pmutex->lock, NULL);
68
69   if (result == ENOMEM || result == EAGAIN)
70     {
71       dbus_free (pmutex);
72       return NULL;
73     }
74   else
75     {
76       PTHREAD_CHECK ("pthread_mutex_init", result);
77     }
78
79   /* Only written */
80   pmutex->count = 0;
81
82   /* There's no portable way to have a "null" pthread afaik so we
83    * can't set pmutex->holder to anything sensible.  We only access it
84    * once the lock is held (which means we've set it).
85    */
86   
87   return DBUS_MUTEX (pmutex);
88 }
89
90 static void
91 _dbus_pthread_mutex_free (DBusMutex *mutex)
92 {
93   DBusMutexPThread *pmutex = DBUS_MUTEX_PTHREAD (mutex);
94
95   _dbus_assert (pmutex->count == 0);
96   
97   PTHREAD_CHECK ("pthread_mutex_destroy", pthread_mutex_destroy (&pmutex->lock));
98
99   dbus_free (pmutex);
100 }
101
102 static void
103 _dbus_pthread_mutex_lock (DBusMutex *mutex)
104 {
105   DBusMutexPThread *pmutex = DBUS_MUTEX_PTHREAD (mutex);
106   pthread_t self = pthread_self ();
107
108   /* If the count is > 0 then someone had the lock, maybe us. If it is
109    * 0, then it might immediately change right after we read it,
110    * but it will be changed by another thread; i.e. if we read 0,
111    * we assume that this thread doesn't have the lock.
112    *
113    * Not 100% sure this is safe, but ... seems like it should be.
114    */
115   if (pmutex->count == 0)
116     {
117       /* We know we don't have the lock; someone may have the lock. */
118       
119       PTHREAD_CHECK ("pthread_mutex_lock", pthread_mutex_lock (&pmutex->lock));
120
121       /* We now have the lock. Count must be 0 since it was before when we
122        * did not have the lock, and we have not changed it in this thread.
123        */
124       _dbus_assert (pmutex->count == 0);
125       
126       pmutex->holder = self;
127       pmutex->count = 1;
128     }
129   else
130     {
131       /* We know someone had the lock, possibly us. Thus
132        * pmutex->holder is not pointing to junk, though it may not be
133        * the lock holder anymore if the lock holder is not us.
134        * If the lock holder is us, then we definitely have the lock.
135        */
136
137       if (pthread_equal (pmutex->holder, self))
138         {
139           /* We already have the lock. */
140         }
141       else
142         {
143           /* Wait for the lock */
144           PTHREAD_CHECK ("pthread_mutex_lock", pthread_mutex_lock (&pmutex->lock));
145           _dbus_assert (pmutex->count == 0);
146         }
147
148       pmutex->count += 1;
149     }
150 }
151
152 static void
153 _dbus_pthread_mutex_unlock (DBusMutex *mutex)
154 {
155   DBusMutexPThread *pmutex = DBUS_MUTEX_PTHREAD (mutex);
156
157   _dbus_assert (pmutex->count > 0);
158   
159   pmutex->count -= 1;
160
161   if (pmutex->count == 0)
162     PTHREAD_CHECK ("pthread_mutex_unlock", pthread_mutex_unlock (&pmutex->lock));
163   
164   /* We leave pmutex->holder set to ourselves, its content is undefined if count is 0 */
165 }
166
167 static DBusCondVar *
168 _dbus_pthread_condvar_new (void)
169 {
170   DBusCondVarPThread *pcond;
171   int result;
172   
173   pcond = dbus_new (DBusCondVarPThread, 1);
174   if (pcond == NULL)
175     return NULL;
176
177   result = pthread_cond_init (&pcond->cond, NULL);
178
179   if (result == EAGAIN || result == ENOMEM)
180     {
181       dbus_free (pcond);
182       return NULL;
183     }
184   else
185     {
186       PTHREAD_CHECK ("pthread_cond_init", result);
187     }
188   
189   return DBUS_COND_VAR (pcond);
190 }
191
192 static void
193 _dbus_pthread_condvar_free (DBusCondVar *cond)
194 {  
195   DBusCondVarPThread *pcond = DBUS_COND_VAR_PTHREAD (cond);
196   
197   PTHREAD_CHECK ("pthread_cond_destroy", pthread_cond_destroy (&pcond->cond));
198
199   dbus_free (pcond);
200 }
201
202 static void
203 _dbus_pthread_condvar_wait (DBusCondVar *cond,
204                             DBusMutex   *mutex)
205 {
206   DBusMutexPThread *pmutex = DBUS_MUTEX_PTHREAD (mutex);
207   DBusCondVarPThread *pcond = DBUS_COND_VAR_PTHREAD (cond);
208   int old_count;
209   
210   _dbus_assert (pmutex->count > 0);
211   _dbus_assert (pthread_equal (pmutex->holder, pthread_self ()));
212
213   old_count = pmutex->count;
214   pmutex->count = 0;
215   PTHREAD_CHECK ("pthread_cond_wait", pthread_cond_wait (&pcond->cond, &pmutex->lock));
216   _dbus_assert (pmutex->count == 0);
217   pmutex->count = old_count;
218 }
219
220 static dbus_bool_t
221 _dbus_pthread_condvar_wait_timeout (DBusCondVar               *cond,
222                                     DBusMutex                 *mutex,
223                                     int                        timeout_milliseconds)
224 {
225   DBusMutexPThread *pmutex = DBUS_MUTEX_PTHREAD (mutex);
226   DBusCondVarPThread *pcond = DBUS_COND_VAR_PTHREAD (cond);
227   struct timeval time_now;
228   struct timespec end_time;
229   int result;
230   int old_count;
231   
232   _dbus_assert (pmutex->count > 0);
233   _dbus_assert (pthread_equal (pmutex->holder, pthread_self ()));  
234   
235   gettimeofday (&time_now, NULL);
236   
237   end_time.tv_sec = time_now.tv_sec + timeout_milliseconds / 1000;
238   end_time.tv_nsec = (time_now.tv_usec + (timeout_milliseconds % 1000) * 1000) * 1000;
239   if (end_time.tv_nsec > 1000*1000*1000)
240     {
241       end_time.tv_sec += 1;
242       end_time.tv_nsec -= 1000*1000*1000;
243     }
244
245   old_count = pmutex->count;
246   pmutex->count = 0;
247   result = pthread_cond_timedwait (&pcond->cond, &pmutex->lock, &end_time);
248   
249   if (result != ETIMEDOUT)
250     {
251       PTHREAD_CHECK ("pthread_cond_timedwait", result);
252     }
253
254   _dbus_assert (pmutex->count == 0);
255   pmutex->count = old_count;
256   
257   /* return true if we did not time out */
258   return result != ETIMEDOUT;
259 }
260
261 static void
262 _dbus_pthread_condvar_wake_one (DBusCondVar *cond)
263 {
264   DBusCondVarPThread *pcond = DBUS_COND_VAR_PTHREAD (cond);
265
266   PTHREAD_CHECK ("pthread_cond_signal", pthread_cond_signal (&pcond->cond));
267 }
268
269 static void
270 _dbus_pthread_condvar_wake_all (DBusCondVar *cond)
271 {
272   DBusCondVarPThread *pcond = DBUS_COND_VAR_PTHREAD (cond);
273   
274   PTHREAD_CHECK ("pthread_cond_broadcast", pthread_cond_broadcast (&pcond->cond));
275 }
276
277 static const DBusThreadFunctions pthread_functions =
278 {
279   DBUS_THREAD_FUNCTIONS_RECURSIVE_MUTEX_NEW_MASK |
280   DBUS_THREAD_FUNCTIONS_RECURSIVE_MUTEX_FREE_MASK |
281   DBUS_THREAD_FUNCTIONS_RECURSIVE_MUTEX_LOCK_MASK |
282   DBUS_THREAD_FUNCTIONS_RECURSIVE_MUTEX_UNLOCK_MASK |
283   DBUS_THREAD_FUNCTIONS_CONDVAR_NEW_MASK |
284   DBUS_THREAD_FUNCTIONS_CONDVAR_FREE_MASK |
285   DBUS_THREAD_FUNCTIONS_CONDVAR_WAIT_MASK |
286   DBUS_THREAD_FUNCTIONS_CONDVAR_WAIT_TIMEOUT_MASK |
287   DBUS_THREAD_FUNCTIONS_CONDVAR_WAKE_ONE_MASK|
288   DBUS_THREAD_FUNCTIONS_CONDVAR_WAKE_ALL_MASK,
289   NULL, NULL, NULL, NULL,
290   _dbus_pthread_condvar_new,
291   _dbus_pthread_condvar_free,
292   _dbus_pthread_condvar_wait,
293   _dbus_pthread_condvar_wait_timeout,
294   _dbus_pthread_condvar_wake_one,
295   _dbus_pthread_condvar_wake_all,
296   _dbus_pthread_mutex_new,
297   _dbus_pthread_mutex_free,
298   _dbus_pthread_mutex_lock,
299   _dbus_pthread_mutex_unlock
300 };
301
302 dbus_bool_t
303 _dbus_threads_init_platform_specific (void)
304 {
305   return dbus_threads_init (&pthread_functions);
306 }