1 /*
2     Copyright (c) 2005-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 "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
42     a.enqueue([&done] {
43         CHECK(oneapi::tbb::this_task_arena::max_concurrency() == 2);
44         done = true;
45     });
46     //! Execute interface
47     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;
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 
85     conformance_observer( oneapi::tbb::task_arena &a ) : oneapi::tbb::task_scheduler_observer(a) {
86         observe(true); // activate the observer
87     }
88 
89     void on_scheduler_entry(bool) override {
90         is_entry_called.store(true, std::memory_order_relaxed);
91     }
92 
93     void on_scheduler_exit(bool) override {
94         is_exit_called.store(true, std::memory_order_relaxed);
95     }
96 
97     bool is_callbacks_called() {
98         return is_entry_called.load(std::memory_order_relaxed)
99             && is_exit_called.load(std::memory_order_relaxed);
100     }
101 };
102 
103 //! Test task arena observer interfaces
104 //! \brief \ref requirement \ref interface
105 TEST_CASE("Task arena observer") {
106     oneapi::tbb::task_arena a; a.initialize();
107     conformance_observer observer(a);
108     a.execute([&] {
109         oneapi::tbb::parallel_for(0, 100, utils::DummyBody(10), oneapi::tbb::simple_partitioner());
110     });
111     REQUIRE(observer.is_callbacks_called());
112 }
113 
114 //! Test task arena copy constructor
115 //! \brief \ref interface \ref requirement
116 TEST_CASE("Task arena copy constructor") {
117     oneapi::tbb::task_arena arena(1);
118     oneapi::tbb::task_arena copy = arena;
119 
120     REQUIRE(arena.max_concurrency() == copy.max_concurrency());
121     REQUIRE(arena.is_active() == copy.is_active());
122 }
123 
124 
125 //! Basic test for arena::enqueue with task handle
126 //! \brief \ref interface \ref requirement
127 TEST_CASE("enqueue task_handle") {
128     oneapi::tbb::task_arena arena;
129     oneapi::tbb::task_group tg;
130 
131     //This flag is intentionally made non-atomic for Thread Sanitizer
132     //to raise a flag if implementation of task_group is incorrect
133     bool run{false};
134 
135     auto task_handle = tg.defer([&]{ run = true; });
136 
137     arena.enqueue(std::move(task_handle));
138     tg.wait();
139 
140     CHECK(run == true);
141 }
142 
143 //! Basic test for this_task_arena::enqueue with task handle
144 //! \brief \ref interface \ref requirement
145 TEST_CASE("this_task_arena::enqueue task_handle") {
146     oneapi::tbb::task_arena arena;
147     oneapi::tbb::task_group tg;
148 
149     //This flag is intentionally made non-atomic for Thread Sanitizer
150     //to raise a flag if implementation of task_group is incorrect
151     bool run{false};
152 
153     arena.execute([&]{
154         auto task_handle = tg.defer([&]{ run = true; });
155 
156         oneapi::tbb::this_task_arena::enqueue(std::move(task_handle));
157     });
158 
159     tg.wait();
160 
161     CHECK(run == true);
162 }
163 
164 //TODO: Add
165 //! Basic test for this_task_arena::enqueue with functor
166 
167 //! Test case for the common use-case of prolonging task_group lifetime
168 //! \brief \ref interface \ref requirement
169 TEST_CASE("this_task_arena::enqueue prolonging task_group") {
170     oneapi::tbb::task_arena arena;
171     oneapi::tbb::task_group tg;
172 
173     //This flag is intentionally made non-atomic for Thread Sanitizer
174     //to raise a flag if implementation of task_group is incorrect
175     bool run{false};
176 
177     //block the task_group to wait on it
178     auto task_handle = tg.defer([]{});
179 
180     arena.execute([&]{
181         oneapi::tbb::this_task_arena::enqueue([&]{
182             run = true;
183             //release the task_group
184             task_handle = oneapi::tbb::task_handle{};
185         });
186     });
187 
188     tg.wait();
189 
190     CHECK(run == true);
191 }
192 
193 #if TBB_USE_EXCEPTIONS
194 //! Basic test for exceptions in task_arena::enqueue with task_handle
195 //! \brief \ref interface \ref requirement
196 TEST_CASE("task_arena::enqueue(task_handle) exception propagation"){
197     oneapi::tbb::task_group tg;
198     oneapi::tbb::task_arena arena;
199 
200     oneapi::tbb::task_handle h = tg.defer([&]{
201         volatile bool suppress_unreachable_code_warning = true;
202         if (suppress_unreachable_code_warning) {
203             throw std::runtime_error{ "" };
204         }
205     });
206 
207     arena.enqueue(std::move(h));
208 
209     CHECK_THROWS_AS(tg.wait(), std::runtime_error);
210 }
211 
212 //! Basic test for exceptions in this_task_arena::enqueue with task_handle
213 //! \brief \ref interface \ref requirement
214 TEST_CASE("this_task_arena::enqueue(task_handle) exception propagation"){
215     oneapi::tbb::task_group tg;
216 
217     oneapi::tbb::task_handle h = tg.defer([&]{
218         volatile bool suppress_unreachable_code_warning = true;
219         if (suppress_unreachable_code_warning) {
220             throw std::runtime_error{ "" };
221         }
222     });
223 
224     oneapi::tbb::this_task_arena::enqueue(std::move(h));
225 
226     CHECK_THROWS_AS(tg.wait(), std::runtime_error);
227 }
228 
229 #endif // TBB_USE_EXCEPTIONS
230