eathread_semaphore_unix.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. ///////////////////////////////////////////////////////////////////////////////
  2. // Copyright (c) Electronic Arts Inc. All rights reserved.
  3. ///////////////////////////////////////////////////////////////////////////////
  4. #include <EABase/eabase.h>
  5. #include <eathread/eathread_semaphore.h>
  6. #if defined(EA_PLATFORM_UNIX) || EA_POSIX_THREADS_AVAILABLE
  7. #include <time.h>
  8. #include <errno.h>
  9. #include <string.h>
  10. #include <stdio.h>
  11. #include <limits.h>
  12. #ifdef EA_PLATFORM_WINDOWS
  13. EA_DISABLE_ALL_VC_WARNINGS()
  14. #include <pthread.h>
  15. #include <Windows.h> // Presumably we are using pthreads-win32.
  16. EA_RESTORE_ALL_VC_WARNINGS()
  17. #ifdef CreateSemaphore
  18. #undef CreateSemaphore // Windows #defines CreateSemaphore to CreateSemaphoreA or CreateSemaphoreW.
  19. #endif
  20. #endif
  21. EASemaphoreData::EASemaphoreData()
  22. : mnCount(0), mnMaxCount(INT_MAX)
  23. {
  24. memset(&mSemaphore, 0, sizeof(mSemaphore));
  25. }
  26. EA::Thread::SemaphoreParameters::SemaphoreParameters(int initialCount, bool bIntraProcess, const char* /*pName*/)
  27. : mInitialCount(initialCount), mMaxCount(INT_MAX), mbIntraProcess(bIntraProcess)
  28. {
  29. }
  30. EA::Thread::Semaphore::Semaphore(const SemaphoreParameters* pSemaphoreParameters, bool bDefaultParameters)
  31. {
  32. if(!pSemaphoreParameters && bDefaultParameters)
  33. {
  34. SemaphoreParameters parameters;
  35. Init(&parameters);
  36. }
  37. else
  38. Init(pSemaphoreParameters);
  39. }
  40. EA::Thread::Semaphore::Semaphore(int initialCount)
  41. {
  42. SemaphoreParameters parameters(initialCount);
  43. Init(&parameters);
  44. }
  45. EA::Thread::Semaphore::~Semaphore()
  46. {
  47. #if defined(EA_PLATFORM_ANDROID)
  48. sem_destroy(&mSemaphoreData.mSemaphore); // Android's sem_destroy is broken. http://code.google.com/p/android/issues/detail?id=3106
  49. #else
  50. int result = -1;
  51. for(;;)
  52. {
  53. result = sem_destroy(&mSemaphoreData.mSemaphore);
  54. if((result == -1) && (errno == EBUSY)) // If another thread or process is blocked on this semaphore...
  55. ThreadSleep(kTimeoutYield); // Yield. If we don't yield, it's possible we could block other other threads or processes from running, on some systems.
  56. else
  57. break;
  58. }
  59. EAT_ASSERT(result != -1);
  60. #endif
  61. }
  62. bool EA::Thread::Semaphore::Init(const SemaphoreParameters* pSemaphoreParameters)
  63. {
  64. if(pSemaphoreParameters)
  65. {
  66. mSemaphoreData.mnCount = pSemaphoreParameters->mInitialCount;
  67. mSemaphoreData.mnMaxCount = pSemaphoreParameters->mMaxCount;
  68. if(mSemaphoreData.mnCount < 0)
  69. mSemaphoreData.mnCount = 0;
  70. mSemaphoreData.mbIntraProcess = pSemaphoreParameters->mbIntraProcess;
  71. int result = sem_init(&mSemaphoreData.mSemaphore, mSemaphoreData.mbIntraProcess ? 1 : 0, (unsigned)mSemaphoreData.mnCount);
  72. // To consider: Remove this fallback and simply return false if the first attempt failed.
  73. if((result == -1) && mSemaphoreData.mbIntraProcess)
  74. {
  75. result = sem_init(&mSemaphoreData.mSemaphore, 0, (unsigned)mSemaphoreData.mnCount);
  76. if(result == -1)
  77. {
  78. EAT_ASSERT(false);
  79. memset(&mSemaphoreData.mSemaphore, 0, sizeof(mSemaphoreData.mSemaphore));
  80. }
  81. else
  82. mSemaphoreData.mbIntraProcess = false;
  83. }
  84. return (result != -1);
  85. }
  86. return false;
  87. }
  88. int EA::Thread::Semaphore::Wait(const ThreadTime& timeoutAbsolute)
  89. {
  90. int result;
  91. if(timeoutAbsolute == kTimeoutNone)
  92. {
  93. // We retry waits that were interrupted by signals. Should we instead require
  94. // the user to deal with this and return an error value? Or should we require
  95. // the user to disable the appropriate signal interruptions?
  96. while(((result = sem_wait(&mSemaphoreData.mSemaphore)) == -1) && (errno == EINTR))
  97. continue;
  98. if(result == -1)
  99. {
  100. EAT_ASSERT(false); // This is an error condition.
  101. return kResultError;
  102. }
  103. }
  104. else if(timeoutAbsolute == kTimeoutImmediate)
  105. {
  106. // The sem_trywait() and sem_wait() functions shall return zero if the calling process successfully
  107. // performed the semaphore lock operation on the semaphore designated by sem. If the call was
  108. // unsuccessful, the state of the semaphore shall be unchanged, and the function shall return a
  109. // value of -1 and set errno to indicate the error.
  110. int trywaitResult = sem_trywait(&mSemaphoreData.mSemaphore);
  111. if(trywaitResult == -1)
  112. {
  113. if(errno == EAGAIN) // The sem_* family of functions are different from pthreads because they set errno instead of returning an error value.
  114. return kResultTimeout;
  115. #ifdef EA_PLATFORM_WINDOWS // On Windows, the errno mechanism doesn't work unless you
  116. if(mSemaphoreData.mnCount == 0) // are using the C runtime library as a shared dll between
  117. return kResultTimeout; // the app and pthreads.dll. We try to account for the existence
  118. #endif // of this problem here in a somewhat conservative way.
  119. return kResultError;
  120. }
  121. // Android sem_trywait is broken and in earlier versions returns EAGAIN instead of setting
  122. // errno to EAGAIN. http://source-android.frandroid.com/bionic/libc/docs/CHANGES.TXT
  123. #if defined(EA_PLATFORM_ANDROID)
  124. if(trywaitResult == EAGAIN)
  125. return kResultTimeout;
  126. #endif
  127. }
  128. else
  129. {
  130. // Some systems don't have a sem_timedwait. In these cases we
  131. // fall back to a polling mechanism. However, polling really
  132. // isn't proper because the polling thread might be at a greater
  133. // priority level than the lock-owning thread and thus this code
  134. // might not work as well as desired.
  135. #if defined(EA_PLATFORM_LINUX) || defined(EA_PLATFORM_WINDOWS)
  136. // We retry waits that were interrupted by signals. Should we instead require
  137. // the user to deal with this and return an error value? Or should we require
  138. // the user to disable the appropriate signal interruptions?
  139. while(((result = sem_timedwait(&mSemaphoreData.mSemaphore, &timeoutAbsolute)) == -1) && (errno == EINTR))
  140. continue;
  141. if(result == -1)
  142. {
  143. if(errno == ETIMEDOUT) // The sem_* family of functions are different from pthreads because they set errno instead of returning an error value.
  144. return kResultTimeout;
  145. #ifdef EA_PLATFORM_WINDOWS // On Windows, the errno mechanism doesn't work unless you
  146. if(mSemaphoreData.mnCount == 0) // are using the C runtime library as a shared dll between
  147. return kResultTimeout; // the app and pthreads.dll. We try to account for the existence
  148. #endif // of this problem here in a somewhat conservative way.
  149. return kResultError;
  150. }
  151. #else
  152. // BSD family of Unixes usually lack sem_trywait as of this writing.
  153. // This is a major problem, as a polling solution doesn't work under some circumstances.
  154. while(((result = sem_trywait(&mSemaphoreData.mSemaphore)) == -1) && (errno == EAGAIN || errno == EINTR) && (GetThreadTime() < timeoutAbsolute))
  155. ThreadSleep(1);
  156. if(result == -1)
  157. {
  158. if(errno == EAGAIN)
  159. return kResultTimeout;
  160. return kResultError;
  161. }
  162. #endif
  163. }
  164. EAT_ASSERT(mSemaphoreData.mnCount > 0);
  165. return (int)mSemaphoreData.mnCount.Decrement(); // AtomicInt32 operation. Note that the value of the semaphore count could change from the returned value by the time the caller reads it. This is fine but the user should understand this.
  166. }
  167. int EA::Thread::Semaphore::Post(int count)
  168. {
  169. // Some systems have a sem_post_multiple which we could take advantage
  170. // of here to atomically post multiple times.
  171. EAT_ASSERT(mSemaphoreData.mnCount >= 0);
  172. // It's hard to correctly implement mnMaxCount here, given that it
  173. // may be modified by multiple threads during this execution. So if you want
  174. // to use max-count with an IntraProcess semaphore safely then you need to
  175. // post only from a single thread, or at least a single thread at a time.
  176. int currentCount = mSemaphoreData.mnCount;
  177. // If count would cause an overflow exit early
  178. if ((mSemaphoreData.mnMaxCount - count) < currentCount)
  179. return kResultError;
  180. currentCount += count;
  181. while(count-- > 0)
  182. {
  183. ++mSemaphoreData.mnCount; // AtomicInt32 operation.
  184. if(sem_post(&mSemaphoreData.mSemaphore) == -1)
  185. {
  186. --mSemaphoreData.mnCount; // AtomicInt32 operation.
  187. EAT_ASSERT(false);
  188. return kResultError;
  189. }
  190. }
  191. // If all count posts occurred...
  192. return currentCount; // It's possible that another thread may have modified this value since we changed it, but that's not important.
  193. }
  194. int EA::Thread::Semaphore::GetCount() const
  195. {
  196. return (int)mSemaphoreData.mnCount;
  197. }
  198. #endif // EA_PLATFORM_XXX