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