TestThreadFutex.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  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 <EAStdC/EAString.h>
  8. #include <eathread/eathread_futex.h>
  9. #include <eathread/eathread_thread.h>
  10. #include <eathread/eathread_atomic.h>
  11. #include <eathread/eathread_mutex.h>
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #if defined(EA_PLATFORM_MICROSOFT)
  15. #pragma warning(push, 0)
  16. #include <Windows.h>
  17. #pragma warning(pop)
  18. #endif
  19. #if defined(_MSC_VER)
  20. #pragma warning(disable: 4996) // This function or variable may be unsafe / deprecated.
  21. #endif
  22. using namespace EA::Thread;
  23. typedef intptr_t (*FutexTestThreadFunction)(void*);
  24. const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
  25. ///////////////////////////////////////////////////////////////////////////////
  26. // FWorkData
  27. //
  28. struct FWorkData
  29. {
  30. volatile bool mbShouldQuit;
  31. Futex mFutex;
  32. char mUnused[7000]; // We intentionally put a big buffer between these.
  33. int mThreadWithLock[kMaxConcurrentThreadCount];
  34. AtomicInt32 mThreadCount;
  35. AtomicInt32 mnErrorCount;
  36. FWorkData() : mbShouldQuit(false), mFutex(), mThreadCount(0), mnErrorCount(0) { memset(mThreadWithLock, 0, sizeof(mThreadWithLock)); }
  37. private:
  38. // Prevent default generation of these functions by not defining them
  39. FWorkData(const FWorkData& rhs); // copy constructor
  40. FWorkData& operator=(const FWorkData& rhs); // assignment operator
  41. };
  42. ///////////////////////////////////////////////////////////////////////////////
  43. // TestThreadFutexSingle
  44. //
  45. static int TestThreadFutexSingle()
  46. {
  47. // Formerly declared as a global because VC++ for XBox 360 was found to be possibly throwing
  48. // away the atomic operations when it found that the futex was local to the function.
  49. // Now that Xbox 360 support has been removed, the futex has been tentatively moved back to
  50. // local scope. Of course, if this problem manifests again, the change will be reversed.
  51. Futex futexSingle;
  52. int nErrorCount(0);
  53. EA::UnitTest::ReportVerbosity(1, "\nSimple test...\n");
  54. // Single-threaded tests
  55. int nLockCount;
  56. EATEST_VERIFY_MSG(!futexSingle.HasLock(), "Futex failure.");
  57. nLockCount = futexSingle.GetLockCount();
  58. EATEST_VERIFY_MSG(nLockCount == 0, "Futex failure.");
  59. futexSingle.Lock();
  60. nLockCount = futexSingle.GetLockCount();
  61. EATEST_VERIFY_MSG(nLockCount == 1, "Futex failure.");
  62. EATEST_VERIFY_MSG(futexSingle.HasLock(), "Futex failure.");
  63. futexSingle.Lock();
  64. nLockCount = futexSingle.GetLockCount();
  65. EATEST_VERIFY_MSG(nLockCount == 2, "Futex failure.");
  66. futexSingle.Unlock();
  67. nLockCount = futexSingle.GetLockCount();
  68. EATEST_VERIFY_MSG(nLockCount == 1, "Futex failure.");
  69. futexSingle.Unlock();
  70. nLockCount = futexSingle.GetLockCount();
  71. EATEST_VERIFY_MSG(nLockCount == 0, "Futex failure.");
  72. futexSingle.Lock();
  73. nLockCount = futexSingle.GetLockCount();
  74. EATEST_VERIFY_MSG(nLockCount == 1, "Futex failure.");
  75. futexSingle.Unlock();
  76. nLockCount = futexSingle.GetLockCount();
  77. EATEST_VERIFY_MSG(nLockCount == 0, "Futex failure.");
  78. EATEST_VERIFY_MSG(!futexSingle.HasLock(), "Futex failure.");
  79. return nErrorCount;
  80. }
  81. ///////////////////////////////////////////////////////////////////////////////
  82. // FutexTestThreadFunction1
  83. //
  84. static intptr_t FutexTestThreadFunction1(void* pvWorkData)
  85. {
  86. int nErrorCount = 0;
  87. FWorkData* pWorkData = (FWorkData*)pvWorkData;
  88. const int32_t nThreadIndex = pWorkData->mThreadCount++;
  89. ThreadId threadId = GetThreadId();
  90. EA::UnitTest::ReportVerbosity(1, "FutexTestThreadFunction1 created: %s, thread index %d\n", EAThreadThreadIdToString(threadId), nThreadIndex);
  91. while(!pWorkData->mbShouldQuit)
  92. {
  93. int nRecursiveLockCount(rand() % 3);
  94. int i;
  95. for(i = 0; i < nRecursiveLockCount; ++i)
  96. {
  97. pWorkData->mFutex.Lock();
  98. pWorkData->mThreadWithLock[nThreadIndex]++;
  99. for(int j = 0; j < kMaxConcurrentThreadCount; ++j)
  100. {
  101. // Make sure this thread has the lock.
  102. EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId));
  103. }
  104. ThreadCooperativeYield(); // Used by cooperative threading platforms.
  105. }
  106. for(; nRecursiveLockCount > 0; --nRecursiveLockCount)
  107. {
  108. pWorkData->mThreadWithLock[nThreadIndex]--;
  109. // Verify that N locks are set.
  110. int nLockCount = pWorkData->mFutex.GetLockCount();
  111. EATEST_VERIFY_MSG(nLockCount == nRecursiveLockCount, "Futex failure.");
  112. if(nLockCount != nRecursiveLockCount)
  113. pWorkData->mbShouldQuit = true;
  114. // Verify the unlock result.
  115. pWorkData->mFutex.Unlock();
  116. nLockCount = pWorkData->mFutex.GetLockCount();
  117. EATEST_VERIFY_MSG((nRecursiveLockCount == 1) || (nLockCount != nRecursiveLockCount), "Futex failure.");
  118. if(nRecursiveLockCount > 1) // If we have remaining locks...
  119. {
  120. for(int j = 0; j < kMaxConcurrentThreadCount; ++j)
  121. {
  122. // Make sure this thread has the lock.
  123. EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId));
  124. }
  125. }
  126. ThreadCooperativeYield(); // Used by cooperative threading platforms.
  127. }
  128. if(nErrorCount)
  129. pWorkData->mbShouldQuit = true;
  130. else
  131. EA::UnitTest::ThreadSleepRandom(100, 200);
  132. }
  133. pWorkData->mnErrorCount += nErrorCount;
  134. return 0;
  135. }
  136. ///////////////////////////////////////////////////////////////////////////////
  137. // FutexTestThreadFunction2
  138. //
  139. // In this function we test Lock, attempting to detect memory synchronization
  140. // problems that could occur with an incorrect Futex implementation.
  141. //
  142. static intptr_t FutexTestThreadFunction2(void* pvWorkData)
  143. {
  144. int nErrorCount = 0;
  145. FWorkData* pWorkData = (FWorkData*)pvWorkData;
  146. const int32_t nThreadIndex = pWorkData->mThreadCount++;
  147. ThreadId threadId = GetThreadId();
  148. EA::UnitTest::ReportVerbosity(1, "FutexTestThreadFunction2 created: %s, thread index %d\n", EAThreadThreadIdToString(threadId), nThreadIndex);
  149. while(!pWorkData->mbShouldQuit)
  150. {
  151. pWorkData->mFutex.Lock();
  152. pWorkData->mThreadWithLock[nThreadIndex] = 1;
  153. for(int j = 0; j < kMaxConcurrentThreadCount; ++j)
  154. {
  155. // Make sure this thread has the lock.
  156. EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId));
  157. }
  158. pWorkData->mThreadWithLock[nThreadIndex] = 0;
  159. pWorkData->mFutex.Unlock();
  160. ThreadCooperativeYield(); // Used by cooperative threading platforms.
  161. if(nErrorCount)
  162. pWorkData->mbShouldQuit = true;
  163. }
  164. pWorkData->mnErrorCount += nErrorCount;
  165. return 0;
  166. }
  167. ///////////////////////////////////////////////////////////////////////////////
  168. // FutexTestThreadFunction3
  169. //
  170. // In this function we test TryLock.
  171. //
  172. static intptr_t FutexTestThreadFunction3(void* pvWorkData)
  173. {
  174. int nErrorCount = 0;
  175. FWorkData* pWorkData = (FWorkData*)pvWorkData;
  176. const int32_t nThreadIndex = pWorkData->mThreadCount++;
  177. ThreadId threadId = GetThreadId();
  178. EA::UnitTest::ReportVerbosity(1, "FutexTestThreadFunction3 created: %s, thread index %d\n", EAThreadThreadIdToString(threadId), nThreadIndex);
  179. for(int i = 0; !pWorkData->mbShouldQuit; ++i)
  180. {
  181. // We make sure to mix TryLock usage with Lock usage.
  182. bool bResult;
  183. if(((i % 4) != 0)) // 3/4 of the time, use TryLock, 1/4 of the time, use Lock.
  184. bResult = pWorkData->mFutex.TryLock();
  185. else
  186. {
  187. pWorkData->mFutex.Lock();
  188. bResult = true;
  189. }
  190. if(bResult)
  191. {
  192. pWorkData->mThreadWithLock[nThreadIndex] = 1;
  193. for(int j = 0; j < kMaxConcurrentThreadCount; ++j)
  194. {
  195. // Make sure this thread has the lock.
  196. EATEST_VERIFY_F((j == nThreadIndex) || !pWorkData->mThreadWithLock[j], "Futex failure (thread %s)\n", EAThreadThreadIdToString(threadId));
  197. }
  198. pWorkData->mThreadWithLock[nThreadIndex] = 0;
  199. pWorkData->mFutex.Unlock();
  200. if(nErrorCount)
  201. pWorkData->mbShouldQuit = true;
  202. }
  203. ThreadCooperativeYield(); // Used by cooperative threading platforms.
  204. }
  205. pWorkData->mnErrorCount += nErrorCount;
  206. return 0;
  207. }
  208. ///////////////////////////////////////////////////////////////////////////////
  209. // TestThreadFutexMulti
  210. //
  211. static int TestThreadFutexMulti(FutexTestThreadFunction pFutexTestThreadFunction, int nThreadCount)
  212. {
  213. int nErrorCount(0);
  214. FWorkData* const pWorkData = new FWorkData;
  215. Thread thread[kMaxConcurrentThreadCount];
  216. Thread::Status status;
  217. int i;
  218. EA::UnitTest::ReportVerbosity(1, "Multithreaded test...\n");
  219. if(nThreadCount > kMaxConcurrentThreadCount)
  220. nThreadCount = kMaxConcurrentThreadCount;
  221. for(i = 0; i < nThreadCount; i++)
  222. thread[i].Begin(pFutexTestThreadFunction, pWorkData);
  223. EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000);
  224. pWorkData->mbShouldQuit = true;
  225. for(i = 0; i < nThreadCount; i++)
  226. {
  227. status = thread[i].WaitForEnd(GetThreadTime() + 30000);
  228. EATEST_VERIFY_MSG(status == EA::Thread::Thread::kStatusEnded, "Futex/Thread failure: Thread(s) didn't end.");
  229. }
  230. nErrorCount += (int)pWorkData->mnErrorCount;
  231. delete pWorkData;
  232. return nErrorCount;
  233. }
  234. ///////////////////////////////////////////////////////////////////////////////
  235. // TestThreadFutexSpeed
  236. //
  237. static int TestThreadFutexSpeed()
  238. {
  239. int nErrorCount = 0;
  240. size_t i;
  241. uint64_t t0, t1, tDelta;
  242. const size_t kLoopCount = 1000000;
  243. EA::UnitTest::ReportVerbosity(1, "\nSpeed test...\n");
  244. //////////////////////////////////////////////////
  245. // Futex
  246. {
  247. Futex f;
  248. t0 = EA::StdC::Stopwatch::GetCPUCycle();
  249. f.Lock();
  250. f.Unlock();
  251. t1 = EA::StdC::Stopwatch::GetCPUCycle();
  252. t0 = EA::StdC::Stopwatch::GetCPUCycle();
  253. for(i = 0; i < kLoopCount; i++)
  254. {
  255. f.Lock();
  256. f.Unlock();
  257. }
  258. t1 = EA::StdC::Stopwatch::GetCPUCycle();
  259. tDelta = t1 - t0;
  260. EA::UnitTest::ReportVerbosity(1, "Futex time (ticks): %" PRIu64 "\n", tDelta);
  261. }
  262. //////////////////////////////////////////////////
  263. //////////////////////////////////////////////////
  264. // Mutex
  265. {
  266. EA::Thread::Mutex m;
  267. t0 = EA::StdC::Stopwatch::GetCPUCycle();
  268. m.Lock();
  269. m.Unlock();
  270. t1 = EA::StdC::Stopwatch::GetCPUCycle();
  271. t0 = EA::StdC::Stopwatch::GetCPUCycle();
  272. for(i = 0; i < kLoopCount; i++)
  273. {
  274. m.Lock();
  275. m.Unlock();
  276. }
  277. t1 = EA::StdC::Stopwatch::GetCPUCycle();
  278. tDelta = t1 - t0;
  279. EA::UnitTest::ReportVerbosity(1, "Mutex time (ticks): %" PRIu64 "\n", tDelta);
  280. }
  281. //////////////////////////////////////////////////
  282. #if defined(EA_PLATFORM_MICROSOFT) && !defined(EA_PLATFORM_WINDOWS_PHONE) && !(defined(EA_PLATFORM_WINDOWS) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP))
  283. //////////////////////////////////////////////////
  284. // HMUTEX
  285. {
  286. HANDLE hMutex = CreateMutexA(NULL, false, NULL);
  287. if (hMutex != NULL)
  288. {
  289. WaitForSingleObject(hMutex, INFINITE);
  290. ReleaseMutex(hMutex);
  291. t0 = EA::StdC::Stopwatch::GetCPUCycle();
  292. for(i = 0; i < kLoopCount; i++)
  293. {
  294. WaitForSingleObject(hMutex, INFINITE);
  295. ReleaseMutex(hMutex);
  296. }
  297. t1 = EA::StdC::Stopwatch::GetCPUCycle();
  298. tDelta = t1 - t0;
  299. CloseHandle(hMutex);
  300. EA::UnitTest::ReportVerbosity(1, "Windows HMUTEX time (ticks): %" PRIu64 "\n", tDelta);
  301. }
  302. }
  303. //////////////////////////////////////////////////
  304. //////////////////////////////////////////////////
  305. // Critical section
  306. {
  307. CRITICAL_SECTION cs;
  308. InitializeCriticalSection(&cs);
  309. EnterCriticalSection(&cs);
  310. LeaveCriticalSection(&cs);
  311. t0 = EA::StdC::Stopwatch::GetCPUCycle();
  312. for(i = 0; i < kLoopCount; i++)
  313. {
  314. EnterCriticalSection(&cs);
  315. LeaveCriticalSection(&cs);
  316. }
  317. t1 = EA::StdC::Stopwatch::GetCPUCycle();
  318. tDelta = t1 - t0;
  319. DeleteCriticalSection(&cs);
  320. EA::UnitTest::ReportVerbosity(1, "Windows CriticalSection time (ticks): %" PRIu64 "\n", tDelta);
  321. }
  322. //////////////////////////////////////////////////
  323. #endif
  324. return nErrorCount;
  325. }
  326. static int TestThreadFutexRegressions()
  327. {
  328. int nErrorCount(0);
  329. #if EA_THREADS_AVAILABLE && EATHREAD_DEBUG_FUTEX_HANG_ENABLED
  330. {
  331. // Test the ability of Futex to report the callstack of another thread holding a futex.
  332. struct FutexCallstackTestThread : public EA::Thread::IRunnable
  333. {
  334. ThreadParameters mThreadParams; //
  335. EA::Thread::Thread mThread; // The Thread object.
  336. EA::Thread::Futex* mpFutex; // A Futex.
  337. FutexCallstackTestThread() : mThreadParams(), mThread(), mpFutex(NULL) {}
  338. FutexCallstackTestThread(const FutexCallstackTestThread&){} // Avoid compiler warnings.
  339. void operator=(const FutexCallstackTestThread&){} // Avoid compiler warnings.
  340. intptr_t Run(void*)
  341. {
  342. if(EA::StdC::Strstr(mThreadParams.mpName, "0")) // If we are thread 0...
  343. {
  344. mpFutex->Lock();
  345. mpFutex->Lock();
  346. EA::Thread::ThreadSleep(4000);
  347. mpFutex->Unlock();
  348. mpFutex->Unlock();
  349. }
  350. else
  351. {
  352. mpFutex->Lock(); // This should result in a printf tracing two thread 0 lock callstacks.
  353. mpFutex->Unlock();
  354. }
  355. return 0;
  356. }
  357. };
  358. FutexCallstackTestThread thread[2];
  359. EA::Thread::Futex futex;
  360. for(int i = 0; i < 3; i++)
  361. {
  362. thread[0].mThreadParams.mpName = "FutexTest0";
  363. thread[0].mpFutex = &futex;
  364. thread[0].mThread.Begin(&thread[0], NULL, &thread[0].mThreadParams);
  365. EA::UnitTest::ThreadSleep(300);
  366. thread[1].mThreadParams.mpName = "FutexTest1";
  367. thread[1].mpFutex = &futex;
  368. thread[1].mThread.Begin(&thread[1], NULL, &thread[1].mThreadParams);
  369. EA::UnitTest::ThreadSleep(5000);
  370. for(int i = 0; i < 2; i++)
  371. thread[i].mThread.WaitForEnd();
  372. }
  373. }
  374. #endif
  375. return nErrorCount;
  376. }
  377. #define EATHREAD_DEBUG_FUTEX_HAMMER_ENABLED 1
  378. // Check multithreaded correctness of Futex.
  379. // Here we hammer on the futex via multiple threads while incrementing non atomic counter
  380. // if the lock ever fails the global count will be incorrect.
  381. volatile uint32_t gCommonCount = 0;
  382. static int TestThreadFutexHammer()
  383. {
  384. int nErrorCount(0);
  385. #if EA_THREADS_AVAILABLE && EATHREAD_DEBUG_FUTEX_HAMMER_ENABLED
  386. {
  387. static const int NUM_SPINNING_THREADS = 4;
  388. static const uint32_t MAX_NUM_LOOPS = 1 << 5;
  389. // Test the ability of Futex to report the callstack of another thread holding a futex.
  390. struct FutexCallstackTestThread : public EA::Thread::IRunnable
  391. {
  392. ThreadParameters mThreadParams; //
  393. EA::Thread::Thread mThread; // The Thread object.
  394. EA::Thread::Futex* mpFutex; // A Futex.
  395. uint32_t mThreadLocalId;
  396. FutexCallstackTestThread() : mThreadParams(), mThread(), mpFutex(NULL) {}
  397. FutexCallstackTestThread(const FutexCallstackTestThread&) {} // Avoid compiler warnings.
  398. void operator=(const FutexCallstackTestThread&) {} // Avoid compiler warnings.
  399. intptr_t Run(void*)
  400. {
  401. for (uint32_t i = 0; i < MAX_NUM_LOOPS; i++)
  402. {
  403. mpFutex->Lock();
  404. gCommonCount++;
  405. mpFutex->Unlock();
  406. }
  407. return 0;
  408. }
  409. };
  410. FutexCallstackTestThread thread[NUM_SPINNING_THREADS];
  411. EA::Thread::Futex futex;
  412. gCommonCount = 0; // for multiple runs
  413. for(int i = 0; i < NUM_SPINNING_THREADS; i++)
  414. {
  415. thread[i].mpFutex = &futex;
  416. thread[i].mThread.Begin(&thread[i], NULL, &thread[i].mThreadParams);
  417. }
  418. EA::UnitTest::ThreadSleep(13);
  419. for(int i = 0; i < NUM_SPINNING_THREADS; i++)
  420. thread[i].mThread.WaitForEnd();
  421. EATEST_VERIFY_MSG(gCommonCount == NUM_SPINNING_THREADS * MAX_NUM_LOOPS, "Multithreaded futex test failed, non atomic counter is incorrect");
  422. }
  423. #endif
  424. return nErrorCount;
  425. }
  426. ///////////////////////////////////////////////////////////////////////////////
  427. // TestThreadFutex
  428. //
  429. int TestThreadFutex()
  430. {
  431. int nErrorCount(0);
  432. nErrorCount += TestThreadFutexSingle();
  433. nErrorCount += TestThreadFutexSpeed();
  434. nErrorCount += TestThreadFutexRegressions();
  435. // hammer on the futex a few times
  436. for(int j=0; j <1;j++)
  437. {
  438. nErrorCount += TestThreadFutexHammer();
  439. }
  440. #if EA_THREADS_AVAILABLE
  441. nErrorCount += TestThreadFutexMulti(FutexTestThreadFunction1, 4);
  442. nErrorCount += TestThreadFutexMulti(FutexTestThreadFunction2, 4);
  443. nErrorCount += TestThreadFutexMulti(FutexTestThreadFunction3, 4);
  444. #endif
  445. return nErrorCount;
  446. }