update FunctionScheduler to use std::chrono::steady_clock
[folly.git] / folly / experimental / test / FunctionSchedulerTest.cpp
1 /*
2  * Copyright 2015 Facebook, 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  */
16 #include <folly/experimental/FunctionScheduler.h>
17
18 #include <atomic>
19 #include <gtest/gtest.h>
20
21 using namespace folly;
22 using std::chrono::milliseconds;
23
24 namespace {
25
26 /*
27  * Helper functions for controlling how long this test takes.
28  *
29  * Using larger intervals here will make the tests less flaky when run on
30  * heavily loaded systems.  However, this will also make the tests take longer
31  * to run.
32  */
33 static const auto timeFactor = std::chrono::milliseconds(100);
34 std::chrono::milliseconds testInterval(int n) {
35   return n * timeFactor;
36 }
37 void delay(int n) {
38   std::chrono::microseconds usec(n * timeFactor);
39   usleep(usec.count());
40 }
41
42 } // unnamed namespace
43
44 TEST(FunctionScheduler, SimpleAdd) {
45   int total = 0;
46   FunctionScheduler fs;
47   fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
48   fs.start();
49   delay(1);
50   EXPECT_EQ(2, total);
51   fs.shutdown();
52   delay(2);
53   EXPECT_EQ(2, total);
54 }
55
56 TEST(FunctionScheduler, AddCancel) {
57   int total = 0;
58   FunctionScheduler fs;
59   fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
60   fs.start();
61   delay(1);
62   EXPECT_EQ(2, total);
63   delay(2);
64   EXPECT_EQ(4, total);
65   EXPECT_TRUE(fs.cancelFunction("add2"));
66   EXPECT_FALSE(fs.cancelFunction("NO SUCH FUNC"));
67   delay(2);
68   EXPECT_EQ(4, total);
69   fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
70   EXPECT_FALSE(fs.start()); // already running
71   delay(1);
72   EXPECT_EQ(5, total);
73   delay(2);
74   EXPECT_EQ(6, total);
75   fs.shutdown();
76 }
77
78 TEST(FunctionScheduler, AddCancel2) {
79   int total = 0;
80   FunctionScheduler fs;
81
82   // Test adds and cancels while the scheduler is stopped
83   EXPECT_FALSE(fs.cancelFunction("add2"));
84   fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
85   EXPECT_TRUE(fs.cancelFunction("add2"));
86   EXPECT_FALSE(fs.cancelFunction("add2"));
87   fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
88   fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
89
90   EXPECT_EQ(0, total);
91   fs.start();
92   delay(1);
93   EXPECT_EQ(5, total);
94
95   // Cancel add2 while the scheduler is running
96   EXPECT_TRUE(fs.cancelFunction("add2"));
97   EXPECT_FALSE(fs.cancelFunction("add2"));
98   EXPECT_FALSE(fs.cancelFunction("bogus"));
99
100   delay(3);
101   EXPECT_EQ(8, total);
102   EXPECT_TRUE(fs.cancelFunction("add3"));
103
104   // Test a function that cancels itself
105   int selfCancelCount = 0;
106   fs.addFunction(
107       [&] {
108         ++selfCancelCount;
109         if (selfCancelCount > 2) {
110           fs.cancelFunction("selfCancel");
111         }
112       },
113       testInterval(1), "selfCancel", testInterval(1));
114   delay(4);
115   EXPECT_EQ(3, selfCancelCount);
116   EXPECT_FALSE(fs.cancelFunction("selfCancel"));
117
118   // Test a function that schedules another function
119   int adderCount = 0;
120   int fn2Count = 0;
121   auto fn2 = [&] { ++fn2Count; };
122   auto fnAdder = [&] {
123     ++adderCount;
124     if (adderCount == 2) {
125       fs.addFunction(fn2, testInterval(3), "fn2", testInterval(2));
126     }
127   };
128   fs.addFunction(fnAdder, testInterval(4), "adder");
129   // t0: adder fires
130   delay(1); // t1
131   EXPECT_EQ(1, adderCount);
132   EXPECT_EQ(0, fn2Count);
133   // t4: adder fires, schedules fn2
134   delay(4); // t5
135   EXPECT_EQ(2, adderCount);
136   EXPECT_EQ(0, fn2Count);
137   // t6: fn2 fires
138   delay(2); // t7
139   EXPECT_EQ(2, adderCount);
140   EXPECT_EQ(1, fn2Count);
141   // t8: adder fires
142   // t9: fn2 fires
143   delay(3); // t10
144   EXPECT_EQ(3, adderCount);
145   EXPECT_EQ(2, fn2Count);
146   EXPECT_TRUE(fs.cancelFunction("fn2"));
147   EXPECT_TRUE(fs.cancelFunction("adder"));
148   delay(5); // t10
149   EXPECT_EQ(3, adderCount);
150   EXPECT_EQ(2, fn2Count);
151
152   EXPECT_EQ(8, total);
153   EXPECT_EQ(3, selfCancelCount);
154 }
155
156 TEST(FunctionScheduler, AddMultiple) {
157   int total = 0;
158   FunctionScheduler fs;
159   fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
160   fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
161   EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(2), "add2"),
162                std::invalid_argument); // function name already exists
163
164   fs.start();
165   delay(1);
166   EXPECT_EQ(5, total);
167   delay(4);
168   EXPECT_EQ(12, total);
169   EXPECT_TRUE(fs.cancelFunction("add2"));
170   delay(2);
171   EXPECT_EQ(15, total);
172   fs.shutdown();
173   delay(3);
174   EXPECT_EQ(15, total);
175   fs.shutdown();
176 }
177
178 TEST(FunctionScheduler, AddAfterStart) {
179   int total = 0;
180   FunctionScheduler fs;
181   fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
182   fs.addFunction([&] { total += 3; }, testInterval(2), "add3");
183   fs.start();
184   delay(3);
185   EXPECT_EQ(10, total);
186   fs.addFunction([&] { total += 2; }, testInterval(3), "add22");
187   delay(2);
188   EXPECT_EQ(17, total);
189 }
190
191 TEST(FunctionScheduler, ShutdownStart) {
192   int total = 0;
193   FunctionScheduler fs;
194   fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
195   fs.start();
196   delay(1);
197   fs.shutdown();
198   fs.start();
199   delay(1);
200   EXPECT_EQ(4, total);
201   EXPECT_FALSE(fs.cancelFunction("add3")); // non existing
202   delay(2);
203   EXPECT_EQ(6, total);
204 }
205
206 TEST(FunctionScheduler, AddInvalid) {
207   int total = 0;
208   FunctionScheduler fs;
209   // interval may not be negative
210   EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(-1), "add2"),
211                std::invalid_argument);
212
213   EXPECT_FALSE(fs.cancelFunction("addNoFunc"));
214 }
215
216 TEST(FunctionScheduler, NoFunctions) {
217   FunctionScheduler fs;
218   EXPECT_TRUE(fs.start());
219   fs.shutdown();
220   FunctionScheduler fs2;
221   fs2.shutdown();
222 }
223
224 TEST(FunctionScheduler, AddWhileRunning) {
225   int total = 0;
226   FunctionScheduler fs;
227   fs.start();
228   delay(1);
229   fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
230   // The function should be invoked nearly immediately when we add it
231   // and the FunctionScheduler is already running
232   usleep(50000);
233   EXPECT_EQ(2, total);
234   delay(2);
235   EXPECT_EQ(4, total);
236 }
237
238 TEST(FunctionScheduler, NoShutdown) {
239   int total = 0;
240   {
241     FunctionScheduler fs;
242     fs.addFunction([&] { total += 2; }, testInterval(1), "add2");
243     fs.start();
244     usleep(50000);
245     EXPECT_EQ(2, total);
246   }
247   // Destroyed the FunctionScheduler without calling shutdown.
248   // Everything should have been cleaned up, and the function will no longer
249   // get called.
250   delay(2);
251   EXPECT_EQ(2, total);
252 }
253
254 TEST(FunctionScheduler, StartDelay) {
255   int total = 0;
256   FunctionScheduler fs;
257   fs.addFunction([&] { total += 2; }, testInterval(2), "add2",
258                  testInterval(2));
259   fs.addFunction([&] { total += 3; }, testInterval(3), "add3",
260                  testInterval(2));
261   EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(3),
262                               "addX", testInterval(-1)),
263                std::invalid_argument);
264   fs.start();
265   delay(1); // t1
266   EXPECT_EQ(0, total);
267   // t2 : add2 total=2
268   // t2 : add3 total=5
269   delay(2); // t3
270   EXPECT_EQ(5, total);
271   // t4 : add2: total=7
272   // t5 : add3: total=10
273   // t6 : add2: total=12
274   delay(4); // t7
275   EXPECT_EQ(12, total);
276   fs.cancelFunction("add2");
277   // t8 : add3: total=15
278   delay(2); // t9
279   EXPECT_EQ(15, total);
280   fs.shutdown();
281   delay(3);
282   EXPECT_EQ(15, total);
283   fs.shutdown();
284 }
285
286 TEST(FunctionScheduler, NoSteadyCatchup) {
287   std::atomic<int> ticks(0);
288   FunctionScheduler fs;
289   // fs.setSteady(false); is the default
290   fs.addFunction([&ticks] {
291                    if (++ticks == 2) {
292                      std::this_thread::sleep_for(
293                          std::chrono::milliseconds(200));
294                    }
295                  },
296                  milliseconds(5));
297   fs.start();
298   std::this_thread::sleep_for(std::chrono::milliseconds(500));
299
300   // no steady catch up means we'd tick once for 200ms, then remaining
301   // 300ms / 5 = 60 times
302   EXPECT_LE(ticks.load(), 61);
303 }
304
305 TEST(FunctionScheduler, SteadyCatchup) {
306   std::atomic<int> ticks(0);
307   FunctionScheduler fs;
308   fs.setSteady(true);
309   fs.addFunction([&ticks] {
310                    if (++ticks == 2) {
311                      std::this_thread::sleep_for(
312                          std::chrono::milliseconds(200));
313                    }
314                  },
315                  milliseconds(5));
316   fs.start();
317
318   std::this_thread::sleep_for(std::chrono::milliseconds(500));
319
320   // tick every 5ms. Despite tick == 2 is slow, later ticks should be fast
321   // enough to catch back up to schedule
322   EXPECT_NEAR(100, ticks.load(), 10);
323 }