xref: /oneTBB/test/common/exception_handling.h (revision ab1409e1)
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