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 41 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 58 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 79 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 99 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 116 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 148 oneapi::tbb::task_handle h = tg.defer([&]{ 149 completed = true; 150 }); 151 152 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 166 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 180 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; 199 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> 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> 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