blob: 11cbfbd7a47ebd560e5d306975180bd41c7fa878 [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;
82
83 std::vector<WorkPool::Task> tasks;
84
85 for (int i = 0; i < 8; ++i) {
86 tasks.push_back([&x] { ++x; });
87 }
88
89 auto handle = p.schedule(tasks);
90
91 p.waitAny(handle, -1);
92
93 EXPECT_GE(x, 1);
94}
95
96// Tests waitAll primitive; each worker increments the atomic int once,
97// so we expect it to end up at 8 (8 workers).
98TEST(WorkPool, WaitAll) {
99 WorkPool p;
100 std::atomic<int> x { 0 };
101
102 std::vector<WorkPool::Task> tasks;
103
104 for (int i = 0; i < 8; ++i) {
105 tasks.push_back([&x] { ++x; });
106 }
107
108 auto handle = p.schedule(tasks);
109
110 p.waitAll(handle, -1);
111
112 EXPECT_EQ(x, 8);
113}
114
115// Tests waitAll primitive with two concurrent wait groups in flight.
116// The second wait group is scheduled after the first, but
117// we wait on the second wait group first. This is to ensure that
118// order of submission does not enforce order of waiting / completion.
119TEST(WorkPool, WaitAllTwoWaitGroups) {
120 WorkPool p;
121 std::atomic<int> x { 0 };
122 std::atomic<int> y { 0 };
123
124 std::vector<WorkPool::Task> tasks1;
125 std::vector<WorkPool::Task> tasks2;
126
127 for (int i = 0; i < 8; ++i) {
128 tasks1.push_back([&x] { ++x; });
129 tasks2.push_back([&y] { ++y; });
130 }
131
132 auto handle1 = p.schedule(tasks1);
133 auto handle2 = p.schedule(tasks2);
134
135 p.waitAll(handle2, -1);
136 p.waitAll(handle1, -1);
137
138 EXPECT_EQ(x, 8);
139 EXPECT_EQ(y, 8);
140}
141
142// Tests waitAll primitive with two concurrent wait groups.
143// The first wait group waits on what the second wait group will signal.
144// This is to ensure that we can send blocking tasks to WorkPool
145// without causing a deadlock.
146TEST(WorkPool, WaitAllWaitSignal) {
147 WorkPool p;
148 Lock lock;
149 ConditionVariable cv;
150 // Similar to a timeline semaphore object;
151 // one process waits on a particular value to get reached,
152 // while other processes gradually increment it.
153 std::atomic<int> x { 0 };
154
155 std::vector<WorkPool::Task> tasks1 = {
156 [&lock, &cv, &x] {
157 AutoLock l(lock);
158 while (x < 8) {
159 cv.wait(&lock);
160 }
161 },
162 };
163
164 std::vector<WorkPool::Task> tasks2;
165
166 for (int i = 0; i < 8; ++i) {
167 tasks2.push_back([&lock, &cv, &x] {
168 AutoLock l(lock);
169 ++x;
170 cv.signal();
171 });
172 }
173
174 auto handle1 = p.schedule(tasks1);
175 auto handle2 = p.schedule(tasks2);
176
177 p.waitAll(handle1, -1);
178
179 EXPECT_EQ(8, x);
180}
181
182// Tests waitAll primitive with some kind of timeout.
183// We don't expect x to be anything in particular..
184TEST(WorkPool, WaitAllTimeout) {
185 WorkPool p;
186 Lock lock;
187 ConditionVariable cv;
188 std::atomic<int> x { 0 };
189
190 std::vector<WorkPool::Task> tasks1 = {
191 [&lock, &cv, &x] {
192 AutoLock l(lock);
193 while (x < 8) {
194 cv.wait(&lock);
195 }
196 },
197 };
198
199 std::vector<WorkPool::Task> tasks2;
200
201 for (int i = 0; i < 8; ++i) {
202 tasks2.push_back([&lock, &cv, &x] {
203 AutoLock l(lock);
204 ++x;
205 cv.signal();
206 });
207 }
208
209 auto handle1 = p.schedule(tasks1);
210 auto handle2 = p.schedule(tasks2);
211
212 p.waitAll(handle1, 10);
213}
214
215// Tests waitAny primitive with some kind of timeout.
216// We don't expect x to be anything in particular..
217TEST(WorkPool, WaitAnyTimeout) {
218 WorkPool p;
219 Lock lock;
220 ConditionVariable cv;
221 std::atomic<int> x { 0 };
222
223 std::vector<WorkPool::Task> tasks1 = {
224 [&lock, &cv, &x] {
225 AutoLock l(lock);
226 while (x < 8) {
227 cv.wait(&lock);
228 }
229 },
230 };
231
232 std::vector<WorkPool::Task> tasks2;
233
234 for (int i = 0; i < 8; ++i) {
235 tasks2.push_back([&lock, &cv, &x] {
236 AutoLock l(lock);
237 ++x;
238 cv.signal();
239 });
240 }
241
242 auto handle1 = p.schedule(tasks1);
243 auto handle2 = p.schedule(tasks2);
244
245 p.waitAny(handle1, 10);
246}
247
248// Nesting waitAll inside another task.
249TEST(WorkPool, NestedWaitAll) {
250 WorkPool p;
251 std::atomic<int> x { 0 };
252 std::atomic<int> y { 0 };
253
254 std::vector<WorkPool::Task> tasks1;
255
256 for (int i = 0; i < 8; ++i) {
257 tasks1.push_back([&x] {
258 ++x;
259 });
260 }
261
262 auto waitGroupHandle = p.schedule(tasks1);
263
264 std::vector<WorkPool::Task> tasks2 = {
265 [&p, waitGroupHandle, &x, &y] {
266 p.waitAll(waitGroupHandle);
267 EXPECT_EQ(8, x);
268 ++y;
269 },
270 };
271
272 auto handle2 = p.schedule(tasks2);
273
274 p.waitAll(handle2);
275
276 EXPECT_EQ(1, y);
277}
278
279} // namespace android
280} // namespace base
281} // namespace guest