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