Teuchos Package Browser (Single Doxygen Collection)  Version of the Day
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ArrayView_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_ArrayView.hpp"
15 #include "Teuchos_Array.hpp"
18 #include <vector>
19 #include <thread>
20 
21 namespace {
22 
23 using Teuchos::ArrayView;
24 using Teuchos::null;
25 using Teuchos::RCP;
26 using Teuchos::rcp;
27 using Teuchos::Array;
28 using Teuchos::ArrayRCP;
31 
32 // utlity method used by unit test mtArrayViewMultipleReads below
33 // check the iterators don't do anything bad
34 static void read_arrayview_in_thread(RCP<ArrayView<int>> shared_arrayview,
35  int expectedValue, std::atomic<int> & countErrors) {
36  // spin lock the threads
37  while (!ThreadTestManager::s_bAllowThreadsToRun) {}
38  for( int n = 0; n < 1000; ++n) {
39  for (ArrayView<int>::iterator iter = shared_arrayview->begin();
40  iter < shared_arrayview->end(); ++iter) {
41  int readAValueByIterator = *iter;
42  // make sure the value is correct and log anything wrong
43  if (readAValueByIterator != expectedValue) {
44  ++countErrors;
45  }
46  }
47  }
48 }
49 
50 // RCP Thread Safety Unit Test: mtArrayViewMultipleReads
51 //
52 // Purpose:
53 // Sanity Check: Validate that the class is working - this was not expected
54 // to have any trouble once the RCP class was made thread and no issues
55 // were found.
56 //
57 // Description:
58 // Creates an Array<int>, sets all the values to an arbitrary known value,
59 // then create an RCP<ArrayView<int>> of that array,
60 // then share it to several threads which all read the ArrayView
61 // at the same time and validate the read works. This tests both using
62 // the iterators to cycle through the array and the actually reading of the
63 // elements. This mirrors the Array test (which will fail without mutex
64 // protection) but ArrayView is ok because the ArrayView begin does not have
65 // any mutable behavior (it is true const).
66 //
67 // Solution to the Problem:
68 // Sanity Check
69 //
70 // Demonstration of Problem:
71 // Sanity Check
72 TEUCHOS_UNIT_TEST( ArrayView, mtArrayViewMultipleReads )
73 {
74  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
75  const int numTests = NUM_TESTS_TO_RUN;
76  const int setValue = 67359487; // arbitrary
77  const int arraySize = 10; // arbitrary
78  std::atomic<int> countErrors(0); // atomic counter to log errors
79  for (int testCycle = 0; testCycle < numTests; ++testCycle) {
80  try {
81  std::vector<std::thread> threads;
82  ThreadTestManager::s_bAllowThreadsToRun = false;
83  Array<int> array(arraySize, setValue); // some array
84  RCP<ArrayView<int>> arrayview_rcp = rcp(new ArrayView<int>(array));
85 
86  for (int i = 0; i < numThreads; ++i) {
87  threads.push_back( std::thread(read_arrayview_in_thread,
88  arrayview_rcp, setValue, std::ref(countErrors)) );
89  }
90 
91  ThreadTestManager::s_bAllowThreadsToRun = true; // let the threads run
92  for (unsigned int i = 0; i < threads.size(); ++i) {
93  threads[i].join();
94  }
95  }
96  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
97 
98  convenience_log_progress(testCycle, numTests); // this is just output
99  }
100 
101  // right now this test is just looking for trouble - so we don't actually
102  // have a verification - a failed test would be corrupted memory for example
103  TEST_EQUALITY_CONST(0, 0);
104 }
105 
106 // this test is only meaningful in DEBUG and would crash in RELEASE
107 // with undefined behaviors
108 #ifdef TEUCHOS_DEBUG
109 
110 // this utility method is defined to create and delete memory
111 // the purpose of this thread is just to put pressued on memory
112 // so we can study whether the classes are thread safe
113 // Note this test closely mirrors the Ptr unit test which detects scrambled
114 // memory events.
115 static void scramble_memory(int scrambleValue, int testArraySize,
116  int finishWhenThisThreadCountCompletes) {
117  while (!ThreadTestManager::s_bAllowThreadsToRun) {}
118  // the idea here is to try and fill any heap holes with new int allocations
119  while (true) {
120  // hard coded this as for thread debugging I didn't want to have extra array
121  // methods running while investigating the main operations
122  #define ARRAY_SCRAMBLE_SIZE 100
123  std::vector<int> * tempPtrArray[ARRAY_SCRAMBLE_SIZE];
124  for (int n = 0; n < ARRAY_SCRAMBLE_SIZE; ++n) {
125  // if the scramble thread does not allocate std::vector chunks identical
126  // to the main thread it won't have any chance to trigger the scrambled
127  // events.
128  tempPtrArray[n] = new std::vector<int>(testArraySize, scrambleValue);
129  }
130  for (int n = 0; n < ARRAY_SCRAMBLE_SIZE; ++n) {
131  delete tempPtrArray[n];
132  }
133  if (ThreadTestManager::s_countCompletedThreads >=
134  finishWhenThisThreadCountCompletes) {
135  break;
136  }
137  }
138 }
139 
140 // note this test mirrors the Ptr test - the mechanisms we are considering
141 // here are essentially equivalent. When a weak RCP is raced, it can indicate
142 // that it is not dangling, and then be read on junk memory because the delete
143 // happens right after the dangling check
144 static void share_arrayview_to_threads(ArrayView<int> shared_arrayview,
145  int theTestValue, Cycle_Index_Tracker & index_tracker) {
146  while (!ThreadTestManager::s_bAllowThreadsToRun) {}
147  int cycle = 0;
148  try {
149  while (true) {
150  bool bCheckStatus = ThreadTestManager::s_bMainThreadSetToNull;
151  // this may return junk data if the new heap allocation jumped in
152  // any of the member values could be junk
153  int tryToReadAValue = shared_arrayview[0];
154  index_tracker.trackCycle = cycle;
155  if (tryToReadAValue != theTestValue) {
156  // if we get here we had an ok from the dangling reference check, but
157  // then memory was deleted and reallocated to a new value by the
158  // scramble thread - a rare but possible condition
159  index_tracker.scambledMemory = cycle;
160  }
161 
162  if (bCheckStatus) {
163  index_tracker.unknownError = cycle;
164  // when bCheckStatus true it means we started the loop after the main
165  // rcp was set null - we should have thrown a DanglingReference by now
166  break;
167  }
168  ++cycle;
169  }
170  }
171  catch (DanglingReferenceError&) {
172  // loop ends - we got the dangling reference
173  index_tracker.danglingReference = cycle;
174  }
175  catch (...) {
176  std::cout << std::endl << "Unknown and unhandled exception!" << std::endl;
177  }
178 
179  ++ThreadTestManager::s_countCompletedThreads;
180 }
181 
182 // RCP Thread Safety Unit Test: mtArrayViewDangling
183 //
184 // Purpose:
185 // To understand this test is may be worth first looking at
186 // the Ptr unit test mtPtrDangling which studies a similar mechanism.
187 // This test is partly a sanity check to make sure dangling references are
188 // properly handled.
189 //
190 // Description:
191 // In this test an ArrayView is created for an ArrayRCP, then passed
192 // to several threads. The main thread kills the ArrayRCP and the subthreads
193 // all get left with dangling weak references. The test collects data on
194 // whether the subthreads properly process this.
195 // There is one outstanding issue here where a weak RCP can think it is valid
196 // but then read invalid memory. So this means Debug can detect problems
197 // most of the time but may sometimes do an undefined behavior. Currently
198 // there is no fix in place. The mtPtrDangling successfully demonstrates
199 // these bad memory reads and detects them. This should happen here as well
200 // but for some reason they never seem to occur - maybe due to the larger
201 // size of the memory structures making it hard to have some other thread
202 // jump in an replace the values. So this test shows that dangling references
203 // are detected but does not show the full picture the way mtPtrDangling does.
204 //
205 // Solution to the Problem:
206 // The dangling references work as expected due to the other RCP changes.
207 //
208 // Demonstration of Problem:
209 // To see the weak RCP accessing bad memory without knowing it, currently
210 // use the mtPtrDangling test as this one doesn't seem to trigger it and it's
211 // not clear why.
212 TEUCHOS_UNIT_TEST( ArrayView, mtArrayViewDangling )
213 {
214  const int numThreads = TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED;
215  const int numTests = NUM_TESTS_TO_RUN;
216  const int theTestValue = 66387; // some value
217  const int scrambleValue = 572778; // some other value
218  const int testArraySize = 3;
219 
220  // we want to count when it's not trivial (first cycle or last cycle)
221  int countDanglingReferences = 0;
222  int scrambledMemoryEvents = 0;
223  int unknownErrors = 0;
224  // 0 is the scrambling thread doing constant new/delete.
225  // The rest are the reader threads looking for troubles
226  int finishWhenThisThreadCountCompletes = numThreads - 1;
227  for (int testCycle = 0; testCycle < numTests; ++testCycle) {
228  try {
229  ThreadTestManager::s_countCompletedThreads = 0;
230 
231  // first make an arrayrcp which we will kill later
232  ArrayRCP<int> arrayrcp = arcp(rcp(
233  new std::vector<int>(testArraySize, theTestValue)));
234  // now make an ArrayView which has a reference to the arrayrcp
235  ArrayView<int> shared_arrayview = arrayrcp();
236  // threads will start spin locked
237  ThreadTestManager::s_bAllowThreadsToRun = false;
238  // this will be used to tell the threads when we have killed the RCP
239  ThreadTestManager::s_bMainThreadSetToNull = false;
240  // used to track errors in the sub threads
241  Cycle_Index_Tracker index_tracker[numThreads];
242  // create the threads
243  std::vector<std::thread> threads;
244  for (int i = 0; i < numThreads; ++i) {
245  switch(i) {
246  case 0:
247  {
248  // the first thread is special and just puts pressure on memory
249  // but allocating and deleting - tried some different combinations
250  // here but could never get this thread to jump in to the released
251  // memory spot of the ArrayRCP as in mtPtrDangling.
252  // The larger data structure probably is making this harder to
253  // demonstrate.
254  threads.push_back(std::thread(scramble_memory, scrambleValue,
255  testArraySize, finishWhenThisThreadCountCompletes));
256  }
257  break;
258  default:
259  {
260  // These threads all just read the ArrayView and will process
261  // dangling reference exceptions when the ArrayRCP is killed by this
262  // main thread.
263  threads.push_back(std::thread(share_arrayview_to_threads,
264  shared_arrayview, theTestValue, std::ref(index_tracker[i])));
265  }
266  break;
267  }
268  }
269  // let the threads start running
270  ThreadTestManager::s_bAllowThreadsToRun = true;
271  // spin lock until we have confirmed the sub threads did something
272  while (index_tracker[1].trackCycle < 1) {}
273  // the ArrayRCP becomes invalid and the ArrayView types all lose their
274  // valid object - now we start getting dangling references
275  arrayrcp = null;
276  ThreadTestManager::s_bMainThreadSetToNull = true; // tell the threads
277  // join the threads
278  for (unsigned int i = 0; i < threads.size(); ++i) {
279  threads[i].join();
280  }
281  // collect all the errors
282  for (unsigned int i = 0; i < threads.size(); ++i) {
283  if (index_tracker[i].danglingReference != -1) {
284  ++countDanglingReferences; // this should always happen
285  }
286  if (index_tracker[i].scambledMemory != -1 ) {
287  // this was expected but does not occur
288  // in the mtPtrDangling these events are detected
289  // presently it's not clear why this test could not also demonstrate
290  // this shortcoming of weak RCP in the present setup.
291  ++scrambledMemoryEvents;
292  }
293  if (index_tracker[i].unknownError != -1 ) {
294  ++unknownErrors; // this is not expected and never occurs
295  }
296  }
297  }
298  TEUCHOS_STANDARD_CATCH_STATEMENTS(true, std::cerr, success);
299  convenience_log_progress(testCycle, numTests); // this is just output
300  }
301 
302  // verify we got all the dangling references
303  // except for thread 0 (scramble thread) we should be getting 1 exception
304  // for every run in each thread.
305  int requiredDanglingReferenceCount = (numThreads-1) * numTests;
306  bool bDanglingReferenceDetectionCountIsOK = (countDanglingReferences ==
307  requiredDanglingReferenceCount);
308 
309  // if the dangling exception count was off log some information
310  if( !bDanglingReferenceDetectionCountIsOK ) {
311  std::cout << std::endl << "Detected " << countDanglingReferences <<
312  " Dangling References but should have found " <<
313  requiredDanglingReferenceCount << "." << std::endl;
314  }
315  else {
316  // if everything is ok log the info along with scrambled memory events
317  // scrambles is always 0 here but I would not be surprised if it could
318  // be non zero. Currently it is not a factor for whether the test will fail.
319  std::cout << "Danglers: " << countDanglingReferences << " Scrambles: " <<
320  scrambledMemoryEvents << " ";
321  }
322 
323  // this is not expected to occur and was not ever observed
324  if (unknownErrors != 0) {
325  std::cout << std::endl << "Detected " << unknownErrors <<
326  " dangling references were missed which should have been detected."
327  << std::endl;
328  }
329  // pass or fail the test
330  TEST_ASSERT( bDanglingReferenceDetectionCountIsOK )
331  // if this ever hits it would be unexpected
332  TEST_EQUALITY_CONST(unknownErrors, 0);
333 }
334 
335 #endif // TEUCHOS_DEBUG
336 
337 } // end namespace
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.
pointer iterator
Type of a nonconst iterator.
#define TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED
ArrayRCP< T > arcp(const RCP< Array< T > > &v)
Wrap an RCP&lt;Array&lt;T&gt; &gt; object as an ArrayRCP&lt;T&gt; object.
#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.
Templated array class derived from the STL std::vector.
Nonowning array view.
Smart reference counting pointer class for automatic garbage collection.
Range error exception class.
Reference-counted smart pointer for managing arrays.
Replacement for std::vector that is compatible with the Teuchos Memory Management classes...