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