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