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