xref: /oneTBB/test/common/exception_handling.h (revision 68e075cb)
1 /*
2     Copyright (c) 2005-2021 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:
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     }
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:
103     test_exception ( const char* description ) : my_description(description) {}
104 
105     const char* what() const throw() override { return my_description; }
106 };
107 
108 class solitary_test_exception : public test_exception {
109 public:
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 
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 
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 exprected = 0;\
177         g_ExecutedAtFirstCatch.compare_exchange_strong(exprected, g_CurExecuted); \
178         g_ExecutedAtLastCatch = g_CurExecuted.load(); \
179         REQUIRE_MESSAGE(e.what(), "Empty what() string" );  \
180         REQUIRE_MESSAGE((strcmp(EXCEPTION_NAME(e), (g_SolitaryException ? typeid(solitary_test_exception) : typeid(test_exception)).name() ) == 0), "Unexpected original exception name"); \
181         REQUIRE_MESSAGE((strcmp(e.what(), EXCEPTION_DESCR) == 0), "Unexpected original exception info"); \
182         g_ExceptionCaught = l_ExceptionCaughtAtCurrentLevel = true; \
183         ++g_NumExceptionsCaught; \
184     } catch ( std::exception& ) { \
185         REQUIRE_MESSAGE (false, "Unexpected std::exception" ); \
186     } catch ( ... ) { \
187         g_ExceptionCaught = l_ExceptionCaughtAtCurrentLevel = true; \
188         g_UnknownException = unknownException = true; \
189     } \
190     if ( !g_SolitaryException ) \
191         WARN_MESSAGE (true, "Multiple exceptions mode");
192 
193 #define ASSERT_EXCEPTION() \
194     { \
195         REQUIRE_MESSAGE ((!g_ExceptionsThrown || g_ExceptionCaught), "throw without catch"); \
196         REQUIRE_MESSAGE ((!g_ExceptionCaught  || g_ExceptionsThrown), "catch without throw"); \
197         REQUIRE_MESSAGE ((g_ExceptionCaught || (g_ExceptionInMaster && !g_MasterExecutedThrow) || (!g_ExceptionInMaster && !g_NonMasterExecutedThrow)), "no exception occurred"); \
198         REQUIRE_MESSAGE (!g_UnknownException, "unknown exception was caught"); \
199     }
200 
201 #define CATCH_AND_ASSERT() \
202     CATCH() \
203     ASSERT_EXCEPTION()
204 
205 #else /* !TBB_USE_EXCEPTIONS */
206 
207 inline void ThrowTestException ( intptr_t ) {}
208 
209 #endif /* !TBB_USE_EXCEPTIONS */
210 
211 #if TBB_USE_EXCEPTIONS
212 #define TRY()   \
213     bool l_ExceptionCaughtAtCurrentLevel = false, unknownException = false;    \
214     try {
215 
216 // "l_ExceptionCaughtAtCurrentLevel || unknownException" is used only to "touch" otherwise unused local variables
217 #define CATCH_AND_FAIL() } catch(...) { \
218         REQUIRE_MESSAGE (false, "Cancelling tasks must not cause any exceptions");    \
219         (void)(l_ExceptionCaughtAtCurrentLevel && unknownException);                        \
220     }
221 #else
222 #define TRY() {
223 #define CATCH_AND_FAIL() }
224 #endif
225 
226 const int c_Timeout = 1000000;
227 
228 #include "oneapi/tbb/task_arena.h"
229 
230 void WaitUntilConcurrencyPeaks ( int expected_peak ) {
231     tbb::task_group tg;
232     if ( g_Flog )
233         return;
234     int n = 0;
235 retry:
236     while ( ++n < c_Timeout && (int)utils::ConcurrencyTracker::PeakParallelism() < expected_peak )
237         utils::yield();
238 #if USE_TASK_SCHEDULER_OBSERVER
239     DOCTEST_WARN_MESSAGE( g_NumThreads == g_ActualMaxThreads, "Library did not provide sufficient threads");
240 #endif
241     DOCTEST_WARN_MESSAGE(n < c_Timeout, g_Wakeup_Msg);
242     // Workaround in case a missed wakeup takes place
243     if ( n == c_Timeout ) {
244         tg.run([]{});
245         n = 0;
246         goto retry;
247     }
248 
249     TRY();
250         tg.wait();
251     CATCH_AND_FAIL();
252 }
253 
254 inline void WaitUntilConcurrencyPeaks () { WaitUntilConcurrencyPeaks(g_NumThreads); }
255 
256 inline bool IsMaster() {
257     return std::this_thread::get_id() == g_Master;
258 }
259 
260 inline bool IsThrowingThread() {
261     return g_ExceptionInMaster ^ IsMaster() ? true : false;
262 }
263 
264 struct Cancellator {
265     static std::atomic<bool> s_Ready;
266     tbb::task_group_context &m_groupToCancel;
267     intptr_t m_cancellationThreshold;
268 
269     void operator()() const {
270         utils::ConcurrencyTracker ct;
271         s_Ready = true;
272         while ( g_CurExecuted < m_cancellationThreshold )
273             utils::yield();
274         m_groupToCancel.cancel_group_execution();
275         g_ExecutedAtLastCatch = g_CurExecuted.load();
276     }
277 
278     Cancellator( tbb::task_group_context& ctx, intptr_t threshold )
279         : m_groupToCancel(ctx), m_cancellationThreshold(threshold)
280     {
281         s_Ready = false;
282     }
283 
284     static void Reset () { s_Ready = false; }
285 
286     static bool WaitUntilReady () {
287         const intptr_t limit = 10000000;
288         intptr_t n = 0;
289         do {
290             utils::yield();
291         } while( !s_Ready && ++n < limit );
292         // should yield once, then continue if Cancellator is ready.
293         REQUIRE( (s_Ready || n == limit) );
294         return s_Ready;
295     }
296 };
297 
298 std::atomic<bool> Cancellator::s_Ready{ false };
299 
300 template<class LauncherT, class CancellatorT>
301 void RunCancellationTest ( intptr_t threshold = 1 )
302 {
303     tbb::task_group_context  ctx;
304     tbb::task_group tg;
305 
306     CancellatorT cancellator(ctx, threshold);
307     LauncherT launcher(ctx);
308 
309     tg.run(launcher);
310     tg.run(cancellator);
311 
312     TRY();
313         tg.wait();
314     CATCH_AND_FAIL();
315 }
316 
317 #endif // __TBB_test_common_exception_handling_H
318