1 /*
2 Copyright (c) 2021-2022 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 "oneapi/tbb/task_group.h"
18 #include "oneapi/tbb/task_arena.h"
19 #include "common/test.h"
20 #include "common/utils.h"
21
22 #include "common/spin_barrier.h"
23
24 #include <type_traits>
25 #include "oneapi/tbb/global_control.h"
26
27
28 //! \file conformance_task_group.cpp
29 //! \brief Test for [scheduler.task_group] specification
30
31 //! Test checks that for lost task handle
32 //! \brief \ref requirement
33 TEST_CASE("Task handle created but not run"){
34 {
35 oneapi::tbb::task_group tg;
36
37 //This flag is intentionally made non-atomic for Thread Sanitizer
38 //to raise a flag if implementation of task_group is incorrect
39 bool run {false};
40
__anon0c2008c70102null41 auto h = tg.defer([&]{
42 run = true;
43 });
44 CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
45 }
46 }
47
48 //! Basic test for task handle
49 //! \brief \ref interface \ref requirement
50 TEST_CASE("Task handle run"){
51 oneapi::tbb::task_handle h;
52
53 oneapi::tbb::task_group tg;
54 //This flag is intentionally made non-atomic for Thread Sanitizer
55 //to raise a flag if implementation of task_group is incorrect
56 bool run {false};
57
__anon0c2008c70202null58 h = tg.defer([&]{
59 run = true;
60 });
61 CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
62 tg.run(std::move(h));
63 tg.wait();
64 CHECK_MESSAGE(run == true, "Delayed task should be completed when task_group::wait exits");
65
66 CHECK_MESSAGE(h == nullptr, "Delayed task can be executed only once");
67 }
68
69 //! Basic test for task handle
70 //! \brief \ref interface \ref requirement
71 TEST_CASE("Task handle run_and_wait"){
72 oneapi::tbb::task_handle h;
73
74 oneapi::tbb::task_group tg;
75 //This flag is intentionally made non-atomic for Thread Sanitizer
76 //to raise a flag if implementation of task_group is incorrect
77 bool run {false};
78
__anon0c2008c70302null79 h = tg.defer([&]{
80 run = true;
81 });
82 CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
83 tg.run_and_wait(std::move(h));
84 CHECK_MESSAGE(run == true, "Delayed task should be completed when task_group::wait exits");
85
86 CHECK_MESSAGE(h == nullptr, "Delayed task can be executed only once");
87 }
88
89 //! Test for empty check
90 //! \brief \ref interface
91 TEST_CASE("Task handle empty check"){
92 oneapi::tbb::task_group tg;
93
94 oneapi::tbb::task_handle h;
95
96 bool empty = (h == nullptr);
97 CHECK_MESSAGE(empty, "default constructed task_handle should be empty");
98
__anon0c2008c70402null99 h = tg.defer([]{});
100
101 CHECK_MESSAGE(h != nullptr, "delayed task returned by task_group::delayed should not be empty");
102 }
103
104 //! Test for comparison operations
105 //! \brief \ref interface
106 TEST_CASE("Task handle comparison/empty checks"){
107 oneapi::tbb::task_group tg;
108
109 oneapi::tbb::task_handle h;
110
111 bool empty = ! static_cast<bool>(h);
112 CHECK_MESSAGE(empty, "default constructed task_handle should be empty");
113 CHECK_MESSAGE(h == nullptr, "default constructed task_handle should be empty");
114 CHECK_MESSAGE(nullptr == h, "default constructed task_handle should be empty");
115
__anon0c2008c70502null116 h = tg.defer([]{});
117
118 CHECK_MESSAGE(h != nullptr, "deferred task returned by task_group::defer() should not be empty");
119 CHECK_MESSAGE(nullptr != h, "deferred task returned by task_group::defer() should not be empty");
120
121 }
122
123 //! Test for task_handle being non copyable
124 //! \brief \ref interface
125 TEST_CASE("Task handle being non copyable"){
126 static_assert(
127 (!std::is_copy_constructible<oneapi::tbb::task_handle>::value)
128 &&(!std::is_copy_assignable<oneapi::tbb::task_handle>::value),
129 "oneapi::tbb::task_handle should be non copyable");
130 }
131 //! Test that task_handle prolongs task_group::wait
132 //! \brief \ref requirement
133 TEST_CASE("Task handle blocks wait"){
134 //Forbid creation of worker threads to ensure that task described by the task_handle is not run until wait is called
135 oneapi::tbb::global_control s(oneapi::tbb::global_control::max_allowed_parallelism, 1);
136 oneapi::tbb::task_group tg;
137 //explicit task_arena is needed to prevent a deadlock,
138 //as both task_group::run() and task_group::wait() should be called in the same arena
139 //to guarantee execution of the task spawned by run().
140 oneapi::tbb::task_arena arena;
141
142 //This flag is intentionally made non-atomic for Thread Sanitizer
143 //to raise a flag if implementation of task_group is incorrect
144 bool completed {false};
145 std::atomic<bool> start_wait {false};
146 std::atomic<bool> thread_started{false};
147
__anon0c2008c70602null148 oneapi::tbb::task_handle h = tg.defer([&]{
149 completed = true;
150 });
151
__anon0c2008c70702null152 std::thread wait_thread {[&]{
153 CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) is called");
154
155 thread_started = true;
156 utils::SpinWaitUntilEq(start_wait, true);
157 arena.execute([&]{
158 tg.wait();
159 CHECK_MESSAGE(completed == true, "Deferred task should be completed when task_group::wait exits");
160 });
161 }};
162
163 utils::SpinWaitUntilEq(thread_started, true);
164 CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) is called");
165
__anon0c2008c70902null166 arena.execute([&]{
167 tg.run(std::move(h));
168 });
169 CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) and wait is called");
170 start_wait = true;
171 wait_thread.join();
172 }
173
174 #if TBB_USE_EXCEPTIONS
175 //! The test for exception handling in task_handle
176 //! \brief \ref requirement
177 TEST_CASE("Task handle exception propagation"){
178 oneapi::tbb::task_group tg;
179
__anon0c2008c70a02null180 oneapi::tbb::task_handle h = tg.defer([&]{
181 volatile bool suppress_unreachable_code_warning = true;
182 if (suppress_unreachable_code_warning) {
183 throw std::runtime_error{ "" };
184 }
185 });
186
187 tg.run(std::move(h));
188
189 CHECK_THROWS_AS(tg.wait(), std::runtime_error);
190 }
191 #endif // TBB_USE_EXCEPTIONS
192
193 //TODO: move to some common place to share with unit tests
194 namespace accept_task_group_context {
195
196 struct SelfRunner {
197 tbb::task_group& m_tg;
198 std::atomic<unsigned>& count;
operator ()accept_task_group_context::SelfRunner199 void operator()() const {
200 unsigned previous_count = count.fetch_sub(1);
201 if (previous_count > 1)
202 m_tg.run( *this );
203 }
204 };
205
206 template <typename TaskGroup, typename CancelF, typename WaitF>
run_cancellation_use_case(CancelF && cancel,WaitF && wait)207 void run_cancellation_use_case(CancelF&& cancel, WaitF&& wait) {
208 std::atomic<bool> outer_cancelled{false};
209 std::atomic<unsigned> count{13};
210
211 tbb::task_group_context inner_ctx(tbb::task_group_context::isolated);
212 TaskGroup inner_tg(inner_ctx);
213
214 tbb::task_group outer_tg;
215 auto outer_tg_task = [&] {
216 inner_tg.run([&] {
217 utils::SpinWaitUntilEq(outer_cancelled, true);
218 inner_tg.run( SelfRunner{inner_tg, count} );
219 });
220
221 utils::try_call([&] {
222 std::forward<CancelF>(cancel)(outer_tg);
223 }).on_completion([&] {
224 outer_cancelled = true;
225 });
226 };
227
228 auto check = [&] {
229 tbb::task_group_status outer_status = tbb::task_group_status::not_complete;
230 outer_status = std::forward<WaitF>(wait)(outer_tg);
231 CHECK_MESSAGE(
232 outer_status == tbb::task_group_status::canceled,
233 "Outer task group should have been cancelled."
234 );
235
236 tbb::task_group_status inner_status = inner_tg.wait();
237 CHECK_MESSAGE(
238 inner_status == tbb::task_group_status::complete,
239 "Inner task group should have completed despite the cancellation of the outer one."
240 );
241
242 CHECK_MESSAGE(0 == count, "Some of the inner group tasks were not executed.");
243 };
244
245 outer_tg.run(outer_tg_task);
246 check();
247 }
248
249 template <typename TaskGroup>
test()250 void test() {
251 run_cancellation_use_case<TaskGroup>(
252 [](tbb::task_group& outer) { outer.cancel(); },
253 [](tbb::task_group& outer) { return outer.wait(); }
254 );
255
256 #if TBB_USE_EXCEPTIONS
257 run_cancellation_use_case<TaskGroup>(
258 [](tbb::task_group& /*outer*/) {
259 volatile bool suppress_unreachable_code_warning = true;
260 if (suppress_unreachable_code_warning) {
261 throw int();
262 }
263 },
264 [](tbb::task_group& outer) {
265 try {
266 outer.wait();
267 return tbb::task_group_status::complete;
268 } catch(const int&) {
269 return tbb::task_group_status::canceled;
270 }
271 }
272 );
273 #endif
274 }
275
276 } // namespace accept_task_group_context
277
278 //! Respect task_group_context passed from outside
279 //! \brief \ref interface \ref requirement
280 TEST_CASE("Respect task_group_context passed from outside") {
281 accept_task_group_context::test<tbb::task_group>();
282 }
283
284