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