174b7fc74SAnton Potapov /*
2*13f9f32bSSergey Zheltov     Copyright (c) 2021-2022 Intel Corporation
374b7fc74SAnton Potapov 
474b7fc74SAnton Potapov     Licensed under the Apache License, Version 2.0 (the "License");
574b7fc74SAnton Potapov     you may not use this file except in compliance with the License.
674b7fc74SAnton Potapov     You may obtain a copy of the License at
774b7fc74SAnton Potapov 
874b7fc74SAnton Potapov         http://www.apache.org/licenses/LICENSE-2.0
974b7fc74SAnton Potapov 
1074b7fc74SAnton Potapov     Unless required by applicable law or agreed to in writing, software
1174b7fc74SAnton Potapov     distributed under the License is distributed on an "AS IS" BASIS,
1274b7fc74SAnton Potapov     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1374b7fc74SAnton Potapov     See the License for the specific language governing permissions and
1474b7fc74SAnton Potapov     limitations under the License.
1574b7fc74SAnton Potapov */
1674b7fc74SAnton Potapov 
1774b7fc74SAnton Potapov #include "oneapi/tbb/task_group.h"
183db67b5bSAnton Potapov #include "oneapi/tbb/task_arena.h"
1974b7fc74SAnton Potapov #include "common/test.h"
2074b7fc74SAnton Potapov #include "common/utils.h"
2174b7fc74SAnton Potapov 
2274b7fc74SAnton Potapov #include "common/spin_barrier.h"
2374b7fc74SAnton Potapov 
2474b7fc74SAnton Potapov #include <type_traits>
253db67b5bSAnton Potapov #include "oneapi/tbb/global_control.h"
263db67b5bSAnton Potapov 
2774b7fc74SAnton Potapov 
2874b7fc74SAnton Potapov //! \file conformance_task_group.cpp
2974b7fc74SAnton Potapov //! \brief Test for [scheduler.task_group] specification
3074b7fc74SAnton Potapov 
3174b7fc74SAnton Potapov //! Test checks that for lost task handle
3274b7fc74SAnton Potapov //! \brief \ref requirement
3374b7fc74SAnton Potapov TEST_CASE("Task handle created but not run"){
3474b7fc74SAnton Potapov     {
3574b7fc74SAnton Potapov         oneapi::tbb::task_group tg;
3674b7fc74SAnton Potapov 
3774b7fc74SAnton Potapov         //This flag is intentionally made non-atomic for Thread Sanitizer
3874b7fc74SAnton Potapov         //to raise a flag if implementation of task_group is incorrect
3974b7fc74SAnton Potapov         bool run {false};
4074b7fc74SAnton Potapov 
__anon0c2008c70102null4174b7fc74SAnton Potapov         auto h = tg.defer([&]{
4274b7fc74SAnton Potapov             run = true;
4374b7fc74SAnton Potapov         });
4474b7fc74SAnton Potapov         CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
4574b7fc74SAnton Potapov     }
4674b7fc74SAnton Potapov }
4774b7fc74SAnton Potapov 
4874b7fc74SAnton Potapov //! Basic test for task handle
4974b7fc74SAnton Potapov //! \brief \ref interface \ref requirement
5074b7fc74SAnton Potapov TEST_CASE("Task handle run"){
5174b7fc74SAnton Potapov     oneapi::tbb::task_handle h;
5274b7fc74SAnton Potapov 
5374b7fc74SAnton Potapov     oneapi::tbb::task_group tg;
5474b7fc74SAnton Potapov     //This flag is intentionally made non-atomic for Thread Sanitizer
5574b7fc74SAnton Potapov     //to raise a flag if implementation of task_group is incorrect
5674b7fc74SAnton Potapov     bool run {false};
5774b7fc74SAnton Potapov 
__anon0c2008c70202null5874b7fc74SAnton Potapov     h = tg.defer([&]{
5974b7fc74SAnton Potapov         run = true;
6074b7fc74SAnton Potapov     });
6174b7fc74SAnton Potapov     CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
6274b7fc74SAnton Potapov     tg.run(std::move(h));
6374b7fc74SAnton Potapov     tg.wait();
6474b7fc74SAnton Potapov     CHECK_MESSAGE(run == true, "Delayed task should be completed when task_group::wait exits");
6574b7fc74SAnton Potapov 
6674b7fc74SAnton Potapov     CHECK_MESSAGE(h == nullptr, "Delayed task can be executed only once");
6774b7fc74SAnton Potapov }
6874b7fc74SAnton Potapov 
6974b7fc74SAnton Potapov //! Basic test for task handle
7074b7fc74SAnton Potapov //! \brief \ref interface \ref requirement
7174b7fc74SAnton Potapov TEST_CASE("Task handle run_and_wait"){
7274b7fc74SAnton Potapov     oneapi::tbb::task_handle h;
7374b7fc74SAnton Potapov 
7474b7fc74SAnton Potapov     oneapi::tbb::task_group tg;
7574b7fc74SAnton Potapov     //This flag is intentionally made non-atomic for Thread Sanitizer
7674b7fc74SAnton Potapov     //to raise a flag if implementation of task_group is incorrect
7774b7fc74SAnton Potapov     bool run {false};
7874b7fc74SAnton Potapov 
__anon0c2008c70302null7974b7fc74SAnton Potapov     h = tg.defer([&]{
8074b7fc74SAnton Potapov         run = true;
8174b7fc74SAnton Potapov     });
8274b7fc74SAnton Potapov     CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
8374b7fc74SAnton Potapov     tg.run_and_wait(std::move(h));
8474b7fc74SAnton Potapov     CHECK_MESSAGE(run == true, "Delayed task should be completed when task_group::wait exits");
8574b7fc74SAnton Potapov 
8674b7fc74SAnton Potapov     CHECK_MESSAGE(h == nullptr, "Delayed task can be executed only once");
8774b7fc74SAnton Potapov }
8874b7fc74SAnton Potapov 
8974b7fc74SAnton Potapov //! Test for empty check
9074b7fc74SAnton Potapov //! \brief \ref interface
9174b7fc74SAnton Potapov TEST_CASE("Task handle empty check"){
9274b7fc74SAnton Potapov     oneapi::tbb::task_group tg;
9374b7fc74SAnton Potapov 
9474b7fc74SAnton Potapov     oneapi::tbb::task_handle h;
9574b7fc74SAnton Potapov 
9674b7fc74SAnton Potapov     bool empty = (h == nullptr);
9774b7fc74SAnton Potapov     CHECK_MESSAGE(empty, "default constructed task_handle should be empty");
9874b7fc74SAnton Potapov 
__anon0c2008c70402null9974b7fc74SAnton Potapov     h = tg.defer([]{});
10074b7fc74SAnton Potapov 
10174b7fc74SAnton Potapov     CHECK_MESSAGE(h != nullptr, "delayed task returned by task_group::delayed should not be empty");
10274b7fc74SAnton Potapov }
10374b7fc74SAnton Potapov 
10474b7fc74SAnton Potapov //! Test for comparison operations
10574b7fc74SAnton Potapov //! \brief \ref interface
10674b7fc74SAnton Potapov TEST_CASE("Task handle comparison/empty checks"){
10774b7fc74SAnton Potapov     oneapi::tbb::task_group tg;
10874b7fc74SAnton Potapov 
10974b7fc74SAnton Potapov     oneapi::tbb::task_handle h;
11074b7fc74SAnton Potapov 
11174b7fc74SAnton Potapov     bool empty =  ! static_cast<bool>(h);
11274b7fc74SAnton Potapov     CHECK_MESSAGE(empty, "default constructed task_handle should be empty");
11374b7fc74SAnton Potapov     CHECK_MESSAGE(h == nullptr, "default constructed task_handle should be empty");
11474b7fc74SAnton Potapov     CHECK_MESSAGE(nullptr == h, "default constructed task_handle should be empty");
11574b7fc74SAnton Potapov 
__anon0c2008c70502null11674b7fc74SAnton Potapov     h = tg.defer([]{});
11774b7fc74SAnton Potapov 
11874b7fc74SAnton Potapov     CHECK_MESSAGE(h != nullptr, "deferred task returned by task_group::defer() should not be empty");
11974b7fc74SAnton Potapov     CHECK_MESSAGE(nullptr != h, "deferred task returned by task_group::defer() should not be empty");
12074b7fc74SAnton Potapov 
12174b7fc74SAnton Potapov }
12274b7fc74SAnton Potapov 
12374b7fc74SAnton Potapov //! Test for task_handle being non copyable
12474b7fc74SAnton Potapov //! \brief \ref interface
12574b7fc74SAnton Potapov TEST_CASE("Task handle being non copyable"){
12674b7fc74SAnton Potapov     static_assert(
12774b7fc74SAnton Potapov               (!std::is_copy_constructible<oneapi::tbb::task_handle>::value)
12874b7fc74SAnton Potapov             &&(!std::is_copy_assignable<oneapi::tbb::task_handle>::value),
12974b7fc74SAnton Potapov             "oneapi::tbb::task_handle should be non copyable");
13074b7fc74SAnton Potapov }
13174b7fc74SAnton Potapov //! Test that task_handle prolongs task_group::wait
13274b7fc74SAnton Potapov //! \brief \ref requirement
13374b7fc74SAnton Potapov TEST_CASE("Task handle blocks wait"){
1343db67b5bSAnton Potapov     //Forbid creation of worker threads to ensure that task described by the task_handle is not run until wait is called
1353db67b5bSAnton Potapov     oneapi::tbb::global_control s(oneapi::tbb::global_control::max_allowed_parallelism, 1);
13674b7fc74SAnton Potapov     oneapi::tbb::task_group tg;
1373db67b5bSAnton Potapov     //explicit task_arena is needed to prevent a deadlock,
1383db67b5bSAnton Potapov     //as both task_group::run() and task_group::wait() should be called in the same arena
1393db67b5bSAnton Potapov     //to guarantee execution of the task spawned by run().
1403db67b5bSAnton Potapov     oneapi::tbb::task_arena arena;
14174b7fc74SAnton Potapov 
14274b7fc74SAnton Potapov     //This flag is intentionally made non-atomic for Thread Sanitizer
14374b7fc74SAnton Potapov     //to raise a flag if implementation of task_group is incorrect
14474b7fc74SAnton Potapov     bool completed  {false};
14574b7fc74SAnton Potapov     std::atomic<bool> start_wait {false};
14674b7fc74SAnton Potapov     std::atomic<bool> thread_started{false};
14774b7fc74SAnton Potapov 
__anon0c2008c70602null14874b7fc74SAnton Potapov     oneapi::tbb::task_handle h = tg.defer([&]{
14974b7fc74SAnton Potapov         completed = true;
15074b7fc74SAnton Potapov     });
15174b7fc74SAnton Potapov 
__anon0c2008c70702null15274b7fc74SAnton Potapov     std::thread wait_thread {[&]{
15374b7fc74SAnton Potapov         CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) is called");
15474b7fc74SAnton Potapov 
15574b7fc74SAnton Potapov         thread_started = true;
15674b7fc74SAnton Potapov         utils::SpinWaitUntilEq(start_wait, true);
1573db67b5bSAnton Potapov         arena.execute([&]{
15874b7fc74SAnton Potapov             tg.wait();
15974b7fc74SAnton Potapov             CHECK_MESSAGE(completed == true, "Deferred task should be completed when task_group::wait exits");
1603db67b5bSAnton Potapov         });
16174b7fc74SAnton Potapov     }};
16274b7fc74SAnton Potapov 
16374b7fc74SAnton Potapov     utils::SpinWaitUntilEq(thread_started, true);
16474b7fc74SAnton Potapov     CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) is called");
16574b7fc74SAnton Potapov 
__anon0c2008c70902null1663db67b5bSAnton Potapov     arena.execute([&]{
16774b7fc74SAnton Potapov         tg.run(std::move(h));
1683db67b5bSAnton Potapov     });
1693db67b5bSAnton Potapov     CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) and wait is called");
17074b7fc74SAnton Potapov     start_wait = true;
17174b7fc74SAnton Potapov     wait_thread.join();
17274b7fc74SAnton Potapov }
17374b7fc74SAnton Potapov 
17474b7fc74SAnton Potapov #if TBB_USE_EXCEPTIONS
17574b7fc74SAnton Potapov //! The test for exception handling in task_handle
17674b7fc74SAnton Potapov //! \brief \ref requirement
17774b7fc74SAnton Potapov TEST_CASE("Task handle exception propagation"){
17874b7fc74SAnton Potapov     oneapi::tbb::task_group tg;
17974b7fc74SAnton Potapov 
__anon0c2008c70a02null18074b7fc74SAnton Potapov     oneapi::tbb::task_handle h = tg.defer([&]{
18174b7fc74SAnton Potapov         volatile bool suppress_unreachable_code_warning = true;
18274b7fc74SAnton Potapov         if (suppress_unreachable_code_warning) {
18374b7fc74SAnton Potapov             throw std::runtime_error{ "" };
18474b7fc74SAnton Potapov         }
18574b7fc74SAnton Potapov     });
18674b7fc74SAnton Potapov 
18774b7fc74SAnton Potapov     tg.run(std::move(h));
18874b7fc74SAnton Potapov 
18974b7fc74SAnton Potapov     CHECK_THROWS_AS(tg.wait(), std::runtime_error);
19074b7fc74SAnton Potapov }
19174b7fc74SAnton Potapov #endif // TBB_USE_EXCEPTIONS
19274b7fc74SAnton Potapov 
19374b7fc74SAnton Potapov //TODO: move to some common place to share with unit tests
19474b7fc74SAnton Potapov namespace accept_task_group_context {
19574b7fc74SAnton Potapov 
19674b7fc74SAnton Potapov struct SelfRunner {
19774b7fc74SAnton Potapov     tbb::task_group& m_tg;
19874b7fc74SAnton Potapov     std::atomic<unsigned>& count;
operator ()accept_task_group_context::SelfRunner19974b7fc74SAnton Potapov     void operator()() const {
20074b7fc74SAnton Potapov         unsigned previous_count = count.fetch_sub(1);
20174b7fc74SAnton Potapov         if (previous_count > 1)
20274b7fc74SAnton Potapov             m_tg.run( *this );
20374b7fc74SAnton Potapov     }
20474b7fc74SAnton Potapov };
20574b7fc74SAnton Potapov 
20674b7fc74SAnton Potapov template <typename TaskGroup, typename CancelF, typename WaitF>
run_cancellation_use_case(CancelF && cancel,WaitF && wait)20774b7fc74SAnton Potapov void run_cancellation_use_case(CancelF&& cancel, WaitF&& wait) {
20874b7fc74SAnton Potapov     std::atomic<bool> outer_cancelled{false};
20974b7fc74SAnton Potapov     std::atomic<unsigned> count{13};
21074b7fc74SAnton Potapov 
21174b7fc74SAnton Potapov     tbb::task_group_context inner_ctx(tbb::task_group_context::isolated);
21274b7fc74SAnton Potapov     TaskGroup inner_tg(inner_ctx);
21374b7fc74SAnton Potapov 
21474b7fc74SAnton Potapov     tbb::task_group outer_tg;
21574b7fc74SAnton Potapov     auto outer_tg_task = [&] {
21674b7fc74SAnton Potapov         inner_tg.run([&] {
21774b7fc74SAnton Potapov             utils::SpinWaitUntilEq(outer_cancelled, true);
21874b7fc74SAnton Potapov             inner_tg.run( SelfRunner{inner_tg, count} );
21974b7fc74SAnton Potapov         });
22074b7fc74SAnton Potapov 
22174b7fc74SAnton Potapov         utils::try_call([&] {
22274b7fc74SAnton Potapov             std::forward<CancelF>(cancel)(outer_tg);
22374b7fc74SAnton Potapov         }).on_completion([&] {
22474b7fc74SAnton Potapov             outer_cancelled = true;
22574b7fc74SAnton Potapov         });
22674b7fc74SAnton Potapov     };
22774b7fc74SAnton Potapov 
22874b7fc74SAnton Potapov     auto check = [&] {
22974b7fc74SAnton Potapov         tbb::task_group_status outer_status = tbb::task_group_status::not_complete;
23074b7fc74SAnton Potapov         outer_status = std::forward<WaitF>(wait)(outer_tg);
23174b7fc74SAnton Potapov         CHECK_MESSAGE(
23274b7fc74SAnton Potapov             outer_status == tbb::task_group_status::canceled,
23374b7fc74SAnton Potapov             "Outer task group should have been cancelled."
23474b7fc74SAnton Potapov         );
23574b7fc74SAnton Potapov 
23674b7fc74SAnton Potapov         tbb::task_group_status inner_status = inner_tg.wait();
23774b7fc74SAnton Potapov         CHECK_MESSAGE(
23874b7fc74SAnton Potapov             inner_status == tbb::task_group_status::complete,
23974b7fc74SAnton Potapov             "Inner task group should have completed despite the cancellation of the outer one."
24074b7fc74SAnton Potapov         );
24174b7fc74SAnton Potapov 
24274b7fc74SAnton Potapov         CHECK_MESSAGE(0 == count, "Some of the inner group tasks were not executed.");
24374b7fc74SAnton Potapov     };
24474b7fc74SAnton Potapov 
24574b7fc74SAnton Potapov     outer_tg.run(outer_tg_task);
24674b7fc74SAnton Potapov     check();
24774b7fc74SAnton Potapov }
24874b7fc74SAnton Potapov 
24974b7fc74SAnton Potapov template <typename TaskGroup>
test()25074b7fc74SAnton Potapov void test() {
25174b7fc74SAnton Potapov     run_cancellation_use_case<TaskGroup>(
25274b7fc74SAnton Potapov         [](tbb::task_group& outer) { outer.cancel(); },
25374b7fc74SAnton Potapov         [](tbb::task_group& outer) { return outer.wait(); }
25474b7fc74SAnton Potapov     );
25574b7fc74SAnton Potapov 
25674b7fc74SAnton Potapov #if TBB_USE_EXCEPTIONS
25774b7fc74SAnton Potapov     run_cancellation_use_case<TaskGroup>(
25874b7fc74SAnton Potapov         [](tbb::task_group& /*outer*/) {
25974b7fc74SAnton Potapov             volatile bool suppress_unreachable_code_warning = true;
26074b7fc74SAnton Potapov             if (suppress_unreachable_code_warning) {
26174b7fc74SAnton Potapov                 throw int();
26274b7fc74SAnton Potapov             }
26374b7fc74SAnton Potapov         },
26474b7fc74SAnton Potapov         [](tbb::task_group& outer) {
26574b7fc74SAnton Potapov             try {
26674b7fc74SAnton Potapov                 outer.wait();
26774b7fc74SAnton Potapov                 return tbb::task_group_status::complete;
26874b7fc74SAnton Potapov             } catch(const int&) {
26974b7fc74SAnton Potapov                 return tbb::task_group_status::canceled;
27074b7fc74SAnton Potapov             }
27174b7fc74SAnton Potapov         }
27274b7fc74SAnton Potapov     );
27374b7fc74SAnton Potapov #endif
27474b7fc74SAnton Potapov }
27574b7fc74SAnton Potapov 
27674b7fc74SAnton Potapov } // namespace accept_task_group_context
27774b7fc74SAnton Potapov 
27874b7fc74SAnton Potapov //! Respect task_group_context passed from outside
27974b7fc74SAnton Potapov //! \brief \ref interface \ref requirement
28074b7fc74SAnton Potapov TEST_CASE("Respect task_group_context passed from outside") {
28174b7fc74SAnton Potapov     accept_task_group_context::test<tbb::task_group>();
28274b7fc74SAnton Potapov }
28374b7fc74SAnton Potapov 
284