1 /*
2 * Copyright (C) 2002 - David W. Durham
3 *
4 * This file is part of ReZound, an audio editing application.
5 *
6 * ReZound is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 2 of the License,
9 * or (at your option) any later version.
10 *
11 * ReZound is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 */
20 #include "CMutex.h"
21 #include <stdio.h>
22 #include <assert.h>
23
24 #ifdef _WIN32
25 // *** WIN32 implementation ***
26
27 #include <stdexcept>
28 #include <string>
29 #include "istring"
30 using namespace std;
31
32 #ifndef __func__
33 #define __func__ __FUNCTION__
34 #endif
35
36 #include <windows.h>
37 #include "DLLFunction.h"
38 #include "Singleton.h"
39
40 namespace {
41 class DLL : public Singleton<DLL> {
42 public:
43 typedef BOOL (WINAPI *InitializeCriticalSectionAndSpinCount_t)(LPCRITICAL_SECTION lpCriticalSection,DWORD dwSpinCount);
44 DLLFunction<InitializeCriticalSectionAndSpinCount_t> InitializeCriticalSectionAndSpinCount;
45 private:
46 friend class Singleton<DLL>;
DLL()47 DLL()
48 : InitializeCriticalSectionAndSpinCount(_T("kernel32.dll"),"InitializeCriticalSectionAndSpinCount")
49 {}
50 };
51 }
52
53 /* if recursive is true, then the same thread can safely lock the mutex even if it already has a lock on it */
CMutex(bool recursive,const char * name)54 CMutex::CMutex(bool recursive,const char *name)
55 {
56 // recursive is the default behavior on win32, so IOW, we can't ever dead-lock ourself if we wanted to.. but other implementations still may be more efficient with the mutex not being recursive
57 mutex=CreateMutex(NULL,FALSE,name?itstring(name).c_str():NULL);
58 }
59
~CMutex()60 CMutex::~CMutex() throw()
61 {
62 CloseHandle(mutex);
63 }
64
trylock(int timeout_ms)65 bool CMutex::trylock(int timeout_ms)
66 {
67 int ret=1;
68
69 if(timeout_ms<0){
70 timeout_ms=INFINITE;
71 }
72
73
74 /*
75 In case you're wondering if this WaitForSingleObject operation is reference counted, it is:
76 http://msdn2.microsoft.com/en-us/library/ms682411.aspx
77 Quote:
78 The thread that owns a mutex can specify the same mutex in repeated wait function calls
79 without blocking its execution. Typically, you would not wait repeatedly for the same
80 mutex, but this mechanism prevents a thread from deadlocking itself while waiting for a
81 mutex that it already owns. However, to release its ownership, the thread must call
82 ReleaseMutex once for each time that the mutex satisfied a wait.
83 */
84
85 // since WAIT_OBJECT_0 == 0L, we can just return whatever we get
86 ret=WaitForSingleObject(mutex,timeout_ms);
87
88 if(WAIT_ABANDONED==ret){
89 // this special case actually does grant the mutex ownership,
90 // so change the return value to ignore this case
91 ret=0;
92 }
93
94 //if(ret && WAIT_TIMEOUT!=ret)
95 // throw runtime_error(string(__FUNCTION__)+" -- error aquiring lock -- "+strerror(ret));
96
97 return ret==0;
98 }
99
unlock()100 void CMutex::unlock()
101 {
102 ReleaseMutex(mutex);
103 }
104
105 //***********************************************************************************
106 // "Critical Sections" have been said to be more efficient in win32 and often involve
107 // just one hardware instruction rather than a system call in the case of mutexes...
108 // ...thus, the birth of CFastMutex!
109
CFastMutex()110 CFastMutex::CFastMutex()
111 : m_ownerthread(0)
112 , m_lockcount(0)
113 {
114 m_CriticalSection=new CRITICAL_SECTION;
115 if(DLL::instance().InitializeCriticalSectionAndSpinCount){
116 DLL::instance().InitializeCriticalSectionAndSpinCount((LPCRITICAL_SECTION)m_CriticalSection,4000);
117 } else {
118 InitializeCriticalSection((LPCRITICAL_SECTION)m_CriticalSection);
119 }
120 }
121
~CFastMutex()122 CFastMutex::~CFastMutex() throw(){
123 unlock();
124 DeleteCriticalSection((LPCRITICAL_SECTION)m_CriticalSection);
125 delete m_CriticalSection;
126 m_CriticalSection=NULL;
127 }
128
trylock(int timeout_ms)129 bool CFastMutex::trylock(int timeout_ms){
130 assert(("trylock called with timeout",timeout_ms<0));
131 assert(("no critical section",m_CriticalSection!=0));
132
133 if (m_ownerthread==GetCurrentThreadId()) {
134 // Recursive lock
135 m_lockcount++;
136 return true;
137 }
138
139 #if defined(_DEBUG) && 0 // alter to enable deadlock detection
140 int count = 0;
141 while(m_ownerthread!=NULL && count<50){
142 Sleep(100);
143 count++;
144 }
145 if(m_ownerthread!=NULL){
146 itstring msg = itstring("Deadlock detected in thread ") + itstring(istring(GetCurrentThreadId())) + itstring(" blocked by thread ") + itstring(istring(m_ownerthread));
147 MessageBox(NULL,msg,_T("CFastMutex::trylock"),MB_ICONSTOP|MB_OK|MB_TOPMOST);
148 DebugBreak();
149 }
150 #endif
151
152 EnterCriticalSection((LPCRITICAL_SECTION)m_CriticalSection);
153 m_lockcount++;
154
155 // guarenteed exclusive access since we entered a critical section
156 // save the threadid that created the mutex so we can check for errors on unlock
157 m_ownerthread=GetCurrentThreadId();
158
159 return true;
160 }
161
unlock()162 void CFastMutex::unlock(){
163 if(m_lockcount > 0){
164 m_lockcount--;
165 if (m_lockcount == 0) {
166 assert(("no critical section",m_CriticalSection!=0));
167
168 // MSDN says: If a thread calls LeaveCriticalSection when it does not have ownership of
169 // the specified critical section object, an error occurs that may cause another
170 // thread using EnterCriticalSection to wait indefinitely.
171 assert(("thread trespassing on unlock",GetCurrentThreadId()==m_ownerthread));
172
173 // guarenteed exclusive access since we haven't left our critical section
174 m_ownerthread=NULL;
175
176 LeaveCriticalSection((LPCRITICAL_SECTION)m_CriticalSection);
177 }
178 }
179 }
180 #else
181 // *** posix implementation ***
182
183 #include <pthread.h>
184
185 #include <stdexcept>
186 #include <string>
187 using namespace std;
188
189 #include <errno.h> // for EBUSY
190 #include <string.h> // for strerror()
191 #include <unistd.h> // for usleep()
192 //#include <time.h> // for clock_gettime()
193
194 #include "clocks.h"
195
196 /* if recursive is true, then the same thread can safely lock the mutex even if it already has a lock on it */
CMutex(bool recursive,const char * name)197 CMutex::CMutex(bool recursive,const char *name) :
198 mutex(new pthread_mutex_t)
199 {
200 if(name!=NULL)
201 throw runtime_error(string(__func__)+" -- globally named mutexes are not implemented on this platform");
202 if(recursive)
203 {
204 //pthread_mutex_t _mutex=PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
205 pthread_mutexattr_t attr;
206 pthread_mutexattr_init(&attr);
207 pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
208
209 pthread_mutex_init((pthread_mutex_t *)mutex,&attr);
210
211 pthread_mutexattr_destroy(&attr);
212 }
213 else
214 pthread_mutex_init((pthread_mutex_t *)mutex,NULL);
215
216 /* these are a little slower, but safer.. needs to be a runtime option probably
217 // make it an error to lock it twice in the same thread
218 pthread_mutex_t _mutex=PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
219 mutex=_mutex;
220 */
221 }
222
~CMutex()223 CMutex::~CMutex() throw()
224 {
225 const int ret=pthread_mutex_destroy((pthread_mutex_t *)mutex);
226 delete (pthread_mutex_t *)mutex;
227 if(ret)
228 //throw runtime_error(string(__func__)+" -- error destroying mutex -- "+strerror(ret)); // may not care tho
229 fprintf(stderr, "%s -- error destroying mutex -- %s\n",__func__,strerror(ret));
230 }
231
232 // returns true if the lock was successful else false
trylock(int timeout_ms)233 bool CMutex::trylock(int timeout_ms)
234 {
235 int ret=1;
236 if(timeout_ms<0)
237 {
238 ret=pthread_mutex_lock((pthread_mutex_t *)mutex);
239 if(ret)
240 throw runtime_error(string(__func__)+" -- error aquiring lock -- "+strerror(ret));
241 }
242 else if(timeout_ms==0)
243 {
244 ret=pthread_mutex_trylock((pthread_mutex_t *)mutex);
245 if(ret && ret!=EBUSY)
246 throw runtime_error(string(__func__)+" -- error doing try lock -- "+strerror(ret));
247 }
248 else //if(timeout_ms>0)
249 {
250 #ifdef __APPLE__
251 /*
252 On OS X there is no implementation of pthread_mutex_timedlock()
253 I attempted to implement it. Looking at the implementation of pthread_mutex_lock() in
254 http://darwinsource.opendarwin.org/10.4.6.ppc/Libc-391.2.5/pthreads/pthread_mutex.c
255 the logic was straigh forward and it looked as tho the semaphore_wait_signal() and pthread_wait()
256 calls on locking the 'sem' value into semaphre_timedwait_signal() and semaphore_timedwait() calls.
257
258 However, after going to much trouble to do this, the two functions, new_sem_from_pool() and
259 restore_sem_to_pool() are in private symbol tables in libc.dynlib and are not accessible when linking.
260
261 Below is a VERY poor-man's and extremely starvation-prone implementation of trylock-with-timeout.
262 It simply periodically tries to lock (with no timeout) until the timeout expires or the lock is obtained.
263
264 There are 2 alternatives to this stupid approach when this implementation just will not work:
265 On OS X: copy the pthread_mutex.c and pthread_cond.c (and other supporting snippets) and use them as a
266 starting point in an alternate implementation of mutexes but with a pthread_mutex_timedlock()
267 (condition variables are included because they have to deal with the internals of a mutex)
268 For all platforms: implement mutex using a condition variable. Apparently pthread_cond_timedwait() is
269 more implemented on other platforms. An implementation using condition variables would go something like:
270 - use a normal pthreads mutex to serialize the lock and unlock methods
271 - when locking, if the our "mutex" is unlocked, just say we locked it..
272 - track who the owner is for recursive mutexes
273 - an unlock with others waiting is just a signal to anyone waiting on a lock, or setting the flag if others aren't waiting
274 - I would guess that the condition variable is fair about who it signals when it wakes a waiter up
275 - a lock without anyone else waiting is a simple set of a flag
276 If I go with the second implementation I would probably just #ifdef out the current implementation in case
277 another platform later doesn't implement pthread_cond_timedwait()
278 */
279
280 #warning this is a very poor and starvation-prone implementation of trylock-with-timeout. Do not use it if starvation is a potential problem. See this source file for alternatives when necessary.
281
282 const time_t base=time(NULL);
283
284 unsigned long expireTime=(clocks::fixed_msec()-((clocks::msec_t)base*1000))+timeout_ms;
285
286 unsigned long currentTime;
287 do {
288 if(trylock(0))
289 return true;
290
291 if(timeout_ms<50)
292 { // just try once again after the full timeout and that's it
293 usleep(timeout_ms*1000);
294 return trylock(0);
295 }
296 else
297 usleep(50*1000); // sleep for 50ms
298
299 currentTime=(clocks::fixed_msec()-((clocks::msec_t)base*1000));
300 } while(currentTime<expireTime);
301
302 return false;
303
304 #else
305 struct timespec abs_timeout;
306
307 #ifdef __linux
308 /* getting the abs_timeout this way is the most precise, but requires linking against librt */
309 clock_gettime(CLOCK_REALTIME,&abs_timeout);
310 #else
311 /* getting the time this way is less precise that above, but is more compatible */
312 {
313 clocks::msec_t current=clocks::time_msec();
314 abs_timeout.tv_sec=current/1000;
315 abs_timeout.tv_nsec=(current%1000)*1000*1000;
316 }
317 #endif
318
319 // add our desired duration to the current time-of-day
320 abs_timeout.tv_sec+=(timeout_ms/1000);
321 abs_timeout.tv_nsec+=(timeout_ms%1000)*1000*1000;
322 abs_timeout.tv_sec+=abs_timeout.tv_nsec/1000000000;
323 abs_timeout.tv_nsec%=1000000000;
324
325 ret=pthread_mutex_timedlock((pthread_mutex_t *)mutex,&abs_timeout);
326 if(ret && ret!=ETIMEDOUT)
327 throw runtime_error(string(__func__)+" -- error aquiring lock -- "+strerror(ret));
328 #endif
329 }
330 return ret==0;
331 }
332
unlock()333 void CMutex::unlock()
334 {
335 const int ret=pthread_mutex_unlock((pthread_mutex_t *)mutex);
336 if(ret)
337 throw runtime_error(string(__func__)+" -- error unlocking mutex -- "+strerror(ret));
338 }
339
340
341
342 #endif
343
CMutexLocker()344 CMutexLocker::CMutexLocker() :
345 m(NULL)
346 {
347 }
348
CMutexLocker(AMutex & _m)349 CMutexLocker::CMutexLocker(AMutex &_m) :
350 m(NULL)
351 {
352 reassign(_m,-1);
353 }
354
CMutexLocker(AMutex & _m,int timeout_ms)355 CMutexLocker::CMutexLocker(AMutex &_m,int timeout_ms) :
356 m(NULL)
357 {
358 reassign(_m,timeout_ms);
359 }
360
~CMutexLocker()361 CMutexLocker::~CMutexLocker() throw()
362 {
363 try
364 {
365 unassign();
366 }
367 catch(exception &e)
368 {
369 fprintf(stderr, "%s -- exception -- %s\n",__func__,e.what());
370 }
371 }
372
373 // unlocks the currently assigned mutex if it was locked, assigns a new mutex to this locked and locks/try-locks the new mutex
374 // the locked status is returned
reassign(AMutex & _m,int timeout_ms)375 bool CMutexLocker::reassign(AMutex &_m,int timeout_ms)
376 {
377 unassign();
378
379 // if you get a crash here, it means you have invoked a null class instance
380 // and this is just where it is showing up... check your call stack!
381
382 if(_m.trylock(timeout_ms))
383 {
384 m=&_m;
385 return true;
386 }
387 return false;
388 }
389
390 // unlocks the currently assigned mutex if it was locked and leaves the locker object with no assigned mutex
unassign()391 void CMutexLocker::unassign()
392 {
393 if(m)
394 {
395 m->unlock();
396 }
397 m=NULL;
398 }
399
~CMutexUnlocker()400 CMutexUnlocker::~CMutexUnlocker() throw ()
401 {
402 try
403 {
404 lock();
405 }
406 catch(exception &e)
407 {
408 fprintf(stderr, "%s -- exception -- %s\n",__func__,e.what());
409 }
410 }
411
412