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