eathread_semaphore_pc.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. ///////////////////////////////////////////////////////////////////////////////
  2. // Copyright (c) Electronic Arts Inc. All rights reserved.
  3. ///////////////////////////////////////////////////////////////////////////////
  4. #include "EABase/eabase.h"
  5. #include "eathread/eathread_semaphore.h"
  6. #include "eathread/eathread_sync.h"
  7. #include <limits.h>
  8. #if defined(EA_PLATFORM_MICROSOFT)
  9. EA_DISABLE_ALL_VC_WARNINGS()
  10. #include <Windows.h>
  11. EA_RESTORE_ALL_VC_WARNINGS()
  12. #if defined(EA_PLATFORM_WIN64)
  13. #if !defined _Ret_maybenull_
  14. #define _Ret_maybenull_
  15. #endif
  16. #if !defined _Reserved_
  17. #define _Reserved_
  18. #endif
  19. extern "C" WINBASEAPI _Ret_maybenull_ HANDLE WINAPI CreateSemaphoreExW(_In_opt_ LPSECURITY_ATTRIBUTES, _In_ LONG, _In_ LONG, _In_opt_ LPCWSTR, _Reserved_ DWORD, _In_ DWORD);
  20. #endif
  21. #endif
  22. #ifdef CreateSemaphore
  23. #undef CreateSemaphore
  24. #endif
  25. #if defined(EA_PLATFORM_MICROSOFT) && !EA_POSIX_THREADS_AVAILABLE
  26. // Helper function to abstract away differences between APIs for different versions of Windows
  27. static DWORD EASemaphoreWaitForSingleObject(HANDLE handle, DWORD milliseconds)
  28. {
  29. #if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
  30. return WaitForSingleObject(handle, milliseconds);
  31. #else
  32. return WaitForSingleObjectEx(handle, milliseconds, TRUE);
  33. #endif
  34. }
  35. EASemaphoreData::EASemaphoreData()
  36. : mhSemaphore(0), mnCount(0), mnCancelCount(0), mnMaxCount(INT_MAX), mbIntraProcess(true)
  37. {
  38. EAWriteBarrier();
  39. static_assert(sizeof(int32_t) == sizeof(LONG), "We use int32_t and LONG interchangably. Windows (including Win64) uses 32 bit longs.");
  40. }
  41. void EASemaphoreData::UpdateCancelCount(int32_t cancelCount)
  42. {
  43. // This is used by the fast semaphore path. This function actually isn't called very often -- only under uncommon circumstances.
  44. // This is based on an algorithm discussed on usenet in 2004.
  45. // We safely increment count by min(cancelCount, -count) if count < 0.
  46. int32_t oldCount, newCount, cmpCount;
  47. if(cancelCount > 0)
  48. {
  49. oldCount = mnCount;
  50. while(oldCount < 0)
  51. {
  52. // Increment old count by the number of cancels
  53. if((newCount = oldCount + cancelCount) > 0)
  54. newCount = 0; // ...but not greater then zero.
  55. cmpCount = oldCount;
  56. oldCount = InterlockedCompareExchange((LONG*)&mnCount, newCount, cmpCount);
  57. if(oldCount == cmpCount)
  58. {
  59. cancelCount -= (newCount - oldCount);
  60. break;
  61. }
  62. }
  63. if(cancelCount > 0)
  64. InterlockedExchangeAdd((LONG*)&mnCancelCount, cancelCount);
  65. }
  66. }
  67. EA::Thread::SemaphoreParameters::SemaphoreParameters(int initialCount, bool bIntraProcess, const char* pName)
  68. : mInitialCount(initialCount), mMaxCount(INT_MAX), mbIntraProcess(bIntraProcess)
  69. {
  70. if(pName)
  71. {
  72. strncpy(mName, pName, sizeof(mName)-1);
  73. mName[sizeof(mName)-1] = 0;
  74. }
  75. else
  76. mName[0] = 0;
  77. }
  78. EA::Thread::Semaphore::Semaphore(const SemaphoreParameters* pSemaphoreParameters, bool bDefaultParameters)
  79. {
  80. if(!pSemaphoreParameters && bDefaultParameters)
  81. {
  82. SemaphoreParameters parameters;
  83. Init(&parameters);
  84. }
  85. else
  86. Init(pSemaphoreParameters);
  87. }
  88. EA::Thread::Semaphore::Semaphore(int initialCount)
  89. {
  90. SemaphoreParameters parameters(initialCount);
  91. Init(&parameters);
  92. }
  93. EA::Thread::Semaphore::~Semaphore()
  94. {
  95. if(mSemaphoreData.mhSemaphore)
  96. CloseHandle(mSemaphoreData.mhSemaphore);
  97. }
  98. bool EA::Thread::Semaphore::Init(const SemaphoreParameters* pSemaphoreParameters)
  99. {
  100. if(pSemaphoreParameters && !mSemaphoreData.mhSemaphore)
  101. {
  102. mSemaphoreData.mnCount = pSemaphoreParameters->mInitialCount;
  103. mSemaphoreData.mnMaxCount = pSemaphoreParameters->mMaxCount;
  104. if(mSemaphoreData.mnCount < 0)
  105. mSemaphoreData.mnCount = 0;
  106. mSemaphoreData.mbIntraProcess = pSemaphoreParameters->mbIntraProcess;
  107. // If the fast semaphore is disabled, then we always act like inter-process as opposed to intra-process.
  108. #if EATHREAD_FAST_MS_SEMAPHORE_ENABLED
  109. const bool bIntraProcess = mSemaphoreData.mbIntraProcess;
  110. #else
  111. const bool bIntraProcess = false;
  112. #endif
  113. if(bIntraProcess)
  114. {
  115. // Note that we do things rather differently for intra-process, as we are
  116. // implementing a fast semaphore. This semaphore will be at least 10 times
  117. // faster than the OS semaphore for all Microsoft platforms for the case of
  118. // successful immediate acquire of a semaphore. Semaphore posts (or releases
  119. // will also be much faster than the OS version.
  120. #if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
  121. mSemaphoreData.mhSemaphore = CreateSemaphoreA(NULL, 0, INT_MAX/2, NULL); // Intentionally ignore mnCount and mnMaxCount here.
  122. #else
  123. mSemaphoreData.mhSemaphore = CreateSemaphoreExW(NULL, 0, INT_MAX/2, NULL, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE); // Intentionally ignore mnCount and mnMaxCount here.
  124. #endif
  125. }
  126. else // Else we create a conventional Win32-style semaphore.
  127. {
  128. #if EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)
  129. mSemaphoreData.mhSemaphore = CreateSemaphoreA(NULL, (LONG)mSemaphoreData.mnCount, (LONG)mSemaphoreData.mnMaxCount, pSemaphoreParameters->mName[0] ? pSemaphoreParameters->mName : NULL);
  130. #else
  131. wchar_t wName[EAArrayCount(pSemaphoreParameters->mName)]; // We do an ASCII conversion.
  132. for(size_t c = 0; c < EAArrayCount(wName); c++)
  133. wName[c] = (wchar_t)(uint8_t)pSemaphoreParameters->mName[c];
  134. mSemaphoreData.mhSemaphore = CreateSemaphoreExW(NULL, (LONG)mSemaphoreData.mnCount, (LONG)mSemaphoreData.mnMaxCount, wName[0] ? wName : NULL, 0, SYNCHRONIZE | SEMAPHORE_MODIFY_STATE);
  135. #endif
  136. }
  137. EAWriteBarrier();
  138. EAT_ASSERT(mSemaphoreData.mhSemaphore != 0);
  139. return (mSemaphoreData.mhSemaphore != 0);
  140. }
  141. return false;
  142. }
  143. int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute)
  144. {
  145. EAT_ASSERT(mSemaphoreData.mhSemaphore != 0);
  146. // If the fast semaphore is disabled, then we always act like inter-process as opposed to intra-process.
  147. #if EATHREAD_FAST_MS_SEMAPHORE_ENABLED
  148. const bool bIntraProcess = mSemaphoreData.mbIntraProcess;
  149. #else
  150. const bool bIntraProcess = false;
  151. #endif
  152. if(bIntraProcess) // If this is true, we are using the fast semaphore pathway.
  153. {
  154. if(InterlockedDecrement((LONG*)&mSemaphoreData.mnCount) < 0) // InterlockedDecrement returns the new value. If the mnCount was > 0 before this decrement, then this Wait function will return very quickly.
  155. {
  156. const DWORD dw = EASemaphoreWaitForSingleObject(mSemaphoreData.mhSemaphore, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
  157. if(dw != WAIT_OBJECT_0) // If there was a timeout...
  158. {
  159. mSemaphoreData.UpdateCancelCount(1); // or InterlockedIncrement(&mSemaphoreData.mnCancelCount); // The latter has a bug whereby mnCancelCount can increment indefinitely.
  160. EAT_ASSERT(dw == WAIT_TIMEOUT); // Otherwise it was probably a timeout.
  161. if(dw == WAIT_TIMEOUT)
  162. return kResultTimeout;
  163. return kResultError; // WAIT_FAILED
  164. }
  165. }
  166. // It is by design that a semaphore post does a full memory barrier.
  167. // We don't need such a barrier for this pathway to work, but rather
  168. // it is expected by the user that such a barrier is executed. Investigation
  169. // into the choice of a full vs. just read or write barrier has concluded
  170. // (based on the Posix standard) that a full read-write barrier is expected.
  171. EAReadWriteBarrier();
  172. const int count = (int)mSemaphoreData.mnCount; // Make temporary to avoid race condition in ternary operator below.
  173. return (count > 0 ? count : 0);
  174. }
  175. else
  176. {
  177. const DWORD dw = EASemaphoreWaitForSingleObject(mSemaphoreData.mhSemaphore, RelativeTimeoutFromAbsoluteTimeout(timeoutAbsolute));
  178. if(dw == WAIT_OBJECT_0)
  179. return (int)InterlockedDecrement((LONG*)&mSemaphoreData.mnCount);
  180. else if(dw == WAIT_TIMEOUT)
  181. return kResultTimeout;
  182. return kResultError;
  183. }
  184. }
  185. int EA::Thread::Semaphore::Post(int count)
  186. {
  187. EAT_ASSERT((mSemaphoreData.mhSemaphore != 0) && (count >= 0));
  188. if(count > 0)
  189. {
  190. // If the fast semaphore is disabled, then we always act like inter-process as opposed to intra-process.
  191. #if EATHREAD_FAST_MS_SEMAPHORE_ENABLED
  192. const bool bIntraProcess = mSemaphoreData.mbIntraProcess;
  193. #else
  194. const bool bIntraProcess = false;
  195. #endif
  196. if(bIntraProcess) // If this is true, we are using the fast semaphore pathway.
  197. {
  198. // It is by design that a semaphore post does a full memory barrier.
  199. // We don't need such a barrier for this pathway to work, but rather
  200. // it is expected by the user that such a barrier is executed. Investigation
  201. // into the choice of a full vs. just read or write barrier has concluded
  202. // (based on the Posix standard) that a full read-write barrier is expected.
  203. EAReadWriteBarrier();
  204. if((mSemaphoreData.mnCancelCount > 0) && (mSemaphoreData.mnCount < 0)) // Much of the time this will evaluate to false due to the first condition.
  205. mSemaphoreData.UpdateCancelCount(InterlockedExchange((LONG*)&mSemaphoreData.mnCancelCount, 0)); // It's possible that mnCancelCount may have decremented down to zero between the previous line of code and this line of code.
  206. const int currentCount = mSemaphoreData.mnCount;
  207. if((mSemaphoreData.mnMaxCount - count) < currentCount) // If count would cause an overflow...
  208. return kResultError; // We do what most OS implementations of max-count do. count = (mSemaphoreData.mnMaxCount - currentCount);
  209. const int32_t nWaiterCount = -InterlockedExchangeAdd((LONG*)&mSemaphoreData.mnCount, count); // InterlockedExchangeAdd returns the initial value of mnCount. If it's below zero, then it's a count of waiters.
  210. const int nNewCount = count - nWaiterCount;
  211. if(nWaiterCount > 0) // If there were waiters blocking...
  212. {
  213. const int32_t nReleaseCount = (count < nWaiterCount) ? count : nWaiterCount; // Call ReleaseSemaphore for as many waiters as possible.
  214. ReleaseSemaphore(mSemaphoreData.mhSemaphore, nReleaseCount, NULL); // Note that by the time this executes, nReleaseCount might be > than the actual number of waiting threads, due to timeouts.
  215. }
  216. return (nNewCount > 0 ? nNewCount : 0);
  217. }
  218. else
  219. {
  220. const int32_t nPreviousCount = InterlockedExchangeAdd((LONG*)&mSemaphoreData.mnCount, count);
  221. const int nNewCount = nPreviousCount + count;
  222. const BOOL result = ReleaseSemaphore(mSemaphoreData.mhSemaphore, count, NULL);
  223. if(!result)
  224. {
  225. InterlockedExchangeAdd((LONG*)&mSemaphoreData.mnCount, -count);
  226. EAT_ASSERT(result);
  227. return kResultError;
  228. }
  229. return nNewCount;
  230. }
  231. }
  232. return (int)mSemaphoreData.mnCount; // We don't worry if this value is changing. There is nothing that you can rely upon about this value anyway.
  233. }
  234. int EA::Thread::Semaphore::GetCount() const
  235. {
  236. // Under our fast pathway, mnCount can go below zero.
  237. // Under the fast pathway, we need to add mnCancelCount to mnCount because mnCount is negative by the number of waiters and mnCancelCount is the number of waiters that have abandoned waiting but the value hasn't been rolled back into mnCount yet.
  238. EAReadBarrier();
  239. const int count = (int)mSemaphoreData.mnCount + (int)mSemaphoreData.mnCancelCount; // Make temporary to avoid race condition in ternary operator below.
  240. return (count > 0 ? count : 0);
  241. }
  242. #endif // EA_PLATFORM_XXX