1 /*
2     Copyright (c) 2005-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 "common/test.h"
18 #include "common/utils.h"
19 
20 #include "oneapi/tbb/task_arena.h"
21 #include "oneapi/tbb/task_scheduler_observer.h"
22 #include "oneapi/tbb/enumerable_thread_specific.h"
23 #include "oneapi/tbb/parallel_for.h"
24 
25 //! \file conformance_task_arena.cpp
26 //! \brief Test for [scheduler.task_arena scheduler.task_scheduler_observer] specification
27 
28 // This test requires TBB in an uninitialized state
29 //! Test for uninitilized arena
30 //! \brief \ref requirement \ref interface
31 TEST_CASE("Test current_thread_index") {
32     REQUIRE_MESSAGE((tbb::this_task_arena::current_thread_index() == tbb::task_arena::not_initialized), "TBB was initialized state");
33 }
34 
35 //! Test task arena interfaces
36 //! \brief \ref requirement \ref interface
37 TEST_CASE("Arena interfaces") {
38     //! Initialization interfaces
39     oneapi::tbb::task_arena a(1,1); a.initialize();
40     std::atomic<bool> done{ false };
41     //! Enqueue interface
__anon3e0b77410102null42     a.enqueue([&done] {
43         CHECK(oneapi::tbb::this_task_arena::max_concurrency() == 2);
44         done = true;
45     });
46     //! Execute interface
__anon3e0b77410202null47     a.execute([&] {
48         //! oneapi::tbb::this_task_arena interfaces
49         CHECK(oneapi::tbb::this_task_arena::current_thread_index() >= 0);
50         //! Attach interface
51         oneapi::tbb::task_arena attached_arena{oneapi::tbb::task_arena::attach()};
52         CHECK(attached_arena.is_active());
53         oneapi::tbb::task_arena attached_arena2{oneapi::tbb::attach()};
54         CHECK(attached_arena2.is_active());
55     });
56     while (!done) {
57         utils::yield();
58     }
59     //! Terminate interface
60     a.terminate();
61 }
62 
63 //! Test tasks isolation for inner oneapi::tbb::parallel_for loop
64 //! \brief \ref requirement \ref interface
65 TEST_CASE("Task isolation") {
66     const int N1 = 1000, N2 = 1000;
67     oneapi::tbb::enumerable_thread_specific<int> ets;
__anon3e0b77410302(int i) 68     oneapi::tbb::parallel_for(0, N1, [&](int i) {
69         // Set a thread specific value
70         ets.local() = i;
71         // Run the second parallel loop in an isolated region to prevent the current thread
72         // from taking tasks related to the outer parallel loop.
73         oneapi::tbb::this_task_arena::isolate([&]{
74             oneapi::tbb::parallel_for(0, N2, utils::DummyBody(10));
75         });
76         REQUIRE(ets.local() == i);
77     });
78 }
79 
80 class conformance_observer: public oneapi::tbb::task_scheduler_observer {
81 public:
82     std::atomic<bool> is_entry_called{false};
83     std::atomic<bool> is_exit_called{false};
84 
conformance_observer(oneapi::tbb::task_arena & a)85     conformance_observer( oneapi::tbb::task_arena &a ) : oneapi::tbb::task_scheduler_observer(a) {
86         observe(true); // activate the observer
87     }
88 
~conformance_observer()89     ~conformance_observer() {
90         observe(false);
91     }
92 
on_scheduler_entry(bool)93     void on_scheduler_entry(bool) override {
94         is_entry_called.store(true, std::memory_order_relaxed);
95     }
96 
on_scheduler_exit(bool)97     void on_scheduler_exit(bool) override {
98         is_exit_called.store(true, std::memory_order_relaxed);
99     }
100 
is_callbacks_called()101     bool is_callbacks_called() {
102         return is_entry_called.load(std::memory_order_relaxed)
103             && is_exit_called.load(std::memory_order_relaxed);
104     }
105 };
106 
107 //! Test task arena observer interfaces
108 //! \brief \ref requirement \ref interface
109 TEST_CASE("Task arena observer") {
110     oneapi::tbb::task_arena a; a.initialize();
111     conformance_observer observer(a);
__anon3e0b77410502null112     a.execute([&] {
113         oneapi::tbb::parallel_for(0, 100, utils::DummyBody(10), oneapi::tbb::simple_partitioner());
114     });
115     REQUIRE(observer.is_callbacks_called());
116 }
117 
118 //! Test task arena copy constructor
119 //! \brief \ref interface \ref requirement
120 TEST_CASE("Task arena copy constructor") {
121     oneapi::tbb::task_arena arena(1);
122     oneapi::tbb::task_arena copy = arena;
123 
124     REQUIRE(arena.max_concurrency() == copy.max_concurrency());
125     REQUIRE(arena.is_active() == copy.is_active());
126 }
127 
128 
129 //! Basic test for arena::enqueue with task handle
130 //! \brief \ref interface \ref requirement
131 TEST_CASE("enqueue task_handle") {
132     oneapi::tbb::task_arena arena;
133     oneapi::tbb::task_group tg;
134 
135     //This flag is intentionally made non-atomic for Thread Sanitizer
136     //to raise a flag if implementation of task_group is incorrect
137     bool run{false};
138 
__anon3e0b77410602null139     auto task_handle = tg.defer([&]{ run = true; });
140 
141     arena.enqueue(std::move(task_handle));
142     tg.wait();
143 
144     CHECK(run == true);
145 }
146 
147 //! Basic test for this_task_arena::enqueue with task handle
148 //! \brief \ref interface \ref requirement
149 TEST_CASE("this_task_arena::enqueue task_handle") {
150     oneapi::tbb::task_arena arena;
151     oneapi::tbb::task_group tg;
152 
153     //This flag is intentionally made non-atomic for Thread Sanitizer
154     //to raise a flag if implementation of task_group is incorrect
155     bool run{false};
156 
__anon3e0b77410702null157     arena.execute([&]{
158         auto task_handle = tg.defer([&]{ run = true; });
159 
160         oneapi::tbb::this_task_arena::enqueue(std::move(task_handle));
161     });
162 
163     tg.wait();
164 
165     CHECK(run == true);
166 }
167 
168 //TODO: Add
169 //! Basic test for this_task_arena::enqueue with functor
170 
171 //! Test case for the common use-case of prolonging task_group lifetime
172 //! \brief \ref interface \ref requirement
173 TEST_CASE("this_task_arena::enqueue prolonging task_group") {
174     oneapi::tbb::task_arena arena;
175     oneapi::tbb::task_group tg;
176 
177     //This flag is intentionally made non-atomic for Thread Sanitizer
178     //to raise a flag if implementation of task_group is incorrect
179     bool run{false};
180 
181     //block the task_group to wait on it
__anon3e0b77410902null182     auto task_handle = tg.defer([]{});
183 
__anon3e0b77410a02null184     arena.execute([&]{
185         oneapi::tbb::this_task_arena::enqueue([&]{
186             run = true;
187             //release the task_group
188             task_handle = oneapi::tbb::task_handle{};
189         });
190     });
191 
192     tg.wait();
193 
194     CHECK(run == true);
195 }
196 
197 #if TBB_USE_EXCEPTIONS
198 //! Basic test for exceptions in task_arena::enqueue with task_handle
199 //! \brief \ref interface \ref requirement
200 TEST_CASE("task_arena::enqueue(task_handle) exception propagation"){
201     oneapi::tbb::task_group tg;
202     oneapi::tbb::task_arena arena;
203 
__anon3e0b77410c02null204     oneapi::tbb::task_handle h = tg.defer([&]{
205         volatile bool suppress_unreachable_code_warning = true;
206         if (suppress_unreachable_code_warning) {
207             throw std::runtime_error{ "" };
208         }
209     });
210 
211     arena.enqueue(std::move(h));
212 
213     CHECK_THROWS_AS(tg.wait(), std::runtime_error);
214 }
215 
216 //! Basic test for exceptions in this_task_arena::enqueue with task_handle
217 //! \brief \ref interface \ref requirement
218 TEST_CASE("this_task_arena::enqueue(task_handle) exception propagation"){
219     oneapi::tbb::task_group tg;
220 
__anon3e0b77410d02null221     oneapi::tbb::task_handle h = tg.defer([&]{
222         volatile bool suppress_unreachable_code_warning = true;
223         if (suppress_unreachable_code_warning) {
224             throw std::runtime_error{ "" };
225         }
226     });
227 
228     oneapi::tbb::this_task_arena::enqueue(std::move(h));
229 
230     CHECK_THROWS_AS(tg.wait(), std::runtime_error);
231 }
232 
233 #endif // TBB_USE_EXCEPTIONS
234