2 * Copyright 2016 Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 #include <folly/experimental/FunctionScheduler.h>
22 #include <folly/Random.h>
24 #include <folly/portability/GTest.h>
26 using namespace folly;
27 using std::chrono::milliseconds;
32 * Helper functions for controlling how long this test takes.
34 * Using larger intervals here will make the tests less flaky when run on
35 * heavily loaded systems. However, this will also make the tests take longer
38 static const auto timeFactor = std::chrono::milliseconds(100);
39 std::chrono::milliseconds testInterval(int n) { return n * timeFactor; }
40 int getTicksWithinRange(int n, int min, int max) {
47 std::chrono::microseconds usec(n * timeFactor);
51 } // unnamed namespace
53 TEST(FunctionScheduler, StartAndShutdown) {
55 EXPECT_TRUE(fs.start());
56 EXPECT_FALSE(fs.start());
57 EXPECT_TRUE(fs.shutdown());
58 EXPECT_FALSE(fs.shutdown());
60 EXPECT_TRUE(fs.start());
61 EXPECT_FALSE(fs.start());
62 EXPECT_TRUE(fs.shutdown());
63 EXPECT_FALSE(fs.shutdown());
66 TEST(FunctionScheduler, SimpleAdd) {
69 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
78 TEST(FunctionScheduler, AddCancel) {
81 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
87 EXPECT_TRUE(fs.cancelFunction("add2"));
88 EXPECT_FALSE(fs.cancelFunction("NO SUCH FUNC"));
91 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
99 TEST(FunctionScheduler, AddCancel2) {
101 FunctionScheduler fs;
103 // Test adds and cancels while the scheduler is stopped
104 EXPECT_FALSE(fs.cancelFunction("add2"));
105 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
106 EXPECT_TRUE(fs.cancelFunction("add2"));
107 EXPECT_FALSE(fs.cancelFunction("add2"));
108 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
109 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
116 // Cancel add2 while the scheduler is running
117 EXPECT_TRUE(fs.cancelFunction("add2"));
118 EXPECT_FALSE(fs.cancelFunction("add2"));
119 EXPECT_FALSE(fs.cancelFunction("bogus"));
123 EXPECT_TRUE(fs.cancelFunction("add3"));
125 // Test a function that cancels itself
126 int selfCancelCount = 0;
130 if (selfCancelCount > 2) {
131 fs.cancelFunction("selfCancel");
134 testInterval(1), "selfCancel", testInterval(1));
136 EXPECT_EQ(3, selfCancelCount);
137 EXPECT_FALSE(fs.cancelFunction("selfCancel"));
139 // Test a function that schedules another function
142 auto fn2 = [&] { ++fn2Count; };
145 if (adderCount == 2) {
146 fs.addFunction(fn2, testInterval(3), "fn2", testInterval(2));
149 fs.addFunction(fnAdder, testInterval(4), "adder");
152 EXPECT_EQ(1, adderCount);
153 EXPECT_EQ(0, fn2Count);
154 // t4: adder fires, schedules fn2
156 EXPECT_EQ(2, adderCount);
157 EXPECT_EQ(0, fn2Count);
160 EXPECT_EQ(2, adderCount);
161 EXPECT_EQ(1, fn2Count);
165 EXPECT_EQ(3, adderCount);
166 EXPECT_EQ(2, fn2Count);
167 EXPECT_TRUE(fs.cancelFunction("fn2"));
168 EXPECT_TRUE(fs.cancelFunction("adder"));
170 EXPECT_EQ(3, adderCount);
171 EXPECT_EQ(2, fn2Count);
174 EXPECT_EQ(3, selfCancelCount);
177 TEST(FunctionScheduler, AddMultiple) {
179 FunctionScheduler fs;
180 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
181 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
182 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(2), "add2"),
183 std::invalid_argument); // function name already exists
189 EXPECT_EQ(12, total);
190 EXPECT_TRUE(fs.cancelFunction("add2"));
192 EXPECT_EQ(15, total);
195 EXPECT_EQ(15, total);
199 TEST(FunctionScheduler, AddAfterStart) {
201 FunctionScheduler fs;
202 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
203 fs.addFunction([&] { total += 3; }, testInterval(2), "add3");
206 EXPECT_EQ(10, total);
207 fs.addFunction([&] { total += 2; }, testInterval(3), "add22");
209 EXPECT_EQ(17, total);
212 TEST(FunctionScheduler, ShutdownStart) {
214 FunctionScheduler fs;
215 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
222 EXPECT_FALSE(fs.cancelFunction("add3")); // non existing
227 TEST(FunctionScheduler, ResetFunc) {
229 FunctionScheduler fs;
230 fs.addFunction([&] { total += 2; }, testInterval(3), "add2");
231 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
235 EXPECT_FALSE(fs.resetFunctionTimer("NON_EXISTING"));
236 EXPECT_TRUE(fs.resetFunctionTimer("add2"));
238 // t2: after the reset, add2 should have been invoked immediately
241 // t3.5: add3 should have been invoked. add2 should not
242 EXPECT_EQ(10, total);
244 // t4.5: add2 should have been invoked once more (it was reset at t1)
245 EXPECT_EQ(12, total);
248 TEST(FunctionScheduler, AddInvalid) {
250 FunctionScheduler fs;
251 // interval may not be negative
252 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(-1), "add2"),
253 std::invalid_argument);
255 EXPECT_FALSE(fs.cancelFunction("addNoFunc"));
258 TEST(FunctionScheduler, NoFunctions) {
259 FunctionScheduler fs;
260 EXPECT_TRUE(fs.start());
262 FunctionScheduler fs2;
266 TEST(FunctionScheduler, AddWhileRunning) {
268 FunctionScheduler fs;
271 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
272 // The function should be invoked nearly immediately when we add it
273 // and the FunctionScheduler is already running
280 TEST(FunctionScheduler, NoShutdown) {
283 FunctionScheduler fs;
284 fs.addFunction([&] { total += 2; }, testInterval(1), "add2");
289 // Destroyed the FunctionScheduler without calling shutdown.
290 // Everything should have been cleaned up, and the function will no longer
296 TEST(FunctionScheduler, StartDelay) {
298 FunctionScheduler fs;
299 fs.addFunction([&] { total += 2; }, testInterval(2), "add2",
301 fs.addFunction([&] { total += 3; }, testInterval(3), "add3",
303 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(3),
304 "addX", testInterval(-1)),
305 std::invalid_argument);
313 // t4 : add2: total=7
314 // t5 : add3: total=10
315 // t6 : add2: total=12
317 EXPECT_EQ(12, total);
318 fs.cancelFunction("add2");
319 // t8 : add3: total=15
321 EXPECT_EQ(15, total);
324 EXPECT_EQ(15, total);
328 TEST(FunctionScheduler, NoSteadyCatchup) {
329 std::atomic<int> ticks(0);
330 FunctionScheduler fs;
331 // fs.setSteady(false); is the default
332 fs.addFunction([&ticks] {
334 std::this_thread::sleep_for(
335 std::chrono::milliseconds(200));
340 std::this_thread::sleep_for(std::chrono::milliseconds(500));
342 // no steady catch up means we'd tick once for 200ms, then remaining
343 // 300ms / 5 = 60 times
344 EXPECT_LE(ticks.load(), 61);
347 TEST(FunctionScheduler, SteadyCatchup) {
348 std::atomic<int> ticks(0);
349 FunctionScheduler fs;
351 fs.addFunction([&ticks] {
353 std::this_thread::sleep_for(
354 std::chrono::milliseconds(200));
360 std::this_thread::sleep_for(std::chrono::milliseconds(500));
362 // tick every 5ms. Despite tick == 2 is slow, later ticks should be fast
363 // enough to catch back up to schedule
364 EXPECT_NEAR(100, ticks.load(), 10);
367 TEST(FunctionScheduler, UniformDistribution) {
369 const int kTicks = 2;
370 std::chrono::milliseconds minInterval =
371 testInterval(kTicks) - (timeFactor / 5);
372 std::chrono::milliseconds maxInterval =
373 testInterval(kTicks) + (timeFactor / 5);
374 FunctionScheduler fs;
375 fs.addFunctionUniformDistribution([&] { total += 2; },
378 "UniformDistribution",
379 std::chrono::milliseconds(0));
392 TEST(FunctionScheduler, ExponentialBackoff) {
394 int expectedInterval = 0;
395 int nextInterval = 2;
396 FunctionScheduler fs;
397 fs.addFunctionGenericDistribution(
399 [&expectedInterval, nextInterval]() mutable {
400 expectedInterval = nextInterval;
401 nextInterval *= nextInterval;
402 return testInterval(expectedInterval);
404 "ExponentialBackoff",
406 std::chrono::milliseconds(0));
410 delay(expectedInterval);
412 delay(expectedInterval);
419 TEST(FunctionScheduler, GammaIntervalDistribution) {
421 int expectedInterval = 0;
422 FunctionScheduler fs;
423 std::default_random_engine generator(folly::Random::rand32());
424 // The alpha and beta arguments are selected, somewhat randomly, to be 2.0.
425 // These values do not matter much in this test, as we are not testing the
426 // std::gamma_distribution itself...
427 std::gamma_distribution<double> gamma(2.0, 2.0);
428 fs.addFunctionGenericDistribution(
430 [&expectedInterval, generator, gamma]() mutable {
432 getTicksWithinRange(static_cast<int>(gamma(generator)), 2, 10);
433 return testInterval(expectedInterval);
436 "gamma(2.0,2.0)*100ms",
437 std::chrono::milliseconds(0));
441 delay(expectedInterval);
443 delay(expectedInterval);