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