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