1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2011-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 /* synchronization helper classes */
17 
18 #ifndef INC_StdSync
19 #define INC_StdSync
20 
21 #ifdef _WIN32
22 #include "platform/C4windowswrapper.h"
23 
24 class CStdCSec
25 {
26 public:
CStdCSec()27 	CStdCSec() { InitializeCriticalSection(&sec); }
~CStdCSec()28 	virtual ~CStdCSec() { DeleteCriticalSection(&sec); }
29 
30 protected:
31 	CRITICAL_SECTION sec;
32 
33 public:
Enter()34 	virtual void Enter() { EnterCriticalSection(&sec); }
Leave()35 	virtual void Leave() { LeaveCriticalSection(&sec); }
36 };
37 
38 class CStdEvent
39 {
40 public:
CStdEvent(bool fManualReset)41 	CStdEvent(bool fManualReset) { hEvent = CreateEvent(nullptr, fManualReset, false, nullptr); }
~CStdEvent()42 	~CStdEvent() { CloseHandle(hEvent); }
43 
44 protected:
45 	HANDLE hEvent;
46 
47 public:
Set()48 	void Set() { SetEvent(hEvent); }
Pulse()49 	void Pulse() { PulseEvent(hEvent); }
Reset()50 	void Reset() { ResetEvent(hEvent); }
WaitFor(int iMillis)51 	bool WaitFor(int iMillis) { return WaitForSingleObject(hEvent, iMillis) == WAIT_OBJECT_0; }
52 
GetEvent()53 	HANDLE GetEvent() { return hEvent; }
54 };
55 
56 #else
57 // Value to specify infinite wait.
58 #define INFINITE (~0u)
59 
60 #if defined(HAVE_PTHREAD)
61 #include <pthread.h>
62 
63 class CStdCSec
64 {
65 public:
CStdCSec()66 	CStdCSec()
67 	{
68 		pthread_mutexattr_t attr;
69 		pthread_mutexattr_init(&attr);
70 		pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
71 		pthread_mutex_init(&mutex, &attr);
72 	}
~CStdCSec()73 	virtual ~CStdCSec() { pthread_mutex_destroy(&mutex); }
74 
75 protected:
76 	pthread_mutex_t mutex;
77 
78 public:
Enter()79 	virtual void Enter() { pthread_mutex_lock(&mutex); }
Leave()80 	virtual void Leave() { pthread_mutex_unlock(&mutex); }
81 };
82 
83 class CStdEvent
84 {
85 public:
CStdEvent(bool fManualReset)86 	CStdEvent(bool fManualReset) : fManualReset(fManualReset), fSet(false)
87 	{
88 		pthread_cond_init(&cond, nullptr);
89 		pthread_mutex_init(&mutex, nullptr);
90 	}
~CStdEvent()91 	~CStdEvent()
92 	{
93 		pthread_cond_destroy(&cond);
94 		pthread_mutex_destroy(&mutex);
95 	}
96 
97 protected:
98 	pthread_cond_t cond;
99 	pthread_mutex_t mutex;
100 	bool fManualReset, fSet;
101 
102 public:
Set()103 	void Set()
104 	{
105 		pthread_mutex_lock(&mutex);
106 		fSet = true;
107 		pthread_cond_broadcast(&cond);
108 		pthread_mutex_unlock(&mutex);
109 	}
Pulse()110 	void Pulse()
111 	{
112 		pthread_cond_broadcast(&cond);
113 	}
Reset()114 	void Reset()
115 	{
116 		pthread_mutex_lock(&mutex);
117 		fSet = false;
118 		pthread_mutex_unlock(&mutex);
119 	}
WaitFor(unsigned int iMillis)120 	bool WaitFor(unsigned int iMillis)
121 	{
122 		pthread_mutex_lock(&mutex);
123 		// Already set?
124 		while (!fSet)
125 		{
126 			// Use pthread_cond_wait or pthread_cond_timedwait depending on wait length. Check return value.
127 			// Note this will temporarily unlock the mutex, so no deadlock should occur.
128 			timespec ts = { static_cast<time_t>(iMillis / 1000),
129 				static_cast<long>((iMillis % 1000) * 1000000) };
130 			if (0 != (iMillis != INFINITE ? pthread_cond_timedwait(&cond, &mutex, &ts) : pthread_cond_wait(&cond, &mutex)))
131 			{
132 				pthread_mutex_unlock(&mutex);
133 				return false;
134 			}
135 		}
136 		// Reset flag, release mutex, done.
137 		if (!fManualReset) fSet = false;
138 		pthread_mutex_unlock(&mutex);
139 		return true;
140 	}
141 };
142 
143 #else
144 // Some stubs to silence the compiler
145 class CStdCSec
146 {
147 public:
CStdCSec()148 	CStdCSec() { }
~CStdCSec()149 	virtual ~CStdCSec() { }
Enter()150 	virtual void Enter() { }
Leave()151 	virtual void Leave() { }
152 };
153 class CStdEvent
154 {
155 public:
CStdEvent(bool)156 	CStdEvent(bool) { }
~CStdEvent()157 	~CStdEvent() { }
Set()158 	void Set() { }
Pulse()159 	void Pulse() { }
Reset()160 	void Reset() { }
WaitFor(int)161 	bool WaitFor(int) { return false; }
162 };
163 #endif // HAVE_PTHREAD
164 #endif // _WIN32
165 
166 class CStdLock
167 {
168 public:
CStdLock(CStdCSec * pSec)169 	CStdLock(CStdCSec *pSec) : sec(pSec)
170 	{ sec->Enter(); }
~CStdLock()171 	~CStdLock()
172 	{ Clear(); }
173 
174 protected:
175 	CStdCSec *sec;
176 
177 public:
Clear()178 	void Clear()
179 	{ if (sec) sec->Leave(); sec = nullptr; }
180 };
181 
182 class CStdCSecExCallback
183 {
184 public:
185 	// is called with CSec exlusive locked!
186 	virtual void OnShareFree(class CStdCSecEx *pCSec) = 0;
187 	virtual ~CStdCSecExCallback() = default;
188 };
189 
190 class CStdCSecEx : public CStdCSec
191 {
192 public:
CStdCSecEx()193 	CStdCSecEx()
194 			: ShareFreeEvent(false)
195 	{ }
CStdCSecEx(CStdCSecExCallback * pCallb)196 	CStdCSecEx(CStdCSecExCallback *pCallb)
197 			: lShareCnt(0), ShareFreeEvent(false), pCallbClass(pCallb)
198 	{ }
199 	~CStdCSecEx() override = default;
200 
201 protected:
202 	// share counter
203 	long lShareCnt{0};
204 	// event: exclusive access permitted
205 	CStdEvent ShareFreeEvent;
206 	// callback
207 	CStdCSecExCallback *pCallbClass{nullptr};
208 
209 public:
210 
211 	// (cycles forever if shared locked by calling thread!)
Enter()212 	void Enter() override
213 	{
214 		// lock
215 		CStdCSec::Enter();
216 		// wait for share-free
217 		while (lShareCnt)
218 		{
219 			// reset event
220 			ShareFreeEvent.Reset();
221 			// leave section for waiting
222 			CStdCSec::Leave();
223 			// wait
224 			ShareFreeEvent.WaitFor(INFINITE);
225 			// reenter section
226 			CStdCSec::Enter();
227 		}
228 	}
229 
Leave()230 	void Leave() override
231 	{
232 		// set event
233 		ShareFreeEvent.Set();
234 		// unlock
235 		CStdCSec::Leave();
236 	}
237 
EnterShared()238 	void EnterShared()
239 	{
240 		// lock
241 		CStdCSec::Enter();
242 		// add share
243 		lShareCnt++;
244 		// unlock
245 		CStdCSec::Leave();
246 	}
247 
LeaveShared()248 	void LeaveShared()
249 	{
250 		// lock
251 		CStdCSec::Enter();
252 		// remove share
253 		if (!--lShareCnt)
254 		{
255 			// do callback
256 			if (pCallbClass)
257 				pCallbClass->OnShareFree(this);
258 			// set event
259 			ShareFreeEvent.Set();
260 		}
261 		// unlock
262 		CStdCSec::Leave();
263 	}
264 };
265 
266 class CStdShareLock
267 {
268 public:
CStdShareLock(CStdCSecEx * pSec)269 	CStdShareLock(CStdCSecEx *pSec) : sec(pSec)
270 	{ sec->EnterShared(); }
~CStdShareLock()271 	~CStdShareLock()
272 	{ Clear(); }
273 
274 protected:
275 	CStdCSecEx *sec;
276 
277 public:
Clear()278 	void Clear()
279 	{ if (sec) sec->LeaveShared(); sec = nullptr; }
280 };
281 
282 /* Debug helper class: Set current thread in Set(); assert that it's still the same thread in Check(); */
283 class StdThreadCheck
284 {
285 #if defined(_DEBUG) && defined(_WIN32)
286 	DWORD idThread;
287 public:
StdThreadCheck()288 	StdThreadCheck() : idThread(0) {}
289 
Set()290 	inline void Set() { idThread = ::GetCurrentThreadId(); }
Check()291 	inline void Check() { assert(idThread == ::GetCurrentThreadId()); }
292 #else
293 public:
294 	inline void Set() {}
295 	inline void Check() { }
296 #endif
297 };
298 
299 #endif // INC_StdSync
300