TestThreadSemaphore.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. ///////////////////////////////////////////////////////////////////////////////
  2. // Copyright (c) Electronic Arts Inc. All rights reserved.
  3. ///////////////////////////////////////////////////////////////////////////////
  4. #include "TestThread.h"
  5. #include <EATest/EATest.h>
  6. #include <EAStdC/EAStopwatch.h>
  7. #include <eathread/eathread.h>
  8. #include <eathread/eathread_semaphore.h>
  9. #include <eathread/eathread_thread.h>
  10. #include <eathread/eathread_atomic.h>
  11. using namespace EA::Thread;
  12. const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
  13. struct SWorkData
  14. {
  15. volatile bool mbShouldQuit;
  16. Semaphore mSemaphore;
  17. int mnPostCount; // The count to use for Semaphore::Post()
  18. AtomicInt32 mnExpectedCount;
  19. AtomicInt32 mnThreadCount;
  20. AtomicInt32 mnErrorCount;
  21. SWorkData(const SemaphoreParameters& sp)
  22. : mbShouldQuit(false), mSemaphore(&sp, false),
  23. mnPostCount(1), mnExpectedCount(0), mnThreadCount(0),
  24. mnErrorCount(0) {}
  25. // define copy ctor and assignment operator
  26. // so the compiler does define them intrisically
  27. SWorkData(const SWorkData& rhs); // copy constructor
  28. SWorkData& operator=(const SWorkData& rhs); // assignment operator
  29. };
  30. static intptr_t SemaphoreTestThreadFunction(void* pvWorkData)
  31. {
  32. SWorkData* pWorkData = (SWorkData*)pvWorkData;
  33. int nErrorCount = 0;
  34. ThreadId threadId = GetThreadId();
  35. EA::UnitTest::ReportVerbosity(1, "Semaphore test function created: %s\n", EAThreadThreadIdToString(threadId));
  36. const AtomicInt32::ValueType nThreadCount = pWorkData->mnThreadCount++; // AtomicInt32 operation.
  37. const AtomicInt32::ValueType nPostCount = pWorkData->mnPostCount;
  38. #if defined(EA_PLATFORM_DESKTOP) // If the platform tends to run on fast hardware...
  39. const unsigned kShortSleepMin = 50;
  40. const unsigned kShortSleepMax = 100;
  41. #else
  42. const unsigned kShortSleepMin = 100;
  43. const unsigned kShortSleepMax = 200;
  44. #endif
  45. while(!pWorkData->mbShouldQuit)
  46. {
  47. if((nThreadCount % 2) == 0) // If the first thread or the third thread...
  48. {
  49. // Create 'work'.
  50. pWorkData->mnExpectedCount += nPostCount; // Atomic operation.
  51. pWorkData->mSemaphore.Post(nPostCount);
  52. EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax);
  53. // Get the amount of 'work' that is expected.
  54. int nWaitCount = nPostCount;
  55. // Wait for the 'work' to be queued and signalled.
  56. while(nWaitCount-- > 0)
  57. {
  58. const int result = pWorkData->mSemaphore.Wait(GetThreadTime() + 2000);
  59. EATEST_VERIFY_MSG((result >= 0) || (result == Semaphore::kResultTimeout), "Semaphore failure 4a: semaphore.Wait()\n");
  60. if(result >= 0) // If we we able to acquire the semaphore...
  61. --pWorkData->mnExpectedCount; // Atomic operation.
  62. // else timeout occurred. Every time we have this timeout we miss decrementing the semaphore by one, and thus the expected semaphore count rises by one. It doesn't mean there's a bug, it just means the expected count migrates higher as we get timeouts.
  63. EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax);
  64. }
  65. }
  66. else
  67. {
  68. // Get the amount of 'work' that is expected.
  69. int nWaitCount = nPostCount;
  70. // Wait for the 'work' to be queued and signalled.
  71. while(nWaitCount-- > 0)
  72. {
  73. const int result = pWorkData->mSemaphore.Wait(GetThreadTime() + 2000);
  74. EATEST_VERIFY_MSG((result >= 0) || (result == Semaphore::kResultTimeout), "Semaphore failure 4b: semaphore.Wait()\n");
  75. if(result >= 0) // If we we able to acquire the semaphore...
  76. --pWorkData->mnExpectedCount; // Atomic operation.
  77. // else timeout occurred. Every time we have this timeout we miss decrementing the semaphore by one, and thus the expected semaphore count rises by one. It doesn't mean there's a bug, it just means the expected count migrates higher as we get timeouts.
  78. EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax);
  79. }
  80. // Create 'work'.
  81. pWorkData->mnExpectedCount += nPostCount; // Atomic operation.
  82. pWorkData->mSemaphore.Post(nPostCount);
  83. EA::UnitTest::ThreadSleepRandom(kShortSleepMin, kShortSleepMax);
  84. }
  85. EATEST_VERIFY_MSG(pWorkData->mSemaphore.GetCount() >= 0, "Semaphore failure 4c: The count should always be >= 0.\n");
  86. // We restrict this assert to desktop platforms because the expected value migrates upward on every Wait timeout, and on slower platforms there could be a lot of such timeouts.
  87. #if defined(EA_PLATFORM_DESKTOP)
  88. EATEST_VERIFY_MSG(pWorkData->mSemaphore.GetCount() < 200, "Semaphore failure 4d: The count should always be a small value.\n");
  89. #endif
  90. }
  91. pWorkData->mnErrorCount = nErrorCount;
  92. return 0;
  93. }
  94. struct BadSignalTestData
  95. {
  96. EA::Thread::Semaphore mSemaphore; // The Semaphore that we are using.
  97. AtomicInt32 mnOKWaitCount; // The number of Wait timeouts that occurred.
  98. AtomicInt32 mnTimeoutWaitCount; // The number of Wait OK returns that occurred.
  99. AtomicInt32 mnErrorWaitCount; // The number of Wait error returns that occurred.
  100. int mnWaitTimeout; // How long a waiter waits before timing out.
  101. int mnWaitThreadCount; // How many waiter threads there are. Caller sets up this value.
  102. int mnPostThreadCount; // How many poster threads there are. Caller sets up this value.
  103. int mnPostCount; // How many signals the poster should Post. Needs to be less than mnWaitThreadCount for this test. Caller sets up this value.
  104. int mnPostDelay; // How long the poster waits before Posting. Needs to be significantly less than mnWaitTimeout.
  105. BadSignalTestData()
  106. : mSemaphore(), mnOKWaitCount(0), mnTimeoutWaitCount(0), mnErrorWaitCount(0),
  107. mnWaitTimeout(0), mnWaitThreadCount(0), mnPostThreadCount(0),
  108. mnPostCount(0), mnPostDelay(0) { }
  109. // Define copy ctor and assignment operator
  110. // so the compiler does define them intrisically
  111. BadSignalTestData(const BadSignalTestData& rhs); // copy constructor
  112. BadSignalTestData& operator=(const BadSignalTestData& rhs); // assignment operator
  113. };
  114. static intptr_t BadSignalTestWaitFunction(void *data)
  115. {
  116. BadSignalTestData* const pBSTD = (BadSignalTestData*)data;
  117. const int waitResult = pBSTD->mSemaphore.Wait(EA::Thread::GetThreadTime() + pBSTD->mnWaitTimeout);
  118. if(waitResult == Semaphore::kResultTimeout)
  119. pBSTD->mnTimeoutWaitCount.Increment();
  120. else if(waitResult >= 0)
  121. pBSTD->mnOKWaitCount.Increment();
  122. else
  123. pBSTD->mnErrorWaitCount.Increment();
  124. return 0;
  125. }
  126. static intptr_t BadSignalTestPostFunction(void *data)
  127. {
  128. BadSignalTestData* const pBSTD = (BadSignalTestData*)data;
  129. ThreadSleep((ThreadTime) pBSTD->mnPostDelay);
  130. pBSTD->mSemaphore.Post(pBSTD->mnPostCount); // Intentionally post less than the number of waiting threads.
  131. return 0;
  132. }
  133. int TestThreadSemaphore()
  134. {
  135. int nErrorCount(0);
  136. { // Single threaded test.
  137. const int kInitialCount(4);
  138. int nResult;
  139. SemaphoreParameters sp(kInitialCount, false, "SingleThreadTest");
  140. Semaphore semaphore(&sp);
  141. nResult = semaphore.GetCount();
  142. EATEST_VERIFY_F(nResult == kInitialCount, "Semaphore failure 2a: semaphore.GetCount(). result = %d\n", nResult);
  143. // This triggers an assert and so cannot be tested here:
  144. // bResult = semaphore.SetCount(10);
  145. // EATEST_VERIFY_MSG(!bResult, "Semaphore failure in semaphore.SetCount(10)\n");
  146. // Grab the full count, leaving none.
  147. for(int i(0); i < kInitialCount; i++)
  148. {
  149. nResult = semaphore.Wait(kTimeoutNone);
  150. EATEST_VERIFY_F(nResult == kInitialCount - i - 1, "Semaphore failure 2b: semaphore.Wait(kTimeoutNone). result = %d\n", nResult);
  151. nResult = semaphore.GetCount();
  152. EATEST_VERIFY_F(nResult == kInitialCount - i - 1, "Semaphore failure 2c: semaphore.GetCount(). result = %d\n", nResult);
  153. }
  154. nResult = semaphore.Wait(kTimeoutImmediate);
  155. EATEST_VERIFY_F(nResult == Semaphore::kResultTimeout, "Semaphore failure 2d: semaphore.Wait(kTimeoutImmediate). result = %d\n", nResult);
  156. nResult = semaphore.GetCount();
  157. EATEST_VERIFY_F(nResult == 0, "Semaphore failure 2e: semaphore.GetCount(). result = %d\n", nResult);
  158. nResult = semaphore.Post(2);
  159. EATEST_VERIFY_F(nResult == 2, "Semaphore failure 2f: semaphore.Post(2). result = %d\n", nResult);
  160. nResult = semaphore.Post(2);
  161. EATEST_VERIFY_F(nResult == 4, "Semaphore failure 2g: semaphore.Post(2). result = %d\n", nResult);
  162. }
  163. #if EA_THREADS_AVAILABLE
  164. { // Bad signal test.
  165. BadSignalTestData bstd;
  166. Thread thread[4];
  167. // These values need to be set up right or else this test won't work.
  168. // What we are trying to do here is make certain that N number of Semaphore
  169. // waiters time out, while M waiters succeed.
  170. bstd.mnWaitTimeout = 5000;
  171. bstd.mnPostDelay = 2000;
  172. bstd.mnWaitThreadCount = 3; // mnWaitThreadCount + mnPostThreadCount should be [4].
  173. bstd.mnPostThreadCount = 1;
  174. bstd.mnPostCount = 1;
  175. thread[0].Begin(BadSignalTestWaitFunction, &bstd);
  176. thread[1].Begin(BadSignalTestWaitFunction, &bstd);
  177. thread[2].Begin(BadSignalTestWaitFunction, &bstd);
  178. thread[3].Begin(BadSignalTestPostFunction, &bstd);
  179. // Wait for the threads to be completed
  180. thread[0].WaitForEnd();
  181. thread[1].WaitForEnd();
  182. thread[2].WaitForEnd();
  183. thread[3].WaitForEnd();
  184. EATEST_VERIFY_MSG(bstd.mnTimeoutWaitCount == (bstd.mnWaitThreadCount - (bstd.mnPostThreadCount * bstd.mnPostCount)), "Semaphore failure 1a: bad signal test.\n");
  185. EATEST_VERIFY_MSG(bstd.mnErrorWaitCount == 0, "Semaphore failure 1b: bad signal test.\n");
  186. EATEST_VERIFY_MSG(bstd.mnOKWaitCount == (bstd.mnPostThreadCount * bstd.mnPostCount), "Semaphore failure 1c: bad signal test.\n");
  187. }
  188. { // Multithreaded test
  189. // Problem: In the case of the inter-process test below, since we are using a named semaphore it's possible that
  190. // if two instances of this unit test are run on the same machine simultaneously, they will collide because
  191. // they are using the same semaphore. This applies only to true multi-process-capable machines.
  192. uint64_t cycle64 = EA::StdC::Stopwatch::GetCPUCycle();
  193. uint16_t cycle16 = (uint16_t)((uint16_t)(cycle64 >> 48) ^ (uint16_t)(cycle64 >> 32) ^ (uint16_t)(cycle64 >> 16) ^ (uint16_t)(cycle64 >> 0)); // Munge all the bits together because some platforms might have zeroed low bits; others high bits. This will work for all.
  194. char name[16]; sprintf(name, "SMT%u", (unsigned)cycle16);
  195. SemaphoreParameters semaphoreParameters(0, false, name);
  196. #if defined(EA_PLATFORM_DESKTOP)
  197. const int kLoopCount = 16;
  198. #else
  199. const int kLoopCount = 4;
  200. #endif
  201. for(int j = 1; j <= kLoopCount; j++) // Intentionally start with 1, as we use j below.
  202. {
  203. if(semaphoreParameters.mbIntraProcess)
  204. semaphoreParameters.mbIntraProcess = false;
  205. else
  206. semaphoreParameters.mbIntraProcess = true;
  207. SWorkData workData(semaphoreParameters);
  208. workData.mnPostCount = (j % 4);
  209. const int kThreadCount(kMaxConcurrentThreadCount - 1);
  210. Thread thread[kThreadCount];
  211. ThreadId threadId[kThreadCount];
  212. Thread::Status status;
  213. int i;
  214. for(i = 0; i < kThreadCount; i++)
  215. {
  216. threadId[i] = thread[i].Begin(SemaphoreTestThreadFunction, &workData);
  217. EATEST_VERIFY_MSG(threadId[i] != kThreadIdInvalid, "Semaphore failure 3a: Couldn't create thread.\n");
  218. }
  219. EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds*2000, gTestLengthSeconds*2000);
  220. workData.mbShouldQuit = true;
  221. #if defined(EA_PLATFORM_DESKTOP)
  222. EA::UnitTest::ThreadSleepRandom(500, 500);
  223. #else
  224. EA::UnitTest::ThreadSleepRandom(1000, 1000);
  225. #endif
  226. // We seed the semaphore with more posts because timing issues could cause the
  227. // worker threads to get hung waiting for posts but it really isn't because
  228. // of a problem with the semaphore. By the time all threads have quit, the
  229. // expected count should be equal to the increment.
  230. const int kIncrement = 20;
  231. workData.mnExpectedCount += kIncrement; // AtomicInt32 operation
  232. workData.mSemaphore.Post(kIncrement);
  233. #if defined(EA_PLATFORM_DESKTOP)
  234. EA::UnitTest::ThreadSleepRandom(1000, 1000);
  235. #else
  236. EA::UnitTest::ThreadSleepRandom(2000, 2000);
  237. #endif
  238. for(i = 0; i < kThreadCount; i++)
  239. {
  240. if(threadId[i] != kThreadIdInvalid)
  241. {
  242. status = thread[i].WaitForEnd(GetThreadTime() + 30000);
  243. EATEST_VERIFY_MSG(status == Thread::kStatusEnded, "Semaphore failure 3b: Thread(s) didn't end.\n");
  244. }
  245. }
  246. // Normally the Semaphore::GetCount function returns a value that is volatile, but we know that
  247. // there aren't any threads using mSemaphore any more, so GetCount returns a value we can rely on.
  248. EATEST_VERIFY_MSG(workData.mnExpectedCount == workData.mSemaphore.GetCount(), "Semaphore failure 3c: Unexpected value.\n");
  249. if(workData.mnExpectedCount != workData.mSemaphore.GetCount())
  250. {
  251. EA::UnitTest::ReportVerbosity(1, " Thread count: %d, Intraprocess: %s, Post count: %d, Expected count: %d, Semaphore GetCount: %d\n",
  252. kThreadCount, semaphoreParameters.mbIntraProcess ? "yes" : "no", workData.mnPostCount, (int)workData.mnExpectedCount.GetValue(), workData.mSemaphore.GetCount());
  253. }
  254. nErrorCount += (int)workData.mnErrorCount;
  255. }
  256. }
  257. #endif // EA_THREADS_AVAILABLE
  258. return nErrorCount;
  259. }