Teuchos Package Browser (Single Doxygen Collection) Version of the Day
Loading...
Searching...
No Matches
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
47
48#include "Teuchos_RCP.hpp"
49#include "Teuchos_RCPNode.hpp"
52#include <vector>
53#include <thread>
54
55namespace {
56
57using Teuchos::null;
58using Teuchos::RCP;
59using 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
64static 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.
98TEUCHOS_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
117static 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.
146TEUCHOS_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.
173static 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.
198TEUCHOS_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.
218template<typename SOURCE_RCP_TYPE>
219static 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().
246TEUCHOS_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.
287void 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
311TEUCHOS_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.
348void 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
373TEUCHOS_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,
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
413static 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
437TEUCHOS_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
479template<typename T>
480class ExtraDataTest {
481public:
482 static RCP<ExtraDataTest<T> > create(T *ptr)
483 { return rcp(new ExtraDataTest(ptr)); }
484 ~ExtraDataTest() { delete [] ptr_; } // proper delete
485private:
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
509TEUCHOS_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.
576TEUCHOS_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.
623static std::atomic<int> s_count_successful_conversions(0);
624static std::atomic<int> s_count_failed_conversions(0);
625template<class SOURCE_RCP_TYPE>
626static 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.
662TEUCHOS_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 NUM_TESTS_TO_RUN
#define TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED
#define TEST_INEQUALITY_CONST(v1, v2)
Assert the inequality of v1 and constant v2.
#define TEST_EQUALITY_CONST(v1, v2)
Assert the equality of v1 and constant v2.
#define TEST_EQUALITY(v1, v2)
Assert the equality of v1 and v2.
Reference-counted pointer node classes.
Reference-counted pointer class and non-member templated function implementations.
#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 TEUCHOS_UNIT_TEST(TEST_GROUP, TEST_NAME)
Macro for defining a (non-templated) unit test.
static int numActiveRCPNodes()
Print the number of active RCPNode objects currently being tracked.
Smart reference counting pointer class for automatic garbage collection.
Concrete serial communicator subclass.
TEUCHOS_DEPRECATED RCP< T > rcp(T *p, Dealloc_T dealloc, bool owns_mem)
Deprecated.