| 1 | /* |
| 2 | Copyright (c) 2005-2019 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 | #include <typeinfo> |
| 18 | #include "tbb/tbb_exception.h" |
| 19 | #include "tbb/atomic.h" |
| 20 | #if USE_TASK_SCHEDULER_OBSERVER |
| 21 | #include "tbb/task_scheduler_observer.h" |
| 22 | #endif |
| 23 | #include "harness.h" |
| 24 | #include "harness_concurrency_tracker.h" |
| 25 | |
| 26 | int g_NumThreads = 0; |
| 27 | Harness::tid_t g_Master = 0; |
| 28 | const char * g_Orig_Wakeup_Msg = "Missed wakeup or machine is overloaded?" ; |
| 29 | const char * g_Wakeup_Msg = g_Orig_Wakeup_Msg; |
| 30 | |
| 31 | tbb::atomic<intptr_t> g_CurExecuted, |
| 32 | g_ExecutedAtLastCatch, |
| 33 | g_ExecutedAtFirstCatch, |
| 34 | g_ExceptionsThrown, |
| 35 | g_MasterExecutedThrow, // number of times master entered exception code |
| 36 | g_NonMasterExecutedThrow, // number of times nonmaster entered exception code |
| 37 | g_PipelinesStarted; |
| 38 | volatile bool g_ExceptionCaught = false, |
| 39 | g_UnknownException = false; |
| 40 | |
| 41 | #if USE_TASK_SCHEDULER_OBSERVER |
| 42 | tbb::atomic<intptr_t> g_ActualMaxThreads; |
| 43 | tbb::atomic<intptr_t> g_ActualCurrentThreads; |
| 44 | #endif |
| 45 | |
| 46 | volatile bool g_ThrowException = true, |
| 47 | // g_Flog is true for nested construct tests with catches (exceptions are not allowed to |
| 48 | // propagate to the tbb construct itself.) |
| 49 | g_Flog = false, |
| 50 | g_MasterExecuted = false, |
| 51 | g_NonMasterExecuted = false; |
| 52 | |
| 53 | bool g_ExceptionInMaster = false; |
| 54 | bool g_SolitaryException = false; |
| 55 | bool g_NestedPipelines = false; |
| 56 | |
| 57 | //! Number of exceptions propagated into the user code (i.e. intercepted by the tests) |
| 58 | tbb::atomic<intptr_t> g_NumExceptionsCaught; |
| 59 | |
| 60 | //----------------------------------------------------------- |
| 61 | |
| 62 | #if USE_TASK_SCHEDULER_OBSERVER |
| 63 | class eh_test_observer : public tbb::task_scheduler_observer { |
| 64 | public: |
| 65 | void on_scheduler_entry(bool is_worker) __TBB_override { |
| 66 | if(is_worker) { // we've already counted the master |
| 67 | size_t p = ++g_ActualCurrentThreads; |
| 68 | size_t q = g_ActualMaxThreads; |
| 69 | while(q < p) { |
| 70 | q = g_ActualMaxThreads.compare_and_swap(p,q); |
| 71 | } |
| 72 | } |
| 73 | else { |
| 74 | // size_t q = g_ActualMaxThreads; |
| 75 | } |
| 76 | } |
| 77 | void on_scheduler_exit(bool is_worker) __TBB_override { |
| 78 | if(is_worker) { |
| 79 | --g_ActualCurrentThreads; |
| 80 | } |
| 81 | } |
| 82 | }; |
| 83 | #endif |
| 84 | //----------------------------------------------------------- |
| 85 | |
| 86 | inline void ResetEhGlobals ( bool throwException = true, bool flog = false ) { |
| 87 | Harness::ConcurrencyTracker::Reset(); |
| 88 | g_CurExecuted = g_ExecutedAtLastCatch = g_ExecutedAtFirstCatch = 0; |
| 89 | g_ExceptionCaught = false; |
| 90 | g_UnknownException = false; |
| 91 | g_NestedPipelines = false; |
| 92 | g_ThrowException = throwException; |
| 93 | g_MasterExecutedThrow = 0; |
| 94 | g_NonMasterExecutedThrow = 0; |
| 95 | g_Flog = flog; |
| 96 | g_MasterExecuted = false; |
| 97 | g_NonMasterExecuted = false; |
| 98 | #if USE_TASK_SCHEDULER_OBSERVER |
| 99 | g_ActualMaxThreads = 1; // count master |
| 100 | g_ActualCurrentThreads = 1; // count master |
| 101 | #endif |
| 102 | g_ExceptionsThrown = g_NumExceptionsCaught = g_PipelinesStarted = 0; |
| 103 | } |
| 104 | |
| 105 | #if TBB_USE_EXCEPTIONS |
| 106 | class test_exception : public std::exception { |
| 107 | const char* my_description; |
| 108 | public: |
| 109 | test_exception ( const char* description ) : my_description(description) {} |
| 110 | |
| 111 | const char* what() const throw() __TBB_override { return my_description; } |
| 112 | }; |
| 113 | |
| 114 | class solitary_test_exception : public test_exception { |
| 115 | public: |
| 116 | solitary_test_exception ( const char* description ) : test_exception(description) {} |
| 117 | }; |
| 118 | |
| 119 | #if TBB_USE_CAPTURED_EXCEPTION |
| 120 | typedef tbb::captured_exception PropagatedException; |
| 121 | #define EXCEPTION_NAME(e) e.name() |
| 122 | #else |
| 123 | typedef test_exception PropagatedException; |
| 124 | #define EXCEPTION_NAME(e) typeid(e).name() |
| 125 | #endif |
| 126 | |
| 127 | #define EXCEPTION_DESCR "Test exception" |
| 128 | |
| 129 | #if HARNESS_EH_SIMPLE_MODE |
| 130 | |
| 131 | static void ThrowTestException () { |
| 132 | ++g_ExceptionsThrown; |
| 133 | throw test_exception(EXCEPTION_DESCR); |
| 134 | } |
| 135 | |
| 136 | #else /* !HARNESS_EH_SIMPLE_MODE */ |
| 137 | |
| 138 | static void ThrowTestException ( intptr_t threshold ) { |
| 139 | bool inMaster = (Harness::CurrentTid() == g_Master); |
| 140 | if ( !g_ThrowException || // if we're not supposed to throw |
| 141 | (!g_Flog && // if we're not catching throw in bodies and |
| 142 | (g_ExceptionInMaster ^ inMaster)) ) { // we're the master and not expected to throw |
| 143 | // or are the master and the master is not the one to throw (??) |
| 144 | return; |
| 145 | } |
| 146 | while ( Existed() < threshold ) |
| 147 | __TBB_Yield(); |
| 148 | if ( !g_SolitaryException ) { |
| 149 | ++g_ExceptionsThrown; |
| 150 | if(inMaster) ++g_MasterExecutedThrow; else ++g_NonMasterExecutedThrow; |
| 151 | throw test_exception(EXCEPTION_DESCR); |
| 152 | } |
| 153 | // g_SolitaryException == true |
| 154 | if(g_NestedPipelines) { |
| 155 | // only throw exception if we have started at least two inner pipelines |
| 156 | // else return |
| 157 | if(g_PipelinesStarted >= 3) { |
| 158 | if ( g_ExceptionsThrown.compare_and_swap(1, 0) == 0 ) { |
| 159 | if(inMaster) ++g_MasterExecutedThrow; else ++g_NonMasterExecutedThrow; |
| 160 | throw solitary_test_exception(EXCEPTION_DESCR); |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | else { |
| 165 | if ( g_ExceptionsThrown.compare_and_swap(1, 0) == 0 ) { |
| 166 | if(inMaster) ++g_MasterExecutedThrow; else ++g_NonMasterExecutedThrow; |
| 167 | throw solitary_test_exception(EXCEPTION_DESCR); |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | #endif /* !HARNESS_EH_SIMPLE_MODE */ |
| 172 | |
| 173 | #define UPDATE_COUNTS() \ |
| 174 | { \ |
| 175 | ++g_CurExecuted; \ |
| 176 | if(g_Master == Harness::CurrentTid()) g_MasterExecuted = true; \ |
| 177 | else g_NonMasterExecuted = true; \ |
| 178 | if( tbb::task::self().is_cancelled() ) ++g_TGCCancelled; \ |
| 179 | } |
| 180 | |
| 181 | #define CATCH() \ |
| 182 | } catch ( PropagatedException& e ) { \ |
| 183 | g_ExecutedAtFirstCatch.compare_and_swap(g_CurExecuted,0); \ |
| 184 | g_ExecutedAtLastCatch = g_CurExecuted; \ |
| 185 | ASSERT( e.what(), "Empty what() string" ); \ |
| 186 | ASSERT (__TBB_EXCEPTION_TYPE_INFO_BROKEN || strcmp(EXCEPTION_NAME(e), (g_SolitaryException ? typeid(solitary_test_exception) : typeid(test_exception)).name() ) == 0, "Unexpected original exception name"); \ |
| 187 | ASSERT (__TBB_EXCEPTION_TYPE_INFO_BROKEN || strcmp(e.what(), EXCEPTION_DESCR) == 0, "Unexpected original exception info"); \ |
| 188 | g_ExceptionCaught = l_ExceptionCaughtAtCurrentLevel = true; \ |
| 189 | ++g_NumExceptionsCaught; \ |
| 190 | } catch ( tbb::tbb_exception& e ) { \ |
| 191 | REPORT("Unexpected %s\n", e.name()); \ |
| 192 | ASSERT (g_UnknownException && !g_UnknownException, "Unexpected tbb::tbb_exception" ); \ |
| 193 | } catch ( std::exception& e ) { \ |
| 194 | REPORT("Unexpected %s\n", typeid(e).name()); \ |
| 195 | ASSERT (g_UnknownException && !g_UnknownException, "Unexpected std::exception" ); \ |
| 196 | } catch ( ... ) { \ |
| 197 | g_ExceptionCaught = l_ExceptionCaughtAtCurrentLevel = true; \ |
| 198 | g_UnknownException = unknownException = true; \ |
| 199 | } \ |
| 200 | if ( !g_SolitaryException ) \ |
| 201 | REMARK_ONCE ("Multiple exceptions mode: %d throws", (intptr_t)g_ExceptionsThrown); |
| 202 | |
| 203 | #define ASSERT_EXCEPTION() \ |
| 204 | { \ |
| 205 | ASSERT (!g_ExceptionsThrown || g_ExceptionCaught, "throw without catch"); \ |
| 206 | ASSERT (!g_ExceptionCaught || g_ExceptionsThrown, "catch without throw"); \ |
| 207 | ASSERT (g_ExceptionCaught || (g_ExceptionInMaster && !g_MasterExecutedThrow) || (!g_ExceptionInMaster && !g_NonMasterExecutedThrow), "no exception occurred"); \ |
| 208 | ASSERT (__TBB_EXCEPTION_TYPE_INFO_BROKEN || !g_UnknownException, "unknown exception was caught"); \ |
| 209 | } |
| 210 | |
| 211 | #define CATCH_AND_ASSERT() \ |
| 212 | CATCH() \ |
| 213 | ASSERT_EXCEPTION() |
| 214 | |
| 215 | #else /* !TBB_USE_EXCEPTIONS */ |
| 216 | |
| 217 | inline void ThrowTestException ( intptr_t ) {} |
| 218 | |
| 219 | #endif /* !TBB_USE_EXCEPTIONS */ |
| 220 | |
| 221 | #define TRY() \ |
| 222 | bool l_ExceptionCaughtAtCurrentLevel = false, unknownException = false; \ |
| 223 | __TBB_TRY { |
| 224 | |
| 225 | // "l_ExceptionCaughtAtCurrentLevel || unknownException" is used only to "touch" otherwise unused local variables |
| 226 | #define CATCH_AND_FAIL() } __TBB_CATCH(...) { \ |
| 227 | ASSERT (false, "Cancelling tasks must not cause any exceptions"); \ |
| 228 | (void)(l_ExceptionCaughtAtCurrentLevel && unknownException); \ |
| 229 | } |
| 230 | |
| 231 | const int c_Timeout = 1000000; |
| 232 | |
| 233 | void WaitUntilConcurrencyPeaks ( int expected_peak ) { |
| 234 | if ( g_Flog ) |
| 235 | return; |
| 236 | int n = 0; |
| 237 | retry: |
| 238 | while ( ++n < c_Timeout && (int)Harness::ConcurrencyTracker::PeakParallelism() < expected_peak ) |
| 239 | __TBB_Yield(); |
| 240 | #if USE_TASK_SCHEDULER_OBSERVER |
| 241 | ASSERT_WARNING( g_NumThreads == g_ActualMaxThreads, "Library did not provide sufficient threads" ); |
| 242 | #endif |
| 243 | ASSERT_WARNING(n < c_Timeout,g_Wakeup_Msg); |
| 244 | // Workaround in case a missed wakeup takes place |
| 245 | if ( n == c_Timeout ) { |
| 246 | tbb::task &r = *new( tbb::task::allocate_root() ) tbb::empty_task(); |
| 247 | r.spawn(r); |
| 248 | n = 0; |
| 249 | goto retry; |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | inline void WaitUntilConcurrencyPeaks () { WaitUntilConcurrencyPeaks(g_NumThreads); } |
| 254 | |
| 255 | inline bool IsMaster() { |
| 256 | return Harness::CurrentTid() == g_Master; |
| 257 | } |
| 258 | |
| 259 | inline bool IsThrowingThread() { |
| 260 | return g_ExceptionInMaster ^ IsMaster() ? true : false; |
| 261 | } |
| 262 | |
| 263 | class CancellatorTask : public tbb::task { |
| 264 | static volatile bool s_Ready; |
| 265 | tbb::task_group_context &m_groupToCancel; |
| 266 | intptr_t m_cancellationThreshold; |
| 267 | |
| 268 | tbb::task* execute () __TBB_override { |
| 269 | Harness::ConcurrencyTracker ct; |
| 270 | s_Ready = true; |
| 271 | while ( g_CurExecuted < m_cancellationThreshold ) |
| 272 | __TBB_Yield(); |
| 273 | m_groupToCancel.cancel_group_execution(); |
| 274 | g_ExecutedAtLastCatch = g_CurExecuted; |
| 275 | return NULL; |
| 276 | } |
| 277 | public: |
| 278 | CancellatorTask ( 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 | __TBB_Yield(); |
| 291 | } while( !s_Ready && ++n < limit ); |
| 292 | // should yield once, then continue if Cancellator is ready. |
| 293 | ASSERT( s_Ready || n == limit, NULL ); |
| 294 | return s_Ready; |
| 295 | } |
| 296 | }; |
| 297 | |
| 298 | volatile bool CancellatorTask::s_Ready = false; |
| 299 | |
| 300 | template<class LauncherTaskT, class CancellatorTaskT> |
| 301 | void RunCancellationTest ( intptr_t threshold = 1 ) |
| 302 | { |
| 303 | tbb::task_group_context ctx; |
| 304 | tbb::empty_task &r = *new( tbb::task::allocate_root(ctx) ) tbb::empty_task; |
| 305 | r.set_ref_count(3); |
| 306 | r.spawn( *new( r.allocate_child() ) CancellatorTaskT(ctx, threshold) ); |
| 307 | __TBB_Yield(); |
| 308 | r.spawn( *new( r.allocate_child() ) LauncherTaskT(ctx) ); |
| 309 | TRY(); |
| 310 | r.wait_for_all(); |
| 311 | CATCH_AND_FAIL(); |
| 312 | r.destroy(r); |
| 313 | } |
| 314 | |