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 | |