TestThreadCondition.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. ///////////////////////////////////////////////////////////////////////////////
  2. // Copyright (c) Electronic Arts Inc. All rights reserved.
  3. ///////////////////////////////////////////////////////////////////////////////
  4. #include "TestThread.h"
  5. #include <EATest/EATest.h>
  6. #include <eathread/eathread_condition.h>
  7. #include <eathread/eathread_thread.h>
  8. #include <eathread/eathread_mutex.h>
  9. #include <eathread/eathread_list.h>
  10. #include <eathread/eathread_sync.h>
  11. #include <stdlib.h>
  12. #include <time.h>
  13. using namespace EA::Thread;
  14. ///////////////////////////////////////////////////////////////////////////////
  15. // EATHREAD_INTERPROCESS_CONDITION_SUPPORTED
  16. //
  17. #ifndef EATHREAD_INTERPROCESS_CONDITION_SUPPORTED
  18. #if defined(EA_PLATFORM_MICROSOFT) || defined(EA_PLATFORM_LINUX)
  19. #define EATHREAD_INTERPROCESS_CONDITION_SUPPORTED 1
  20. #else
  21. #define EATHREAD_INTERPROCESS_CONDITION_SUPPORTED 0
  22. #endif
  23. #endif
  24. const int kMaxConcurrentThreadCount = EATHREAD_MAX_CONCURRENT_THREAD_COUNT;
  25. struct TMWorkData
  26. {
  27. volatile bool mbProducersShouldQuit;
  28. volatile bool mbConsumersShouldQuit;
  29. EA::Thread::simple_list<int> mJobList;
  30. Condition mCondition;
  31. Mutex mMutex;
  32. int mnLastJobID;
  33. int mnConditionTimeout;
  34. AtomicInt32 mnTotalJobsCreated;
  35. AtomicInt32 mnTotalJobsCompleted;
  36. TMWorkData( const ConditionParameters* pCondParams ) : mbProducersShouldQuit(false), mbConsumersShouldQuit(false), mCondition( pCondParams ),
  37. mMutex(NULL, true), mnLastJobID(0), mnConditionTimeout(60000), mnTotalJobsCreated(0), mnTotalJobsCompleted(0)
  38. {
  39. // Empty
  40. }
  41. // define copy ctor and assignment operator
  42. // so the compiler does define them intrisically
  43. TMWorkData(const TMWorkData& rhs); // copy constructor
  44. TMWorkData& operator=(const TMWorkData& rhs); // assignment operator
  45. };
  46. static intptr_t ProducerFunction(void* pvWorkData)
  47. {
  48. int nErrorCount = 0;
  49. TMWorkData* pWorkData = (TMWorkData*)pvWorkData;
  50. ThreadId threadId = GetThreadId();
  51. EA::UnitTest::ReportVerbosity(1, "Condition producer test function created: %s\n", EAThreadThreadIdToString(threadId));
  52. EAReadWriteBarrier();
  53. while(!pWorkData->mbProducersShouldQuit)
  54. {
  55. EA::UnitTest::ThreadSleepRandom(100, 200);
  56. pWorkData->mMutex.Lock();
  57. for(int i(0), iEnd(rand() % 3); i < iEnd; i++)
  58. {
  59. const int nJob(++pWorkData->mnLastJobID);
  60. pWorkData->mJobList.push_back(nJob);
  61. ++pWorkData->mnTotalJobsCreated;
  62. EA::UnitTest::ReportVerbosity(1, "Job %d created by %s.\n", nJob, EAThreadThreadIdToString(threadId));
  63. ThreadCooperativeYield(); // Used by cooperative threading platforms.
  64. }
  65. pWorkData->mMutex.Unlock();
  66. pWorkData->mCondition.Signal(false);
  67. ThreadCooperativeYield(); // Used by cooperative threading platforms.
  68. }
  69. EA::UnitTest::ReportVerbosity(1, "Producer exiting: %s.\n", EAThreadThreadIdToString(threadId));
  70. return nErrorCount;
  71. }
  72. static intptr_t ProducerFunction_DoesNotSignal(void* pvWorkData)
  73. {
  74. int nErrorCount = 0;
  75. TMWorkData* pWorkData = (TMWorkData*)pvWorkData;
  76. ThreadId threadId = GetThreadId();
  77. EA_UNUSED(pWorkData);
  78. EA::UnitTest::ReportVerbosity(1, "Condition producer (does not signal) test function created: %s\n", EAThreadThreadIdToString(threadId));
  79. // Intentionally do nothing here. We are testing the conditional variable time out code path by
  80. // ensuring we do not signal the Consumer that any work has been added into the queue for them
  81. // to consume therefor explicitly causing a condition variable timeout.
  82. EA::UnitTest::ReportVerbosity(1, "Producer (does not signal) exiting: %s.\n", EAThreadThreadIdToString(threadId));
  83. return nErrorCount;
  84. }
  85. static intptr_t ConsumerFunction(void* pvWorkData)
  86. {
  87. int nErrorCount = 0;
  88. TMWorkData* pWorkData = (TMWorkData*)pvWorkData;
  89. ThreadId threadId = GetThreadId();
  90. EA::UnitTest::ReportVerbosity(1, "Condition producer test function created: %s\n", EAThreadThreadIdToString(threadId));
  91. pWorkData->mMutex.Lock();
  92. do{
  93. if(!pWorkData->mJobList.empty())
  94. {
  95. const int nJob = pWorkData->mJobList.front();
  96. pWorkData->mJobList.pop_front();
  97. pWorkData->mMutex.Unlock();
  98. ThreadCooperativeYield(); // Used by cooperative threading platforms.
  99. // Here we would actually do the job, but printing 'job done' is enough in itself.
  100. ++pWorkData->mnTotalJobsCompleted;
  101. EA::UnitTest::ReportVerbosity(1, "Job %d done by %s.\n", nJob, EAThreadThreadIdToString(threadId));
  102. pWorkData->mMutex.Lock();
  103. }
  104. else
  105. {
  106. const ThreadTime timeoutAbsolute = GetThreadTime() + pWorkData->mnConditionTimeout;
  107. const Condition::Result result = pWorkData->mCondition.Wait(&pWorkData->mMutex, timeoutAbsolute);
  108. if((result != Condition::kResultOK) && pWorkData->mJobList.empty())
  109. break;
  110. }
  111. }while(!pWorkData->mbConsumersShouldQuit || !pWorkData->mJobList.empty());
  112. pWorkData->mMutex.Unlock();
  113. EA::UnitTest::ReportVerbosity(1, "Consumer exiting: %s.\n", EAThreadThreadIdToString(threadId));
  114. return nErrorCount;
  115. }
  116. int TestThreadCondition()
  117. {
  118. int nErrorCount(0);
  119. { // ctor tests
  120. // We test various combinations of Mutex ctor and ConditionParameters.
  121. // ConditionParameters(bool bIntraProcess = true, const char* pName = NULL);
  122. // Condition(const ConditionParameters* pConditionParameters = NULL, bool bDefaultParameters = true);
  123. ConditionParameters cp1(true, NULL);
  124. ConditionParameters cp2(true, "EATcp2");
  125. #if EATHREAD_INTERPROCESS_CONDITION_SUPPORTED
  126. ConditionParameters cp3(false, NULL);
  127. ConditionParameters cp4(false, "EATcp4");
  128. #else
  129. ConditionParameters cp3(true, NULL);
  130. ConditionParameters cp4(true, "EATcp4");
  131. #endif
  132. // Create separate scopes below because some platforms are so
  133. // limited that they can't create all of them at once.
  134. {
  135. Condition cond1(&cp1, false);
  136. Condition cond2(&cp2, false);
  137. Condition cond3(&cp3, false);
  138. cond1.Signal();
  139. cond2.Signal();
  140. cond3.Signal();
  141. }
  142. {
  143. Condition cond4(&cp4, false);
  144. Condition cond5(NULL, true);
  145. Condition cond6(NULL, false);
  146. cond6.Init(&cp1);
  147. cond4.Signal();
  148. cond5.Signal();
  149. cond6.Signal();
  150. }
  151. }
  152. #if EA_THREADS_AVAILABLE
  153. {
  154. // test producer/consumer wait condition with intra-process condition
  155. {
  156. ConditionParameters exlusiveConditionParams( true, NULL );
  157. TMWorkData workData( &exlusiveConditionParams );
  158. Thread::Status status;
  159. int i;
  160. const int kThreadCount(kMaxConcurrentThreadCount - 1);
  161. Thread threadProducer[kThreadCount];
  162. ThreadId threadIdProducer[kThreadCount];
  163. Thread threadConsumer[kThreadCount];
  164. ThreadId threadIdConsumer[kThreadCount];
  165. // Create producers and consumers.
  166. for(i = 0; i < kThreadCount; i++)
  167. {
  168. threadIdProducer[i] = threadProducer[i].Begin(ProducerFunction, &workData);
  169. EATEST_VERIFY_MSG(threadIdProducer[i] != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
  170. threadIdConsumer[i] = threadConsumer[i].Begin(ConsumerFunction, &workData);
  171. EATEST_VERIFY_MSG(threadIdConsumer[i] != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
  172. }
  173. EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000);
  174. // Wait for producers to quit.
  175. workData.mbProducersShouldQuit = true;
  176. for(i = 0; i < kThreadCount; i++)
  177. {
  178. if(threadIdProducer[i] != kThreadIdInvalid)
  179. {
  180. status = threadProducer[i].WaitForEnd(GetThreadTime() + 30000);
  181. EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for producer end failed.");
  182. }
  183. }
  184. EA::UnitTest::ThreadSleepRandom(2000, 2000);
  185. // Wait for consumers to quit.
  186. workData.mbConsumersShouldQuit = true;
  187. workData.mCondition.Signal(true);
  188. for(i = 0; i < kThreadCount; i++)
  189. {
  190. if(threadIdConsumer[i] != kThreadIdInvalid)
  191. {
  192. status = threadConsumer[i].WaitForEnd(GetThreadTime() + 30000);
  193. EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for consumer end failed.");
  194. }
  195. }
  196. EATEST_VERIFY_MSG(workData.mnTotalJobsCreated == workData.mnTotalJobsCompleted, "Condition failure: Not all consumer work was processed.");
  197. }
  198. // test single producer/ single consumer wait condition with inter-process condition
  199. #if /*EATHREAD_INTERPROCESS_CONDITION_SUPPORTED*/ 0 // Disabled because this code fails on most platforms.
  200. {
  201. ConditionParameters sharedConditionParams( false, NULL );
  202. TMWorkData workData( &sharedConditionParams ); // Inter-process.
  203. Thread::Status status;
  204. Thread threadProducer;
  205. Thread threadConsumer;
  206. ThreadId threadIdProducer = threadProducer.Begin(ProducerFunction, &workData);
  207. EATEST_VERIFY_MSG(threadIdProducer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
  208. ThreadId threadIdConsumer = threadConsumer.Begin(ConsumerFunction, &workData);
  209. EATEST_VERIFY_MSG(threadIdConsumer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
  210. EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000);
  211. // Wait for producer to quit.
  212. workData.mbProducersShouldQuit = true;
  213. if(threadIdProducer != kThreadIdInvalid)
  214. {
  215. status = threadProducer.WaitForEnd(GetThreadTime() + 30000);
  216. EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for producer end failed.");
  217. }
  218. EA::UnitTest::ThreadSleepRandom(2000, 2000);
  219. // Wait for consumers to quit.
  220. workData.mbConsumersShouldQuit = true;
  221. workData.mCondition.Signal(true);
  222. if(threadIdConsumer != kThreadIdInvalid)
  223. {
  224. status = threadConsumer.WaitForEnd(GetThreadTime() + 30000);
  225. EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for consumer end failed.");
  226. }
  227. EATEST_VERIFY_MSG(workData.mnTotalJobsCreated == workData.mnTotalJobsCompleted, "Condition failure: Not all consumer work was processed.");
  228. }
  229. #endif
  230. // Test conditional variable timeout explicitly by not sending a signal.
  231. {
  232. //::EA::EAMain::SetVerbosity(5);
  233. ConditionParameters sharedConditionParams( true, NULL );
  234. TMWorkData workData( &sharedConditionParams ); // Inter-process.
  235. workData.mnConditionTimeout = 3000; // timeout value has to be less than thread timeout value below.
  236. Thread::Status status;
  237. Thread threadProducer;
  238. Thread threadConsumer;
  239. ThreadId threadIdProducer = threadProducer.Begin(ProducerFunction_DoesNotSignal, &workData);
  240. EATEST_VERIFY_MSG(threadIdProducer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
  241. ThreadId threadIdConsumer = threadConsumer.Begin(ConsumerFunction, &workData);
  242. EATEST_VERIFY_MSG(threadIdConsumer != kThreadIdInvalid, "Condition/Thread failure: Thread creation failed.");
  243. EA::UnitTest::ThreadSleepRandom(gTestLengthSeconds * 1000, gTestLengthSeconds * 1000);
  244. // Wait for producer to quit.
  245. if(threadIdProducer != kThreadIdInvalid)
  246. {
  247. status = threadProducer.WaitForEnd(GetThreadTime() + 30000);
  248. EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for producer end failed.");
  249. }
  250. EA::UnitTest::ThreadSleepRandom(2000, 2000);
  251. // Wait for consumers to quit.
  252. workData.mbConsumersShouldQuit = true;
  253. if(threadIdConsumer != kThreadIdInvalid)
  254. {
  255. status = threadConsumer.WaitForEnd(GetThreadTime() + 30000);
  256. EATEST_VERIFY_MSG(status != Thread::kStatusRunning, "Condition/Thread failure: Wait for consumer end failed.");
  257. }
  258. }
  259. }
  260. #endif
  261. return nErrorCount;
  262. }