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     });
54     while (!done) {
55         utils::yield();
56     }
57     //! Terminate interface
58     a.terminate();
59 }
60 
61 //! Test tasks isolation for inner oneapi::tbb::parallel_for loop
62 //! \brief \ref requirement \ref interface
63 TEST_CASE("Task isolation") {
64     const int N1 = 1000, N2 = 1000;
65     oneapi::tbb::enumerable_thread_specific<int> ets;
66     oneapi::tbb::parallel_for(0, N1, [&](int i) {
67         // Set a thread specific value
68         ets.local() = i;
69         // Run the second parallel loop in an isolated region to prevent the current thread
70         // from taking tasks related to the outer parallel loop.
71         oneapi::tbb::this_task_arena::isolate([&]{
72             oneapi::tbb::parallel_for(0, N2, utils::DummyBody(10));
73         });
74         REQUIRE(ets.local() == i);
75     });
76 }
77 
78 class conformance_observer: public oneapi::tbb::task_scheduler_observer {
79 public:
80     std::atomic<bool> is_entry_called{false};
81     std::atomic<bool> is_exit_called{false};
82 
83     conformance_observer( oneapi::tbb::task_arena &a ) : oneapi::tbb::task_scheduler_observer(a) {
84         observe(true); // activate the observer
85     }
86 
87     void on_scheduler_entry(bool) override {
88         is_entry_called.store(true, std::memory_order_relaxed);
89     }
90 
91     void on_scheduler_exit(bool) override {
92         is_exit_called.store(true, std::memory_order_relaxed);
93     }
94 
95     bool is_callbacks_called() {
96         return is_entry_called.load(std::memory_order_relaxed)
97             && is_exit_called.load(std::memory_order_relaxed);
98     }
99 };
100 
101 //! Test task arena observer interfaces
102 //! \brief \ref requirement \ref interface
103 TEST_CASE("Task arena observer") {
104     oneapi::tbb::task_arena a; a.initialize();
105     conformance_observer observer(a);
106     a.execute([&] {
107         oneapi::tbb::parallel_for(0, 100, utils::DummyBody(10), oneapi::tbb::simple_partitioner());
108     });
109     REQUIRE(observer.is_callbacks_called());
110 }
111 
112 //! Test task arena copy constructor
113 //! \brief \ref interface \ref requirement
114 TEST_CASE("Task arena copy constructor") {
115     oneapi::tbb::task_arena arena(1);
116     oneapi::tbb::task_arena copy = arena;
117 
118     REQUIRE(arena.max_concurrency() == copy.max_concurrency());
119     REQUIRE(arena.is_active() == copy.is_active());
120 }
121 
122 
123 //! Basic test for arena::enqueue with task handle
124 //! \brief \ref interface \ref requirement
125 TEST_CASE("enqueue task_handle") {
126     oneapi::tbb::task_arena arena;
127     oneapi::tbb::task_group tg;
128 
129     //This flag is intentionally made non-atomic for Thread Sanitizer
130     //to raise a flag if implementation of task_group is incorrect
131     bool run{false};
132 
133     auto task_handle = tg.defer([&]{ run = true; });
134 
135     arena.enqueue(std::move(task_handle));
136     tg.wait();
137 
138     CHECK(run == true);
139 }
140 
141 //! Basic test for this_task_arena::enqueue with task handle
142 //! \brief \ref interface \ref requirement
143 TEST_CASE("this_task_arena::enqueue task_handle") {
144     oneapi::tbb::task_arena arena;
145     oneapi::tbb::task_group tg;
146 
147     //This flag is intentionally made non-atomic for Thread Sanitizer
148     //to raise a flag if implementation of task_group is incorrect
149     bool run{false};
150 
151     arena.execute([&]{
152         auto task_handle = tg.defer([&]{ run = true; });
153 
154         oneapi::tbb::this_task_arena::enqueue(std::move(task_handle));
155     });
156 
157     tg.wait();
158 
159     CHECK(run == true);
160 }
161 
162 //TODO: Add
163 //! Basic test for this_task_arena::enqueue with functor
164 
165 //! Test case for the common use-case of prolonging task_group lifetime
166 //! \brief \ref interface \ref requirement
167 TEST_CASE("this_task_arena::enqueue prolonging task_group") {
168     oneapi::tbb::task_arena arena;
169     oneapi::tbb::task_group tg;
170 
171     //This flag is intentionally made non-atomic for Thread Sanitizer
172     //to raise a flag if implementation of task_group is incorrect
173     bool run{false};
174 
175     //block the task_group to wait on it
176     auto task_handle = tg.defer([]{});
177 
178     arena.execute([&]{
179         oneapi::tbb::this_task_arena::enqueue([&]{
180             run = true;
181             //release the task_group
182             task_handle = oneapi::tbb::task_handle{};
183         });
184     });
185 
186     tg.wait();
187 
188     CHECK(run == true);
189 }
190 
191 #if TBB_USE_EXCEPTIONS
192 //! Basic test for exceptions in task_arena::enqueue with task_handle
193 //! \brief \ref interface \ref requirement
194 TEST_CASE("task_arena::enqueue(task_handle) exception propagation"){
195     oneapi::tbb::task_group tg;
196     oneapi::tbb::task_arena arena;
197 
198     oneapi::tbb::task_handle h = tg.defer([&]{
199         volatile bool suppress_unreachable_code_warning = true;
200         if (suppress_unreachable_code_warning) {
201             throw std::runtime_error{ "" };
202         }
203     });
204 
205     arena.enqueue(std::move(h));
206 
207     CHECK_THROWS_AS(tg.wait(), std::runtime_error);
208 }
209 
210 //! Basic test for exceptions in this_task_arena::enqueue with task_handle
211 //! \brief \ref interface \ref requirement
212 TEST_CASE("this_task_arena::enqueue(task_handle) exception propagation"){
213     oneapi::tbb::task_group tg;
214 
215     oneapi::tbb::task_handle h = tg.defer([&]{
216         volatile bool suppress_unreachable_code_warning = true;
217         if (suppress_unreachable_code_warning) {
218             throw std::runtime_error{ "" };
219         }
220     });
221 
222     oneapi::tbb::this_task_arena::enqueue(std::move(h));
223 
224     CHECK_THROWS_AS(tg.wait(), std::runtime_error);
225 }
226 
227 #endif // TBB_USE_EXCEPTIONS
228