Teuchos Package Browser (Single Doxygen Collection) Version of the Day
Loading...
Searching...
No Matches
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
47
48#include "Teuchos_ArrayView.hpp"
49#include "Teuchos_Array.hpp"
52#include <vector>
53#include <thread>
54
55namespace {
56
58using Teuchos::null;
59using Teuchos::RCP;
60using Teuchos::rcp;
61using Teuchos::Array;
65
66// utlity method used by unit test mtArrayViewMultipleReads below
67// check the iterators don't do anything bad
68static 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
106TEUCHOS_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
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.
149static 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
178static 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.
246TEUCHOS_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
#define NUM_TESTS_TO_RUN
#define TEUCHOS_THREAD_SAFE_UNIT_TESTS_THREADS_USED
Templated array class derived from the STL std::vector.
#define TEST_ASSERT(v1)
Assert the given statement is true.
#define TEST_EQUALITY_CONST(v1, v2)
Assert the equality of v1 and constant v2.
#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.
Reference-counted smart pointer for managing arrays.
Nonowning array view.
Replacement for std::vector that is compatible with the Teuchos Memory Management classes.
Dangling reference error exception class.
Smart reference counting pointer class for automatic garbage collection.
Range error exception class.
TEUCHOS_DEPRECATED RCP< T > rcp(T *p, Dealloc_T dealloc, bool owns_mem)
Deprecated.