1 // Copyright (C) 2019 The Android Open Source Project
2 // Copyright (C) 2019 Google Inc.
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 #include <gtest/gtest.h>
16
17 #include "android/base/synchronization/AndroidConditionVariable.h"
18 #include "android/base/synchronization/AndroidLock.h"
19 #include "android/base/threads/AndroidWorkPool.h"
20
21 #include <atomic>
22 #include <vector>
23
24 namespace android {
25 namespace base {
26 namespace guest {
27
28 // Tests basic default construction/deconstruction.
TEST(WorkPool,Basic)29 TEST(WorkPool, Basic) {
30 WorkPool p;
31 }
32
33 // Tests sending one task.
TEST(WorkPool,One)34 TEST(WorkPool, One) {
35 WorkPool p;
36
37 WorkPool::Task task = [] {
38 fprintf(stderr, "do something\n");
39 };
40
41 std::vector<WorkPool::Task> tasks { task };
42
43 p.schedule(tasks);
44 }
45
46 // Tests sending two tasks.
TEST(WorkPool,Two)47 TEST(WorkPool, Two) {
48 WorkPool p;
49
50 std::vector<WorkPool::Task> tasks {
51 [] { fprintf(stderr, "do something 1\n"); },
52 [] { fprintf(stderr, "do something 2\n"); },
53 };
54
55 p.schedule(tasks);
56 }
57
58 // Tests sending eight tasks (can require spawning more threads)
TEST(WorkPool,Eight)59 TEST(WorkPool, Eight) {
60 WorkPool p;
61
62 std::vector<WorkPool::Task> tasks {
63 [] { fprintf(stderr, "do something 1\n"); },
64 [] { fprintf(stderr, "do something 2\n"); },
65 [] { fprintf(stderr, "do something 3\n"); },
66 [] { fprintf(stderr, "do something 4\n"); },
67 [] { fprintf(stderr, "do something 5\n"); },
68 [] { fprintf(stderr, "do something 6\n"); },
69 [] { fprintf(stderr, "do something 7\n"); },
70 [] { fprintf(stderr, "do something 8\n"); },
71 };
72
73 p.schedule(tasks);
74 }
75
76 // Tests waitAny primitive; if at least one of the tasks successfully run,
77 // at least one of them will read 0 and store back 1 in |x|, or more,
78 // so check that x >= 1.
TEST(WorkPool,WaitAny)79 TEST(WorkPool, WaitAny) {
80 WorkPool p;
81 int x = 0;
82 WorkPool::WaitGroupHandle handle = 0;
83
84 {
85 std::vector<WorkPool::Task> tasks;
86
87 for (int i = 0; i < 8; ++i) {
88 tasks.push_back([&x] { ++x; });
89 }
90
91 handle = p.schedule(tasks);
92 }
93
94
95 p.waitAny(handle, -1);
96
97 EXPECT_GE(x, 1);
98
99 // Prevent use after scope after test finish
100 p.waitAll(handle);
101 }
102
103 // Tests waitAll primitive; each worker increments the atomic int once,
104 // so we expect it to end up at 8 (8 workers).
TEST(WorkPool,WaitAll)105 TEST(WorkPool, WaitAll) {
106 WorkPool p;
107 std::atomic<int> x { 0 };
108
109 std::vector<WorkPool::Task> tasks;
110
111 for (int i = 0; i < 8; ++i) {
112 tasks.push_back([&x] { ++x; });
113 }
114
115 auto handle = p.schedule(tasks);
116
117 p.waitAll(handle, -1);
118
119 EXPECT_EQ(x, 8);
120 }
121
122 // Tests waitAll primitive with two concurrent wait groups in flight.
123 // The second wait group is scheduled after the first, but
124 // we wait on the second wait group first. This is to ensure that
125 // order of submission does not enforce order of waiting / completion.
TEST(WorkPool,WaitAllTwoWaitGroups)126 TEST(WorkPool, WaitAllTwoWaitGroups) {
127 WorkPool p;
128 std::atomic<int> x { 0 };
129 std::atomic<int> y { 0 };
130
131 std::vector<WorkPool::Task> tasks1;
132 std::vector<WorkPool::Task> tasks2;
133
134 for (int i = 0; i < 8; ++i) {
135 tasks1.push_back([&x] { ++x; });
136 tasks2.push_back([&y] { ++y; });
137 }
138
139 auto handle1 = p.schedule(tasks1);
140 auto handle2 = p.schedule(tasks2);
141
142 p.waitAll(handle2, -1);
143 p.waitAll(handle1, -1);
144
145 EXPECT_EQ(x, 8);
146 EXPECT_EQ(y, 8);
147 }
148
149 // Tests waitAll primitive with two concurrent wait groups.
150 // The first wait group waits on what the second wait group will signal.
151 // This is to ensure that we can send blocking tasks to WorkPool
152 // without causing a deadlock.
TEST(WorkPool,WaitAllWaitSignal)153 TEST(WorkPool, WaitAllWaitSignal) {
154 WorkPool p;
155 Lock lock;
156 ConditionVariable cv;
157 // Similar to a timeline semaphore object;
158 // one process waits on a particular value to get reached,
159 // while other processes gradually increment it.
160 std::atomic<int> x { 0 };
161
162 std::vector<WorkPool::Task> tasks1 = {
163 [&lock, &cv, &x] {
164 AutoLock l(lock);
165 while (x < 8) {
166 cv.wait(&lock);
167 }
168 },
169 };
170
171 std::vector<WorkPool::Task> tasks2;
172
173 for (int i = 0; i < 8; ++i) {
174 tasks2.push_back([&lock, &cv, &x] {
175 AutoLock l(lock);
176 ++x;
177 cv.signal();
178 });
179 }
180
181 auto handle1 = p.schedule(tasks1);
182 auto handle2 = p.schedule(tasks2);
183
184 p.waitAll(handle1, -1);
185
186 EXPECT_EQ(8, x);
187 }
188
189 // Tests waitAll primitive with some kind of timeout.
190 // We don't expect x to be anything in particular..
TEST(WorkPool,WaitAllTimeout)191 TEST(WorkPool, WaitAllTimeout) {
192 WorkPool p;
193 Lock lock;
194 ConditionVariable cv;
195 std::atomic<int> x { 0 };
196
197 std::vector<WorkPool::Task> tasks1 = {
198 [&lock, &cv, &x] {
199 AutoLock l(lock);
200 while (x < 8) {
201 cv.wait(&lock);
202 }
203 },
204 };
205
206 std::vector<WorkPool::Task> tasks2;
207
208 for (int i = 0; i < 8; ++i) {
209 tasks2.push_back([&lock, &cv, &x] {
210 AutoLock l(lock);
211 ++x;
212 cv.signal();
213 });
214 }
215
216 auto handle1 = p.schedule(tasks1);
217 auto handle2 = p.schedule(tasks2);
218
219 p.waitAll(handle1, 10);
220 }
221
222 // Tests waitAny primitive with some kind of timeout.
223 // We don't expect x to be anything in particular..
TEST(WorkPool,WaitAnyTimeout)224 TEST(WorkPool, WaitAnyTimeout) {
225 WorkPool p;
226 Lock lock;
227 ConditionVariable cv;
228 std::atomic<int> x { 0 };
229
230 std::vector<WorkPool::Task> tasks1 = {
231 [&lock, &cv, &x] {
232 AutoLock l(lock);
233 while (x < 8) {
234 cv.wait(&lock);
235 }
236 },
237 };
238
239 std::vector<WorkPool::Task> tasks2;
240
241 for (int i = 0; i < 8; ++i) {
242 tasks2.push_back([&lock, &cv, &x] {
243 AutoLock l(lock);
244 ++x;
245 cv.signal();
246 });
247 }
248
249 auto handle1 = p.schedule(tasks1);
250 auto handle2 = p.schedule(tasks2);
251
252 p.waitAny(handle1, 10);
253 }
254
255 // Nesting waitAll inside another task.
TEST(WorkPool,NestedWaitAll)256 TEST(WorkPool, NestedWaitAll) {
257 WorkPool p;
258 std::atomic<int> x { 0 };
259 std::atomic<int> y { 0 };
260
261 std::vector<WorkPool::Task> tasks1;
262
263 for (int i = 0; i < 8; ++i) {
264 tasks1.push_back([&x] {
265 ++x;
266 });
267 }
268
269 auto waitGroupHandle = p.schedule(tasks1);
270
271 std::vector<WorkPool::Task> tasks2 = {
272 [&p, waitGroupHandle, &x, &y] {
273 p.waitAll(waitGroupHandle);
274 EXPECT_EQ(8, x);
275 ++y;
276 },
277 };
278
279 auto handle2 = p.schedule(tasks2);
280
281 p.waitAll(handle2);
282
283 EXPECT_EQ(1, y);
284 }
285
286 } // namespace android
287 } // namespace base
288 } // namespace guest
289