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