Teuchos Package Browser (Single Doxygen Collection)  Version of the Day
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Ptr_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 // this test is only meaningful in DEBUG and would crash in RELEASE
13 // with undefined behavior. This is because the test involves debug checks
14 // to detect weak ptrs and in release these errors are ignored. So here we
15 // are checking whether debug code can safely detectly badly written code.
16 #include "Teuchos_ConfigDefs.hpp" // get TEUCHOS_DEBUG
17 
18 #ifdef TEUCHOS_DEBUG
19 
20 #include "General_MT_UnitTests.hpp"
21 #include "Teuchos_Ptr.hpp"
24 #include <vector>
25 #include <thread>
26 #include <atomic>
27 
28 namespace {
29 
30 using Teuchos::Ptr;
31 using Teuchos::RCP;
33 using Teuchos::null;
34 using Teuchos::rcp;
35 using Teuchos::ptrFromRef;
36 using Teuchos::rcpFromPtr;
37 
38 // method used by unit test mtPtrDangling below.
39 // the thread reads the shared Ptr<int> which has been release by the
40 // main thread. The weak RCP is intended to detect when this is read after
41 // being released, which is a programmer error.
42 // The thread also puts pressue on memory by allocating/deleting ints.
43 static void share_ptr_to_threads(Ptr<int> shared_ptr, int theTestValue,
44  Cycle_Index_Tracker & index_tracker) {
45  // spin lock the threads until release by the main thread
46  while (!ThreadTestManager::s_bAllowThreadsToRun) {}
47  int cycle = 0;
48  try {
49  // If there is lots of competition for threads setting this to some
50  // safety limit of counts may fail because another thread was held up.
51  // So looping while(true) may be the cleanest and then we just
52  // time out if something goes wrong.
53  while(true) {
54  // check if the main thread has released the RCP which we point to.
55  bool bCheckStatus = ThreadTestManager::s_bMainThreadSetToNull;
56  // keep track of which cycle we are on
57  index_tracker.trackCycle = cycle;
58 
59  // Now read the ptr - there are 4 possible outcomes:
60  // (1) the ptr debug check returns dangling and a proper throw is
61  // detected - in this case we are certain of our result
62  // (2) the ptr debug check returns valid and we can read the data
63  // (because we are lucky and the data remains valid while we use it)
64  // (3) the ptr debug check returns valid, gets deleted by another
65  // thread immediately after, but we read the deleted data without
66  // knowing because it still contains the proper memory
67  // (4) the ptr debug check returns valid, gets deleted by another
68  // thread immediately after, is overwriteen by another heap
69  // allocation, and we read the scrambled data without knowing
70  if (*shared_ptr != theTestValue) {
71  index_tracker.scambledMemory = cycle; // record the cycle of the error
72  }
73 
74  // the scrambler int is trying to jump into the released memory spot
75  // through a heap allocation and disrupt the ptr value
76  int * pScramblerInt = new int;
77  *pScramblerInt = 0; // we hope to set the dangling memory space here
78  delete pScramblerInt;
79 
80  // if the main thread had released the memory before we read the ptr
81  // then we should have thrown by now. So something else has gone wrong
82  // and we record an unknown error (this currently does not every happen).
83  if (bCheckStatus) {
84  index_tracker.unknownError = cycle;
85  break;
86  }
87  ++cycle;
88  }
89  }
90  catch(DanglingReferenceError&) {
91  // we got the dangling error as expected
92  index_tracker.danglingReference = cycle;
93  }
94 }
95 
96 // RCP Thread Safety Unit Test: mtPtrDangling
97 //
98 // Purpose:
99 // Validate the RCP Ptr mechanism are all thread safe.
100 // Currently Ptr can detect a dangling reference if the original RCP was
101 // released in another thread. However because it is based on a weak RCP
102 // mechanism it can be fooled and think the Ptr was valid but then before
103 // actually reading the memory, lose that value.
104 // So this test will show the danlging references are processed properly
105 // which happens almost every time. However occasionally the scrambled memory
106 // event will occur (currently no fix) and this test is designed to detect
107 // that it took place. At some point if we decide to fix this we can use this
108 // test to validate it's all working.
109 //
110 // Description:
111 // An RCP<int> is created and then a Ptr<int> is creaeted from the RCP which
112 // maintains a weak reference to the original RCP<int>. The subthreads read
113 // the Ptr<int> while the main thread releases the memory. The subthreads then
114 // detect they have a dangling reference and throw an exception.
115 //
116 // Solution to the Problem:
117 // There is currently no implemented solution for the debug situation where
118 // an RCP class attempts to dereference a weak ptr
119 //
120 // Demonstration of Problem:
121 // Running this test will provide output showing dangling reference being
122 // properly detected and a few srambled memory events occuring which currently
123 // does not have a fix in place.
124 TEUCHOS_UNIT_TEST( Ptr, mtPtrDangling )
125 {
126  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
127  const int numTests = NUM_TESTS_TO_RUN;
128  const int theTestValue = 1454083084; // see Ptr to arbitrary value
129  // we want to count when it's not trivial (first cycle or last cycle)
130  int countDanglingReferences = 0; // detect attempt to access deleted RCP
131  int scrambledMemoryEvents = 0; // detect scambled memory
132  int unknownErrors = 0; // detect unknown errors - currently shouldn't happen
133  for (int testCycle = 0; testCycle < numTests; ++testCycle) {
134  try {
135  // create a new int - RCP will own this int and manage its memory
136  int * pInt = new int;
137  // set the int to a test value - we will check for this value in threads
138  *pInt = theTestValue;
139  // first make an RCP
140  RCP<int> shared_rcp = rcp(pInt);
141  // now make a Ptr which remembers a weak reference to that RCP
142  Ptr<int> shared_ptr = shared_rcp.ptr();
143  // threads will start spin locked
144  ThreadTestManager::s_bAllowThreadsToRun = false;
145  // we have not yet deleted the RCP in this thread
146  ThreadTestManager::s_bMainThreadSetToNull = false;
147  // manager to keep track of events
148  Cycle_Index_Tracker index_tracker[numThreads];
149  // Now create the threads
150  std::vector<std::thread> threads;
151  for (int i = 0; i < numThreads; ++i) {
152  threads.push_back(std::thread(share_ptr_to_threads, shared_ptr,
153  theTestValue, std::ref(index_tracker[i])));
154  }
155  // let the threads run
156  ThreadTestManager::s_bAllowThreadsToRun = true;
157  // spin lock the main thread until the sub threads get started
158  while( index_tracker[0].trackCycle < 1 ) {}
159  // Now set the RCP null
160  // the RCP becomes invalid and the Ptr types all lose their valid object
161  shared_rcp = null;
162  // tell the threads the RCP is now dead
163  // This lets the threads know they 'must' detect errors on next loop
164  ThreadTestManager::s_bMainThreadSetToNull = true;
165  // Join all threads to completion
166  for (unsigned int i = 0; i < threads.size(); ++i) {
167  threads[i].join();
168  }
169  // count up all the errors
170  for (unsigned int i = 0; i < threads.size(); ++i) {
171  if (index_tracker[i].danglingReference != -1) {
172  ++countDanglingReferences; // common event
173  }
174  if (index_tracker[i].scambledMemory != -1 ) {
175  ++scrambledMemoryEvents; // happens but rarely
176  }
177  if (index_tracker[i].unknownError != -1 ) {
178  ++unknownErrors; // not presently ever an issue
179  }
180  }
181  }
182  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
183  convenience_log_progress(testCycle, numTests);// this is just output
184  }
185 
186  // verify we caught a dangler everytime
187  int expectedDanglingReferences = numThreads * numTests;
188  if( countDanglingReferences != expectedDanglingReferences) {
189  std::cout << std::endl << "Test FAILED because only " <<
190  countDanglingReferences <<
191  " dangling references were detected but expected "
192  << expectedDanglingReferences << "." << std::endl;
193  }
194  else {
195  // we got the expected number of danglers so this log is the output
196  // when the test succeeeds. We also log the number of scambled events.
197  // At some point we may implement a fix so that this missed event does not
198  // occur but that is not currently the case. The weak RCP can be tricked,
199  // think it's valid, and read the memory which subsequently was deleted.
200  // If we do implement a fix in the future, we can check here as scrambled
201  // events should then go to 0. We would then always detect invalid memory
202  // in debug mode.
203  std::cout << "Danglers: " << countDanglingReferences << " Scrambles: "
204  << scrambledMemoryEvents << " ";
205  }
206 
207  // this is not currently an issue - it was a safety check in case something
208  // unexpected ever happened in the thread loop
209  if (unknownErrors != 0) {
210  std::cout << std::endl << "Detected " << unknownErrors <<
211  " dangling references were missed which should have been detected."
212  << std::endl;
213  }
214  // verify we detected the expectedDanglingReferences
215  TEST_ASSERT(countDanglingReferences == expectedDanglingReferences)
216  // not presently an issue - this is searching for the possibility of a
217  // dangling reference missed when it should have been recorded
218  TEST_EQUALITY_CONST(unknownErrors, 0);
219 }
220 
221 } // end namespace
222 
223 #endif // TEUCHOS_DEBUG
Dangling reference error exception class.
RCP< T > rcp(const boost::shared_ptr< T > &sptr)
Conversion function that takes in a boost::shared_ptr object and spits out a Teuchos::RCP object...
#define TEST_ASSERT(v1)
Assert the given statement is true.
#define TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED
Teuchos header file which uses auto-configuration information to include necessary C++ headers...
#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.
Smart reference counting pointer class for automatic garbage collection.
Simple wrapper class for raw pointers to single objects where no persisting relationship exists...