blob: 744ea31c9f326bdade1039c826bfa7fd5cc253eb [file] [log] [blame]
Lingfeng Yangf6861062019-08-21 21:05:31 -07001// 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
24namespace android {
25namespace base {
26namespace guest {
27
28// Tests basic default construction/deconstruction.
29TEST(WorkPool, Basic) {
30 WorkPool p;
31}
32
33// Tests sending one task.
34TEST(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.
47TEST(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)
59TEST(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.
79TEST(WorkPool, WaitAny) {
80 WorkPool p;
81 int x = 0;
Lingfeng Yangcd2d8fe2019-08-16 12:21:50 -070082 WorkPool::WaitGroupHandle handle = 0;
Lingfeng Yangf6861062019-08-21 21:05:31 -070083
Lingfeng Yangcd2d8fe2019-08-16 12:21:50 -070084 {
85 std::vector<WorkPool::Task> tasks;
Lingfeng Yangf6861062019-08-21 21:05:31 -070086
Lingfeng Yangcd2d8fe2019-08-16 12:21:50 -070087 for (int i = 0; i < 8; ++i) {
88 tasks.push_back([&x] { ++x; });
89 }
90
91 handle = p.schedule(tasks);
Lingfeng Yangf6861062019-08-21 21:05:31 -070092 }
93
Lingfeng Yangf6861062019-08-21 21:05:31 -070094
95 p.waitAny(handle, -1);
96
97 EXPECT_GE(x, 1);
Lingfeng Yange11fd042019-10-15 10:07:08 -070098
99 // Prevent use after scope after test finish
100 p.waitAll(handle);
Lingfeng Yangf6861062019-08-21 21:05:31 -0700101}
102
103// Tests waitAll primitive; each worker increments the atomic int once,
104// so we expect it to end up at 8 (8 workers).
105TEST(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.
126TEST(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.
153TEST(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..
191TEST(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..
224TEST(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.
256TEST(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