Teuchos Package Browser (Single Doxygen Collection)  Version of the Day
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
RCP_MT_UnitTests_Decl.hpp
Go to the documentation of this file.
1 // @HEADER
2 // *****************************************************************************
3 // Teuchos: Common Tools Package
4 //
5 // Copyright 2004 NTESS and the Teuchos contributors.
6 // SPDX-License-Identifier: BSD-3-Clause
7 // *****************************************************************************
8 // @HEADER
9 
10 // These unit tests are used for both a Nightly version and a Basic version
11 
12 #include "General_MT_UnitTests.hpp"
13 
14 #include "Teuchos_RCP.hpp"
15 #include "Teuchos_RCPNode.hpp"
18 #include <vector>
19 #include <thread>
20 
21 namespace {
22 
23 using Teuchos::null;
24 using Teuchos::RCP;
25 using Teuchos::rcp;
26 
27 // Thread Utility Method
28 // See unit test mtRefCount below for details.
29 // This method is called by several threads so each can copy the same RCP
30 static void make_large_number_of_copies(RCP<int> ptr) {
31  std::vector<RCP<int> > ptrs(10000, ptr);
32 }
33 
34 // RCP Thread Safety Unit Test: mtRefCount
35 //
36 // Purpose:
37 // Test that RCP counters are thread safe.
38 //
39 // Description:
40 // This was the first unit test created for developing
41 // RCP thread safe code. The purpose of the test was to demonstrate that the
42 // int RCP counters for weak and strong were not thread safe. This was not
43 // unexpected because they were not atomics.
44 // In this test, several threads are all passed an RCP<int> and simply
45 // create many copies of the same RCP. They all share the same root node
46 // and the total strong count should be consistent with the number of copies.
47 // As the threads complete, they release all of the RCP objects and the
48 // strong counters should deincrement accordingly. At the end of the test
49 // the total strong count should be 1 because there is only the original
50 // RCP<int> remaining.
51 // If the counters were not atomic, simultaneous ++i and --i action on the int
52 // counters will result in a a failed total count.
53 //
54 // Solution to the Problem:
55 // This issue was resolved by changing the counters
56 // to be std::atomic<int>. This is expected to have a performance impact.
57 //
58 // Demonstration of Problem:
59 // To reproduce the original problem, add the following define at the
60 // top of this file:
61 // #define DISABLE_ATOMIC_COUNTERS
62 // That wil change the counters back to int instead of atomic<int>
63 // and the test will then fail.
64 TEUCHOS_UNIT_TEST( RCP, mtRefCount )
65 {
66  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
67  RCP<int> ptr(new int); // RCP that all the threads will copy
68  std::vector<std::thread> threads;
69  for (int i = 0; i < numThreads; ++i) {
70  threads.push_back(std::thread(make_large_number_of_copies, ptr));
71  }
72  for (unsigned int i = 0; i < threads.size(); ++i) {
73  threads[i].join();
74  }
75  // we still have one strong RCP so the total_count should be 1
76  TEST_EQUALITY_CONST(ptr.total_count(), 1);
77 }
78 
79 // Thread Utility Method
80 // See unit test mtCreateIndependentRCP below for description.
81 // Each thread makes new RCP<int> objects and deletes them to stress test
82 // the RCPNodeTracer mechanism
83 static void create_independent_rcp_objects() {
84  // spin lock the threads so we can trigger them all together
85  while (!ThreadTestManager::s_bAllowThreadsToRun) {}
86  for(int n = 0; n < NUM_TESTS_TO_RUN; ++n ) {
87  // this allocates a new rcp ptr independent of all other rcp ptrs,
88  // and then dumps it, over and over
89  RCP<int> ptr( new int );
90  }
91 }
92 
93 // RCP Thread Safety Unit Test: mtCreateIndependentRCP
94 //
95 // Purpose:
96 // Test that the debug RCPNodeTracer is thread safe.
97 //
98 // Description:
99 // Multiple threads all create their own individual RCP<int> objects and
100 // release them many times. In Debug mode, the RCPNodeTracer would fail.
101 // Note that in release this test runs but does not test anything important.
102 //
103 // Solution to the Problem:
104 // Used a static mutex in Teuchos_RCPNode.cpp
105 //
106 // Demonstration of Problem:
107 // To reproduce the original problem, add the following define to the top of
108 // the file: Teuchos_RCPNode.cpp
109 // #define USE_MUTEX_TO_PROTECT_NODE_TRACING
110 // That wil remove the mutex which was added to protect RCPNodeTracker and
111 // restore the original errors. Note that will lead to undefined behaviors.
112 TEUCHOS_UNIT_TEST( RCP, mtCreateIndependentRCP )
113 {
114  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
115  // track node count when we start because other RCP obejcts already exist
116  int initialNodeCount = Teuchos::RCPNodeTracer::numActiveRCPNodes();
117  try {
118  std::vector<std::thread> threads;
119  // threads will start in a spin-lock state
120  ThreadTestManager::s_bAllowThreadsToRun = false;
121  for (int i = 0; i < numThreads; ++i) {
122  threads.push_back(std::thread(create_independent_rcp_objects));
123  }
124  // releasa all the threads
125  ThreadTestManager::s_bAllowThreadsToRun = true;
126  for (unsigned int i = 0; i < threads.size(); ++i) {
127  threads[i].join();
128  }
129  }
130  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
131  // we should be back to where we were when we started the test
132  // this is not going to be 0 because other objects existed before the test
133  TEST_EQUALITY_CONST(initialNodeCount,
135 }
136 
137 // Thread Utility Method
138 // See unit test mtTestGetExistingRCPNodeGivenLookupKey below for details.
139 static void create_independent_rcp_without_ownership() {
140  for(int n = 0; n < 10000; ++n ) {
141  int * intPtr = new int;
142  // this allocates a new rcp but without memory ownership
143  RCP<int> ptr( intPtr, false );
144  // since the RCP doesn't have memory ownership, we delete it manually
145  delete intPtr;
146  }
147 }
148 
149 // RCP Thread Safety Unit Test: mtTestGetExistingRCPNodeGivenLookupKey
150 //
151 // Purpose:
152 // Test that getExistingRCPNodeGivenLookupKey() is thread safe.
153 //
154 // Description:
155 // Multiple threads all create their own individual RCP<int> objects
156 // without ownership and then handle deleting the memory.
157 //
158 // Solution to the Problem:
159 // Added mutex protection to getExistingRCPNodeGivenLookupKey()
160 //
161 // Demonstration of Problem:
162 // Comment out the lock_guard in getExistingRCPNodeGivenLookupKey() in
163 // Teuchos_RCPNode.cpp which will lead to undefined behaviors during the test.
164 TEUCHOS_UNIT_TEST( RCP, mtTestGetExistingRCPNodeGivenLookupKey )
165 {
166  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
167  try {
168  std::vector<std::thread> threads;
169  for (int i = 0; i < numThreads; ++i) {
170  threads.push_back(std::thread(create_independent_rcp_without_ownership));
171  }
172  for (unsigned int i = 0; i < threads.size(); ++i) {
173  threads[i].join();
174  }
175  }
176  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
177 }
178 
179 // Thread Utility Method
180 // This is used by several of the unit tests below.
181 // It simply provides an RCP to a thread.
182 // When the thread is destroyed, it releases the passes RCP object.
183 // See the test descriptions for more details.
184 template<typename SOURCE_RCP_TYPE>
185 static void thread_gets_a_copy_of_rcp(SOURCE_RCP_TYPE ptr) {
186  // spin lock the threads so we can trigger them all at once
187  // note we don't actually do anything - the thread was passed a copy which is
188  // all we need for this test - it will be deleted when the thread ends.
189  while(!ThreadTestManager::s_bAllowThreadsToRun) {}
190 }
191 
192 // RCP Thread Safety Unit Test: mtRCPLastReleaseByAThread
193 //
194 // Purpose:
195 // Demonstrate that the reading of the deincr_count() in RCP is thread safe.
196 //
197 // Description:
198 // Demonstrate the delete path was not thread safe - specifically had to
199 // modify the format of deincr_count(). Calling --count and then checking
200 // count was not thread safe so we must check if(--count ==0) to have a
201 // thread safe atomic read of the deincrement event.
202 //
203 // Solution to the Problem:
204 // Changed the logic of the deincrement() counter path to be atomically safe.
205 //
206 // Demonstration of Problem:
207 // Add the following define to the top of this file:
208 // #define BREAK_THREAD_SAFETY_OF_DEINCR_COUNT
209 // That will create an error in the RCP code which mimics the way the code
210 // originally ran before the development of the thread safe system.
211 // Note that will likely lead to undefined behaviors in removeRCPNode().
212 TEUCHOS_UNIT_TEST( RCP, mtRCPLastReleaseByAThread )
213 {
214  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
215  const int numCycles = NUM_TESTS_TO_RUN;
216  try {
217  for(int cycleIndex = 0; cycleIndex < numCycles; ++cycleIndex) {
218  // initialize
219  CatchMemoryLeak::s_countAllocated = 0;
220  // only 1 new allocation happens in this test
221  RCP<CatchMemoryLeak> ptr(new CatchMemoryLeak);
222  // prepare to spin lock the threads
223  ThreadTestManager::s_bAllowThreadsToRun = false;
224  std::vector<std::thread> threads;
225  for (int threadIndex = 0; threadIndex < numThreads; ++threadIndex) {
226  threads.push_back(std::thread(
227  thread_gets_a_copy_of_rcp<RCP<CatchMemoryLeak>>, ptr));
228  }
229  // at this point threads are spin locked and holding copies
230  // release the ptr in the main thread
231  ptr = null;
232  // now we release all the threads
233  ThreadTestManager::s_bAllowThreadsToRun = true;
234  for (unsigned int i = 0; i < threads.size(); ++i) {
235  // when join completes rcp should be completely deleted
236  threads[i].join();
237  }
238  convenience_log_progress(cycleIndex, numCycles); // this is just output
239  if (CatchMemoryLeak::s_countAllocated != 0) {
240  break; // will catch in error below
241  }
242  }
243  }
244  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
245  // test for valid RCP deletion
246  TEST_EQUALITY_CONST(CatchMemoryLeak::s_countAllocated, 0);
247 }
248 
249 // This dealloc method is passed to RCP objects to be called when they delete.
250 // To track the behavior an atomic counter records all the events so that
251 // thread issues can be detected.
252 // This method is used by unit test mtRCPLastReleaseByAThreadWithDealloc below.
253 void deallocCatchMemoryLeak(CatchMemoryLeak* ptr)
254 {
255  // increment the static global counter used by the unit tests
256  ++CatchMemoryLeak::s_countDeallocs;
257  // then implement the delete of the data
258  delete ptr;
259 }
260 
261 // RCP Thread Safety Unit Test: mtRCPLastReleaseByAThreadWithDealloc
262 //
263 // Purpose:
264 // Sanity Check: Similar to mtRCPLastReleaseByAThread
265 //
266 // Description:
267 // This unit test is identical to mtRCPLastReleaseByAThread except that the
268 // RCP now has a custom dealloc policy. Once the delete path was made thread
269 // safe, this is also expected to be thread safe, so no errors were expected
270 // and no additional probelms were observed.
271 //
272 // Solution to the Problem:
273 // Sanity Check
274 //
275 // Demonstration of Problem:
276 // Sanity Check
277 TEUCHOS_UNIT_TEST( RCP, mtRCPLastReleaseByAThreadWithDealloc )
278 {
279  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
280  const int numCycles = NUM_TESTS_TO_RUN;
281  try {
282  for(int cycleIndex = 0; cycleIndex < numCycles; ++cycleIndex) {
283  CatchMemoryLeak::s_countDeallocs = 0; // set it to 0
284  RCP<CatchMemoryLeak> ptr = rcpWithDealloc(new CatchMemoryLeak,
285  Teuchos::deallocFunctorDelete<CatchMemoryLeak>(deallocCatchMemoryLeak));
286  // prepare to spin lock the threads
287  ThreadTestManager::s_bAllowThreadsToRun = false;
288  std::vector<std::thread> threads;
289  for (int threadIndex = 0; threadIndex < numThreads; ++threadIndex) {
290  threads.push_back(std::thread(
291  thread_gets_a_copy_of_rcp<RCP<CatchMemoryLeak>>, ptr));
292  }
293  // at this point threads are spin locked and holding copies
294  // Release the ptr in the main thread
295  ptr = null;
296  // now we release all the threads
297  ThreadTestManager::s_bAllowThreadsToRun = true;
298  for (unsigned int i = 0; i < threads.size(); ++i) {
299  // when join completes rcp should be completely deleted
300  threads[i].join();
301  }
302  convenience_log_progress(cycleIndex, numCycles);// this is just output
303  if (CatchMemoryLeak::s_countDeallocs != 1) {
304  break; // will catch in error below
305  }
306  }
307  }
308  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
309  // we should have ended with exactly one dealloc call
310  TEST_EQUALITY_CONST(CatchMemoryLeak::s_countDeallocs, 1);
311 }
312 
313 // This method is used by mtRCPLastReleaseByAThreadWithDeallocHandle below.
314 void deallocHandleCatchMemoryLeak(CatchMemoryLeak** handle)
315 {
316  ++CatchMemoryLeak::s_countDeallocs;
317  CatchMemoryLeak *ptr = *handle;
318  delete ptr;
319  *handle = 0;
320 }
321 
322 
323 // RCP Thread Safety Unit Test: mtRCPLastReleaseByAThreadWithDeallocHandle
324 //
325 // Purpose:
326 // Sanity Check: Similar to mtRCPLastReleaseByAThread
327 //
328 // Description:
329 // This unit test is identical to mtRCPLastReleaseByAThread except that the
330 // RCP now has a custom dealloc handle policy. Similar to the test
331 // mtRCPLastReleaseByAThreadWithDealloc above, this was not found to have
332 // any additional problems.
333 //
334 // Solution to the Problem:
335 // Sanity Check
336 //
337 // Demonstration of Problem:
338 // Sanity Check
339 TEUCHOS_UNIT_TEST( RCP, mtRCPLastReleaseByAThreadWithDeallocHandle )
340 {
341  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
342  const int numCycles = NUM_TESTS_TO_RUN;
343  try {
344  for(int cycleIndex = 0; cycleIndex < numCycles; ++cycleIndex) {
345  CatchMemoryLeak::s_countDeallocs = 0; // set it to 0
346  RCP<CatchMemoryLeak> ptr = rcpWithDealloc(new CatchMemoryLeak,
347  Teuchos::deallocFunctorHandleDelete<CatchMemoryLeak>(
348  deallocHandleCatchMemoryLeak));
349  // prepare the threads to be spin locked
350  ThreadTestManager::s_bAllowThreadsToRun = false;
351  std::vector<std::thread> threads;
352  for (unsigned int threadIndex = 0; threadIndex <
353  numThreads; ++threadIndex) {
354  threads.push_back(std::thread(
355  thread_gets_a_copy_of_rcp<RCP<CatchMemoryLeak>>, ptr));
356  }
357  // at this point threads are spin locked and holding copies
358  // Release the ptr in the main thread
359  ptr = null;
360  // now we release all the threads
361  ThreadTestManager::s_bAllowThreadsToRun = true;
362  for (unsigned int i = 0; i < threads.size(); ++i) {
363  // when join completes rcp should be completely deleted
364  threads[i].join();
365  }
366  convenience_log_progress(cycleIndex, numCycles); // this is just output
367  if (CatchMemoryLeak::s_countDeallocs != 1) {
368  break; // will catch in error below
369  }
370  }
371  }
372  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
373  TEST_EQUALITY_CONST(CatchMemoryLeak::s_countDeallocs, 1); // should be 1 only
374 }
375 
376 
377 // See mtRCPThreadCallsRelease below for details
378 // This utitily method allows one thread to call release on the RCP
379 static void call_release_on_rcp_if_flag_is_set(RCP<CatchMemoryLeak> ptr,
380  int numCopies, bool bCallsRelease) {
381  // spin lock the threads so we can trigger them all at once
382  while(!ThreadTestManager::s_bAllowThreadsToRun) {}
383  if(bCallsRelease) {
384  ptr.release(); // should make a memory leak!
385  }
386 }
387 
388 // RCP Thread Safety Unit Test: mtRCPThreadCallsRelease
389 //
390 // Purpose:
391 // Sanity Check: To validate the release mechanism. There was no explicit
392 // problem expected and none was observed.
393 //
394 // Description:
395 // In this test only one thread calls release which means the data should
396 // not be deleted. It is expected to be deleted manually. The test verifies
397 //
398 // Solution to the Problem:
399 // Sanity Check
400 //
401 // Demonstration of Problem:
402 // Sanity Check
403 TEUCHOS_UNIT_TEST( RCP, mtRCPThreadCallsRelease )
404 {
405  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
406  const int numCycles = NUM_TESTS_TO_RUN;
407  bool bFailure = false;
408  try {
409  for(int cycleIndex = 0; cycleIndex < numCycles; ++cycleIndex) {
410  CatchMemoryLeak::s_countAllocated = 0; // initialize
411  CatchMemoryLeak * pMemoryToLeak = new CatchMemoryLeak;
412  RCP<CatchMemoryLeak> ptr(pMemoryToLeak);
413  // prepare to spin lock the threads
414  ThreadTestManager::s_bAllowThreadsToRun = false;
415  std::vector<std::thread> threads;
416  for (int threadIndex = 0; threadIndex < numThreads; ++threadIndex) {
417  bool bCallRelease = (threadIndex==0); // only the first calls release
418  threads.push_back(std::thread(call_release_on_rcp_if_flag_is_set,
419  ptr, 1, bCallRelease));
420  }
421  ptr = null;
422  ThreadTestManager::s_bAllowThreadsToRun = true;
423  for (unsigned int i = 0; i < threads.size(); ++i) {
424  threads[i].join();
425  }
426  convenience_log_progress(cycleIndex, numCycles); // this is just output
427  if (CatchMemoryLeak::s_countAllocated != 1) {
428  break; // will catch in error below
429  }
430  else {
431  // we can clean up our memory leak now by hand
432  // if the test is passing we want a clean finish
433  delete pMemoryToLeak;
434  }
435  }
436  }
437  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
438  // organized like this because I want the above loops to properly clean up
439  // memory if the test is passing - which changes the counters and makes this
440  // interpretation complicated
441  TEST_EQUALITY_CONST(bFailure, false);
442 }
443 
444 // Used by unit test mtRCPExtraData below to test ExtraData feature of RCP
445 template<typename T>
446 class ExtraDataTest {
447 public:
448  static RCP<ExtraDataTest<T> > create(T *ptr)
449  { return rcp(new ExtraDataTest(ptr)); }
450  ~ExtraDataTest() { delete [] ptr_; } // proper delete
451 private:
452  T *ptr_;
453  ExtraDataTest(T *ptr) : ptr_(ptr) {}
454  // Not defined!
455  ExtraDataTest();
456  ExtraDataTest(const ExtraDataTest&);
457  ExtraDataTest& operator=(const ExtraDataTest&);
458 };
459 
460 // RCP Thread Safety Unit Test: mtRCPExtraData
461 //
462 // Purpose:
463 // Sanity Check: Use the extra data feature and make sure things are ok.
464 // There was no explicit problem expected and none was observed.
465 //
466 // Description:
467 // In this test the ExtraData RCP feature is used to properly corred the ptr
468 // to delete using delete [].
469 //
470 // Solution to the Problem:
471 // Sanity Check
472 //
473 // Demonstration of Problem:
474 // Sanity Check
475 TEUCHOS_UNIT_TEST( RCP, mtRCPExtraData )
476 {
477  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
478  const int numCycles = NUM_TESTS_TO_RUN;
479  try {
480  for(int cycleIndex = 0; cycleIndex < numCycles; ++cycleIndex) {
481  CatchMemoryLeak::s_countAllocated = 0; // initialize
482  // standard delete will be wrong - should call delete[]
483  RCP<CatchMemoryLeak> ptr(new CatchMemoryLeak[1]);
484  ptr.release(); // extra data will handle the case
485  Teuchos::set_extra_data( ExtraDataTest<CatchMemoryLeak>::create(
486  ptr.getRawPtr()), "dealloc", Teuchos::inOutArg(ptr));
487  // prepare to spin lock the threads
488  ThreadTestManager::s_bAllowThreadsToRun = false;
489  std::vector<std::thread> threads;
490  for (unsigned int threadIndex = 0; threadIndex < numThreads;
491  ++threadIndex) {
492  threads.push_back(std::thread(
493  thread_gets_a_copy_of_rcp<RCP<CatchMemoryLeak>>, ptr));
494  }
495  // At this point threads are spin locked and holding copies
496  // Release the ptr in the main thread
497  ptr = null;
498  // now we release all the threads
499  ThreadTestManager::s_bAllowThreadsToRun = true;
500  for (unsigned int i = 0; i < threads.size(); ++i) {
501  // when join completes rcp should be completely deleted
502  threads[i].join();
503  }
504  convenience_log_progress(cycleIndex, numCycles); // this is just output
505  if (CatchMemoryLeak::s_countAllocated != 0) {
506  break; // will catch in error below
507  }
508  }
509  }
510  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
511  // test for valid RCP deletion
512  TEST_EQUALITY_CONST(CatchMemoryLeak::s_countAllocated, 0);
513 }
514 
515 // RCP Thread Safety Unit Test: mtRCPWeakStrongDeleteRace
516 //
517 // Purpose:
518 // To demonstrate weak and strong RCP objects can delete simultaneously
519 // and be handled in a thread safe way.
520 //
521 // Description:
522 // 4 threads are creating alternating between having a strong and weak RCP.
523 // They all share the same root RCP object.
524 // The main thread will release the RCP and then release all the threads.
525 // When the threads die the RCP objects will release and strong/weak
526 // counters will process in a race condition enivronment.
527 //
528 // Solution to the Problem:
529 // counters were refactored to work like boost, which uses a nice trick where
530 // the strong count is as expected, but the weak count is equal to weak + 1.
531 // This allows all the race conditions to be resolved in an elegant way.
532 //
533 // Demonstration of Problem:
534 // I had a define for this but it seemed really messy to leave that in core
535 // So here is a way to simulate the original problem:
536 // In Teuchos_RCPNode.hpp change the unbind() method:
537 // Change that one line unbindOneStrong() to be:
538 // node_->deincr_count(RCP_WEAK);
539 // unbindOneStrong();
540 // node_->incr_count(RCP_WEAK);
541 // That will give a behavior similar to the original code.
542 TEUCHOS_UNIT_TEST( RCP, mtRCPWeakStrongDeleteRace )
543 {
544  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
545  const int numCycles = NUM_TESTS_TO_RUN;
546  try {
547  for(int cycleIndex = 0; cycleIndex < numCycles; ++cycleIndex) {
548  CatchMemoryLeak::s_countAllocated = 0; // initialize
549  // only 1 new allocation happens in this test
550  RCP<CatchMemoryLeak> ptr(new CatchMemoryLeak);
551  // prepare to spin lock the threads
552  ThreadTestManager::s_bAllowThreadsToRun = false;
553  std::vector<std::thread> threads;
554  bool bToggleStrong = true;
555  for (int threadIndex = 0; threadIndex < numThreads; ++threadIndex) {
556  if (bToggleStrong) {
557  threads.push_back(std::thread(
558  thread_gets_a_copy_of_rcp<RCP<CatchMemoryLeak>>,
559  ptr.create_strong()));
560  }
561  else {
562  threads.push_back(std::thread(
563  thread_gets_a_copy_of_rcp<RCP<CatchMemoryLeak>>,
564  ptr.create_weak()));
565  }
566  bToggleStrong = !bToggleStrong;
567  }
568  // at this point threads are spin locked and holding copies
569  // Release the ptr in the main thread
570  ptr = null;
571  // now we release all the threads
572  ThreadTestManager::s_bAllowThreadsToRun = true;
573  for (unsigned int i = 0; i < threads.size(); ++i) {
574  // when join completes rcp should be completely deleted
575  threads[i].join();
576  }
577  convenience_log_progress(cycleIndex, numCycles); // this is just output
578  if (CatchMemoryLeak::s_countAllocated != 0) {
579  break; // will catch in error below
580  }
581  }
582  }
583  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
584  // test for valid RCP deletion
585  TEST_EQUALITY_CONST(CatchMemoryLeak::s_countAllocated, 0);
586 }
587 
588 // This utlity method supports the unit test mtRCPWeakStrongDeleteRace below.
589 static std::atomic<int> s_count_successful_conversions(0);
590 static std::atomic<int> s_count_failed_conversions(0);
591 template<class SOURCE_RCP_TYPE>
592 static void attempt_make_a_strong_ptr(SOURCE_RCP_TYPE ptr) {
593  // spin lock the threads so we can trigger them all at once
594  while(!ThreadTestManager::s_bAllowThreadsToRun) {}
595  // ptr can be weak or strong - the weak ptrs may fail
596  RCP<CatchMemoryLeak> possibleStrongPtr = ptr.create_strong_thread_safe();
597  if (possibleStrongPtr.is_null()) {
598  ++s_count_failed_conversions;
599  }
600  else {
601  ++s_count_successful_conversions;
602  }
603 }
604 
605 // RCP Thread Safety Unit Test: mtRCPWeakStrongDeleteRace
606 //
607 // Purpose:
608 // To demonstrate thread safe conversion of weak to strong ptr.
609 // NOTE: This is not currently part of the primary goals - it is expected
610 // to have application in future work.
611 //
612 // Description:
613 // The threads alternate to have weak or strong ptrs to the same RCP.
614 // The main thread releases it's RCP.
615 // Now all the thread attempt to make a strong ptr and release their threads.
616 // This creates a mixed race condition where the attempt to create a strong
617 // ptr may fail if all other strong RCP's happen to be gone. This should
618 // handle in a thread safe way and the failure should give a clean null result.
619 //
620 // Solution to the Problem:
621 // attemptIncrementStrongCountFromNonZeroValue() was created to implement
622 // this in similar fashion to the boost methods.
623 //
624 // Demonstration of Problem:
625 // To see the test fail, we can modify the Teuchos_RCPNode.hpp method
626 // attemptIncrementStrongCountFromNonZeroValue() to behave as if USING_ATOMICS
627 // was not defined. Then the test will detect the failures.
628 TEUCHOS_UNIT_TEST( RCP, mtRCPMixedWeakAndStrongConvertToStrong )
629 {
630  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
631  int numCycles = NUM_TESTS_TO_RUN;
632  s_count_successful_conversions = 0;
633  s_count_failed_conversions = 0;
634  try {
635  for(int cycleIndex = 0; cycleIndex < numCycles; ++cycleIndex) {
636  CatchMemoryLeak::s_countAllocated = 0; // initialize
637  // only 1 new allocation happens in this test
638  RCP<CatchMemoryLeak> ptr(new CatchMemoryLeak);
639  // prepare to spin lock the threads
640  ThreadTestManager::s_bAllowThreadsToRun = false;
641  std::vector<std::thread> threads;
642  bool bCycleStrong = true;
643  for (int threadIndex = 0; threadIndex < numThreads; ++threadIndex) {
644  if (bCycleStrong) {
645  threads.push_back(std::thread(
646  attempt_make_a_strong_ptr<RCP<CatchMemoryLeak>>,
647  ptr.create_strong()));
648  }
649  else {
650  threads.push_back(std::thread(
651  attempt_make_a_strong_ptr<RCP<CatchMemoryLeak>>,
652  ptr.create_weak()));
653  }
654  bCycleStrong = !bCycleStrong;
655  }
656  // at this point threads are spin locked and holding copies
657  // Release the ptr in the main thread
658  ptr = null;
659  // now we release all the threads
660  ThreadTestManager::s_bAllowThreadsToRun = true;
661  for (unsigned int i = 0; i < threads.size(); ++i) {
662  // when join completes rcp should be completely deleted
663  threads[i].join();
664  }
665  if (CatchMemoryLeak::s_countAllocated != 0) {
666  break;
667  }
668  }
669  }
670  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
671  std::cout << "Weak converted with null " << s_count_failed_conversions <<
672  " times and success " << s_count_successful_conversions
673  << " times. We want to see a mix of each. ";
674 
675  // for nightly this should definitely hit both fails and success
676  // note that here 'failed' does not mean bad - it means the test properly
677  // determined that the weak to strong conversion was not possible.
678  // for basic I am disabling this check because we just run a few times and
679  // it's possible we won't get enough checks to be sure to hit each type
680  if(NUM_TESTS_TO_RUN >= 100) {
681  // this has to be a mixed result or the test is not doing anything useful
682  TEST_INEQUALITY_CONST(s_count_failed_conversions, 0);
683  // this has to be a mixed result or the test is not doing anything useful
684  TEST_INEQUALITY_CONST(s_count_successful_conversions, 0);
685  }
686 
687  TEST_EQUALITY(CatchMemoryLeak::s_countAllocated, 0); // should be 0
688 }
689 
690 } // namespace
#define TEST_INEQUALITY_CONST(v1, v2)
Assert the inequality of v1 and constant v2.
#define TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED
#define TEST_EQUALITY(v1, v2)
Assert the equality of v1 and v2.
#define TEUCHOS_UNIT_TEST(TEST_GROUP, TEST_NAME)
Macro for defining a (non-templated) unit test.
#define NUM_TESTS_TO_RUN
TEUCHOS_DEPRECATED RCP< T > rcp(T *p, Dealloc_T dealloc, bool owns_mem)
Deprecated.
#define TEUCHOS_STANDARD_CATCH_STATEMENTS(VERBOSE, ERR_STREAM, SUCCESS_FLAG)
Simple macro that catches and reports standard exceptions and other exceptions.
Unit testing support.
#define TEST_EQUALITY_CONST(v1, v2)
Assert the equality of v1 and constant v2.
Ptr< T > ptr(T *p)
Create a pointer to an object from a raw pointer.
Smart reference counting pointer class for automatic garbage collection.
RCP< T > rcpWithDealloc(T *p, Dealloc_T dealloc, bool owns_mem=true)
Initialize from a raw pointer with a deallocation policy.
Reference-counted pointer node classes.
static int numActiveRCPNodes()
Print the number of active RCPNode objects currently being tracked.
Reference-counted pointer class and non-member templated function implementations.