1 /*
2 Copyright (c) 2005-2022 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 #ifndef __TBB_test_common_exception_handling_H
18 #define __TBB_test_common_exception_handling_H
19
20 #include "config.h"
21
22 #include <typeinfo>
23 #include <thread>
24
25 #include "oneapi/tbb/task_scheduler_observer.h"
26
27 #include "concurrency_tracker.h"
28
29 int g_NumThreads = 0;
30 std::thread::id g_Master = std::this_thread::get_id();
31 const char * g_Orig_Wakeup_Msg = "Missed wakeup or machine is overloaded?";
32 const char * g_Wakeup_Msg = g_Orig_Wakeup_Msg;
33
34 std::atomic<intptr_t> g_CurExecuted,
35 g_ExecutedAtLastCatch,
36 g_ExecutedAtFirstCatch,
37 g_ExceptionsThrown,
38 g_MasterExecutedThrow, // number of times external thread entered exception code
39 g_NonMasterExecutedThrow, // number of times non-external thread entered exception code
40 g_PipelinesStarted;
41 std::atomic<bool> g_ExceptionCaught{ false },
42 g_UnknownException{ false };
43
44 std::atomic<intptr_t> g_ActualMaxThreads;
45 std::atomic<intptr_t> g_ActualCurrentThreads;
46
47 std::atomic<bool> g_ThrowException{ true },
48 // g_Flog is true for nested construct tests with catches (exceptions are not allowed to
49 // propagate to the construct itself.)
50 g_Flog{ false },
51 g_MasterExecuted{ false },
52 g_NonMasterExecuted{ false };
53
54 bool g_ExceptionInMaster = false;
55 bool g_SolitaryException = false;
56 bool g_NestedPipelines = false;
57
58 //! Number of exceptions propagated into the user code (i.e. intercepted by the tests)
59 std::atomic<intptr_t> g_NumExceptionsCaught;
60
61 //-----------------------------------------------------------
62
63 class eh_test_observer : public tbb::task_scheduler_observer {
64 public:
on_scheduler_entry(bool is_worker)65 void on_scheduler_entry(bool is_worker) override {
66 if(is_worker) { // we've already counted the external thread
67 intptr_t p = ++g_ActualCurrentThreads;
68 intptr_t q = g_ActualMaxThreads;
69 while(q < p) {
70 g_ActualMaxThreads.compare_exchange_strong(q, p);
71 }
72 }
73 }
on_scheduler_exit(bool is_worker)74 void on_scheduler_exit(bool is_worker) override {
75 if(is_worker) {
76 --g_ActualCurrentThreads;
77 }
78 }
79 };
80 //-----------------------------------------------------------
81
82 inline void ResetEhGlobals ( bool throwException = true, bool flog = false ) {
83 utils::ConcurrencyTracker::Reset();
84 g_CurExecuted = g_ExecutedAtLastCatch = g_ExecutedAtFirstCatch = 0;
85 g_ExceptionCaught = false;
86 g_UnknownException = false;
87 g_NestedPipelines = false;
88 g_ThrowException = throwException;
89 g_MasterExecutedThrow = 0;
90 g_NonMasterExecutedThrow = 0;
91 g_Flog = flog;
92 g_MasterExecuted = false;
93 g_NonMasterExecuted = false;
94 g_ActualMaxThreads = 1; // count external thread
95 g_ActualCurrentThreads = 1; // count external thread
96 g_ExceptionsThrown = g_NumExceptionsCaught = g_PipelinesStarted = 0;
97 }
98
99 #if TBB_USE_EXCEPTIONS
100 class test_exception : public std::exception {
101 const char* my_description;
102 public:
test_exception(const char * description)103 test_exception ( const char* description ) : my_description(description) {}
104
what()105 const char* what() const throw() override { return my_description; }
106 };
107
108 class solitary_test_exception : public test_exception {
109 public:
solitary_test_exception(const char * description)110 solitary_test_exception ( const char* description ) : test_exception(description) {}
111 };
112
113 using PropagatedException = test_exception;
114 #define EXCEPTION_NAME(e) typeid(e).name()
115
116 #define EXCEPTION_DESCR "Test exception"
117
118 #if UTILS_EXCEPTION_HANDLING_SIMPLE_MODE
119
ThrowTestException()120 inline void ThrowTestException () {
121 ++g_ExceptionsThrown;
122 throw test_exception(EXCEPTION_DESCR);
123 }
124
125 #else /* !UTILS_EXCEPTION_HANDLING_SIMPLE_MODE */
126
127 constexpr std::intptr_t Existed = INT_MAX;
128
ThrowTestException(intptr_t threshold)129 inline void ThrowTestException ( intptr_t threshold ) {
130 bool inMaster = (std::this_thread::get_id() == g_Master);
131 if ( !g_ThrowException || // if we're not supposed to throw
132 (!g_Flog && // if we're not catching throw in bodies and
133 (g_ExceptionInMaster ^ inMaster)) ) { // we're the external thread and not expected to throw
134 // or are the external thread not the one to throw (??)
135 return;
136 }
137 while ( Existed < threshold )
138 utils::yield();
139 if ( !g_SolitaryException ) {
140 ++g_ExceptionsThrown;
141 if(inMaster) ++g_MasterExecutedThrow; else ++g_NonMasterExecutedThrow;
142 throw test_exception(EXCEPTION_DESCR);
143 }
144 // g_SolitaryException == true
145 if(g_NestedPipelines) {
146 // only throw exception if we have started at least two inner pipelines
147 // else return
148 if(g_PipelinesStarted >= 3) {
149 intptr_t expected = 0;
150 if ( g_ExceptionsThrown.compare_exchange_strong(expected, 1) ) {
151 if(inMaster) ++g_MasterExecutedThrow; else ++g_NonMasterExecutedThrow;
152 throw solitary_test_exception(EXCEPTION_DESCR);
153 }
154 }
155 }
156 else {
157 intptr_t expected = 0;
158 if ( g_ExceptionsThrown.compare_exchange_strong(expected, 1) ) {
159 if(inMaster) ++g_MasterExecutedThrow; else ++g_NonMasterExecutedThrow;
160 throw solitary_test_exception(EXCEPTION_DESCR);
161 }
162 }
163 }
164 #endif /* !HARNESS_EH_SIMPLE_MODE */
165
166 #define UPDATE_COUNTS() \
167 { \
168 ++g_CurExecuted; \
169 if(g_Master == std::this_thread::get_id()) g_MasterExecuted = true; \
170 else g_NonMasterExecuted = true; \
171 if( tbb::is_current_task_group_canceling() ) ++g_TGCCancelled; \
172 }
173
174 #define CATCH() \
175 } catch ( PropagatedException& e ) { \
176 intptr_t expected = 0;\
177 g_ExecutedAtFirstCatch.compare_exchange_strong(expected , g_CurExecuted); \
178 intptr_t curExecuted = g_CurExecuted.load(); \
179 expected = g_ExecutedAtLastCatch.load();\
180 while (expected < curExecuted) g_ExecutedAtLastCatch.compare_exchange_strong(expected, curExecuted); \
181 REQUIRE_MESSAGE(e.what(), "Empty what() string" ); \
182 REQUIRE_MESSAGE((strcmp(EXCEPTION_NAME(e), (g_SolitaryException ? typeid(solitary_test_exception) : typeid(test_exception)).name() ) == 0), "Unexpected original exception name"); \
183 REQUIRE_MESSAGE((strcmp(e.what(), EXCEPTION_DESCR) == 0), "Unexpected original exception info"); \
184 g_ExceptionCaught = l_ExceptionCaughtAtCurrentLevel = true; \
185 ++g_NumExceptionsCaught; \
186 } catch ( std::exception& ) { \
187 REQUIRE_MESSAGE (false, "Unexpected std::exception" ); \
188 } catch ( ... ) { \
189 g_ExceptionCaught = l_ExceptionCaughtAtCurrentLevel = true; \
190 g_UnknownException = unknownException = true; \
191 } \
192 if ( !g_SolitaryException ) \
193 WARN_MESSAGE (true, "Multiple exceptions mode");
194
195 #define ASSERT_EXCEPTION() \
196 { \
197 REQUIRE_MESSAGE ((!g_ExceptionsThrown || g_ExceptionCaught), "throw without catch"); \
198 REQUIRE_MESSAGE ((!g_ExceptionCaught || g_ExceptionsThrown), "catch without throw"); \
199 REQUIRE_MESSAGE ((g_ExceptionCaught || (g_ExceptionInMaster && !g_MasterExecutedThrow) || (!g_ExceptionInMaster && !g_NonMasterExecutedThrow)), "no exception occurred"); \
200 REQUIRE_MESSAGE (!g_UnknownException, "unknown exception was caught"); \
201 }
202
203 #define CATCH_AND_ASSERT() \
204 CATCH() \
205 ASSERT_EXCEPTION()
206
207 #else /* !TBB_USE_EXCEPTIONS */
208
ThrowTestException(intptr_t)209 inline void ThrowTestException ( intptr_t ) {}
210
211 #endif /* !TBB_USE_EXCEPTIONS */
212
213 #if TBB_USE_EXCEPTIONS
214 #define TRY() \
215 bool l_ExceptionCaughtAtCurrentLevel = false, unknownException = false; \
216 try {
217
218 // "l_ExceptionCaughtAtCurrentLevel || unknownException" is used only to "touch" otherwise unused local variables
219 #define CATCH_AND_FAIL() } catch(...) { \
220 REQUIRE_MESSAGE (false, "Cancelling tasks must not cause any exceptions"); \
221 (void)(l_ExceptionCaughtAtCurrentLevel && unknownException); \
222 }
223 #else
224 #define TRY() {
225 #define CATCH_AND_FAIL() }
226 #endif
227
228 const int c_Timeout = 1000000;
229
230 #include "oneapi/tbb/task_arena.h"
231
WaitUntilConcurrencyPeaks(int expected_peak)232 void WaitUntilConcurrencyPeaks ( int expected_peak ) {
233 tbb::task_group tg;
234 if ( g_Flog )
235 return;
236 int n = 0;
237 retry:
238 while ( ++n < c_Timeout && (int)utils::ConcurrencyTracker::PeakParallelism() < expected_peak )
239 utils::yield();
240 #if USE_TASK_SCHEDULER_OBSERVER
241 DOCTEST_WARN_MESSAGE( g_NumThreads == g_ActualMaxThreads, "Library did not provide sufficient threads");
242 #endif
243 DOCTEST_WARN_MESSAGE(n < c_Timeout, g_Wakeup_Msg);
244 // Workaround in case a missed wakeup takes place
245 if ( n == c_Timeout ) {
246 tg.run([]{});
247 n = 0;
248 goto retry;
249 }
250
251 TRY();
252 tg.wait();
253 CATCH_AND_FAIL();
254 }
255
WaitUntilConcurrencyPeaks()256 inline void WaitUntilConcurrencyPeaks () { WaitUntilConcurrencyPeaks(g_NumThreads); }
257
IsMaster()258 inline bool IsMaster() {
259 return std::this_thread::get_id() == g_Master;
260 }
261
IsThrowingThread()262 inline bool IsThrowingThread() {
263 return g_ExceptionInMaster ^ IsMaster() ? true : false;
264 }
265
266 struct Cancellator {
267 static std::atomic<bool> s_Ready;
268 tbb::task_group_context &m_groupToCancel;
269 intptr_t m_cancellationThreshold;
270
operatorCancellator271 void operator()() const {
272 utils::ConcurrencyTracker ct;
273 s_Ready = true;
274 while ( g_CurExecuted < m_cancellationThreshold )
275 utils::yield();
276 m_groupToCancel.cancel_group_execution();
277 g_ExecutedAtLastCatch = g_CurExecuted.load();
278 }
279
CancellatorCancellator280 Cancellator( tbb::task_group_context& ctx, intptr_t threshold )
281 : m_groupToCancel(ctx), m_cancellationThreshold(threshold)
282 {
283 s_Ready = false;
284 }
285
ResetCancellator286 static void Reset () { s_Ready = false; }
287
WaitUntilReadyCancellator288 static bool WaitUntilReady () {
289 const intptr_t limit = 10000000;
290 intptr_t n = 0;
291 do {
292 utils::yield();
293 } while( !s_Ready && ++n < limit );
294 // should yield once, then continue if Cancellator is ready.
295 REQUIRE( (s_Ready || n == limit) );
296 return s_Ready;
297 }
298 };
299
300 std::atomic<bool> Cancellator::s_Ready{ false };
301
302 template<class LauncherT, class CancellatorT>
303 void RunCancellationTest ( intptr_t threshold = 1 )
304 {
305 tbb::task_group_context ctx;
306 tbb::task_group tg;
307
308 CancellatorT cancellator(ctx, threshold);
309 LauncherT launcher(ctx);
310
311 tg.run(launcher);
312 tg.run(cancellator);
313
314 TRY();
315 tg.wait();
316 CATCH_AND_FAIL();
317 }
318
319 #endif // __TBB_test_common_exception_handling_H
320