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
26int g_NumThreads = 0;
27Harness::tid_t g_Master = 0;
28const char * g_Orig_Wakeup_Msg = "Missed wakeup or machine is overloaded?";
29const char * g_Wakeup_Msg = g_Orig_Wakeup_Msg;
30
31tbb::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;
38volatile bool g_ExceptionCaught = false,
39 g_UnknownException = false;
40
41#if USE_TASK_SCHEDULER_OBSERVER
42tbb::atomic<intptr_t> g_ActualMaxThreads;
43tbb::atomic<intptr_t> g_ActualCurrentThreads;
44#endif
45
46volatile 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
53bool g_ExceptionInMaster = false;
54bool g_SolitaryException = false;
55bool g_NestedPipelines = false;
56
57//! Number of exceptions propagated into the user code (i.e. intercepted by the tests)
58tbb::atomic<intptr_t> g_NumExceptionsCaught;
59
60//-----------------------------------------------------------
61
62#if USE_TASK_SCHEDULER_OBSERVER
63class eh_test_observer : public tbb::task_scheduler_observer {
64public:
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
86inline 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
106class test_exception : public std::exception {
107 const char* my_description;
108public:
109 test_exception ( const char* description ) : my_description(description) {}
110
111 const char* what() const throw() __TBB_override { return my_description; }
112};
113
114class solitary_test_exception : public test_exception {
115public:
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
131static void ThrowTestException () {
132 ++g_ExceptionsThrown;
133 throw test_exception(EXCEPTION_DESCR);
134}
135
136#else /* !HARNESS_EH_SIMPLE_MODE */
137
138static 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
217inline 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
231const int c_Timeout = 1000000;
232
233void WaitUntilConcurrencyPeaks ( int expected_peak ) {
234 if ( g_Flog )
235 return;
236 int n = 0;
237retry:
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
253inline void WaitUntilConcurrencyPeaks () { WaitUntilConcurrencyPeaks(g_NumThreads); }
254
255inline bool IsMaster() {
256 return Harness::CurrentTid() == g_Master;
257}
258
259inline bool IsThrowingThread() {
260 return g_ExceptionInMaster ^ IsMaster() ? true : false;
261}
262
263class 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 }
277public:
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
298volatile bool CancellatorTask::s_Ready = false;
299
300template<class LauncherTaskT, class CancellatorTaskT>
301void 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