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, SimpleAdd) {
56 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
65 TEST(FunctionScheduler, AddCancel) {
68 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
74 EXPECT_TRUE(fs.cancelFunction("add2"));
75 EXPECT_FALSE(fs.cancelFunction("NO SUCH FUNC"));
78 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
79 EXPECT_FALSE(fs.start()); // already running
87 TEST(FunctionScheduler, AddCancel2) {
91 // Test adds and cancels while the scheduler is stopped
92 EXPECT_FALSE(fs.cancelFunction("add2"));
93 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
94 EXPECT_TRUE(fs.cancelFunction("add2"));
95 EXPECT_FALSE(fs.cancelFunction("add2"));
96 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
97 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
104 // Cancel add2 while the scheduler is running
105 EXPECT_TRUE(fs.cancelFunction("add2"));
106 EXPECT_FALSE(fs.cancelFunction("add2"));
107 EXPECT_FALSE(fs.cancelFunction("bogus"));
111 EXPECT_TRUE(fs.cancelFunction("add3"));
113 // Test a function that cancels itself
114 int selfCancelCount = 0;
118 if (selfCancelCount > 2) {
119 fs.cancelFunction("selfCancel");
122 testInterval(1), "selfCancel", testInterval(1));
124 EXPECT_EQ(3, selfCancelCount);
125 EXPECT_FALSE(fs.cancelFunction("selfCancel"));
127 // Test a function that schedules another function
130 auto fn2 = [&] { ++fn2Count; };
133 if (adderCount == 2) {
134 fs.addFunction(fn2, testInterval(3), "fn2", testInterval(2));
137 fs.addFunction(fnAdder, testInterval(4), "adder");
140 EXPECT_EQ(1, adderCount);
141 EXPECT_EQ(0, fn2Count);
142 // t4: adder fires, schedules fn2
144 EXPECT_EQ(2, adderCount);
145 EXPECT_EQ(0, fn2Count);
148 EXPECT_EQ(2, adderCount);
149 EXPECT_EQ(1, fn2Count);
153 EXPECT_EQ(3, adderCount);
154 EXPECT_EQ(2, fn2Count);
155 EXPECT_TRUE(fs.cancelFunction("fn2"));
156 EXPECT_TRUE(fs.cancelFunction("adder"));
158 EXPECT_EQ(3, adderCount);
159 EXPECT_EQ(2, fn2Count);
162 EXPECT_EQ(3, selfCancelCount);
165 TEST(FunctionScheduler, AddMultiple) {
167 FunctionScheduler fs;
168 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
169 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
170 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(2), "add2"),
171 std::invalid_argument); // function name already exists
177 EXPECT_EQ(12, total);
178 EXPECT_TRUE(fs.cancelFunction("add2"));
180 EXPECT_EQ(15, total);
183 EXPECT_EQ(15, total);
187 TEST(FunctionScheduler, AddAfterStart) {
189 FunctionScheduler fs;
190 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
191 fs.addFunction([&] { total += 3; }, testInterval(2), "add3");
194 EXPECT_EQ(10, total);
195 fs.addFunction([&] { total += 2; }, testInterval(3), "add22");
197 EXPECT_EQ(17, total);
200 TEST(FunctionScheduler, ShutdownStart) {
202 FunctionScheduler fs;
203 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
210 EXPECT_FALSE(fs.cancelFunction("add3")); // non existing
215 TEST(FunctionScheduler, ResetFunc) {
217 FunctionScheduler fs;
218 fs.addFunction([&] { total += 2; }, testInterval(3), "add2");
219 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
223 EXPECT_FALSE(fs.resetFunctionTimer("NON_EXISTING"));
224 EXPECT_TRUE(fs.resetFunctionTimer("add2"));
226 // t2: after the reset, add2 should have been invoked immediately
229 // t3.5: add3 should have been invoked. add2 should not
230 EXPECT_EQ(10, total);
232 // t4.5: add2 should have been invoked once more (it was reset at t1)
233 EXPECT_EQ(12, total);
236 TEST(FunctionScheduler, AddInvalid) {
238 FunctionScheduler fs;
239 // interval may not be negative
240 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(-1), "add2"),
241 std::invalid_argument);
243 EXPECT_FALSE(fs.cancelFunction("addNoFunc"));
246 TEST(FunctionScheduler, NoFunctions) {
247 FunctionScheduler fs;
248 EXPECT_TRUE(fs.start());
250 FunctionScheduler fs2;
254 TEST(FunctionScheduler, AddWhileRunning) {
256 FunctionScheduler fs;
259 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
260 // The function should be invoked nearly immediately when we add it
261 // and the FunctionScheduler is already running
268 TEST(FunctionScheduler, NoShutdown) {
271 FunctionScheduler fs;
272 fs.addFunction([&] { total += 2; }, testInterval(1), "add2");
277 // Destroyed the FunctionScheduler without calling shutdown.
278 // Everything should have been cleaned up, and the function will no longer
284 TEST(FunctionScheduler, StartDelay) {
286 FunctionScheduler fs;
287 fs.addFunction([&] { total += 2; }, testInterval(2), "add2",
289 fs.addFunction([&] { total += 3; }, testInterval(3), "add3",
291 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(3),
292 "addX", testInterval(-1)),
293 std::invalid_argument);
301 // t4 : add2: total=7
302 // t5 : add3: total=10
303 // t6 : add2: total=12
305 EXPECT_EQ(12, total);
306 fs.cancelFunction("add2");
307 // t8 : add3: total=15
309 EXPECT_EQ(15, total);
312 EXPECT_EQ(15, total);
316 TEST(FunctionScheduler, NoSteadyCatchup) {
317 std::atomic<int> ticks(0);
318 FunctionScheduler fs;
319 // fs.setSteady(false); is the default
320 fs.addFunction([&ticks] {
322 std::this_thread::sleep_for(
323 std::chrono::milliseconds(200));
328 std::this_thread::sleep_for(std::chrono::milliseconds(500));
330 // no steady catch up means we'd tick once for 200ms, then remaining
331 // 300ms / 5 = 60 times
332 EXPECT_LE(ticks.load(), 61);
335 TEST(FunctionScheduler, SteadyCatchup) {
336 std::atomic<int> ticks(0);
337 FunctionScheduler fs;
339 fs.addFunction([&ticks] {
341 std::this_thread::sleep_for(
342 std::chrono::milliseconds(200));
348 std::this_thread::sleep_for(std::chrono::milliseconds(500));
350 // tick every 5ms. Despite tick == 2 is slow, later ticks should be fast
351 // enough to catch back up to schedule
352 EXPECT_NEAR(100, ticks.load(), 10);
355 TEST(FunctionScheduler, UniformDistribution) {
357 const int kTicks = 2;
358 std::chrono::milliseconds minInterval =
359 testInterval(kTicks) - (timeFactor / 5);
360 std::chrono::milliseconds maxInterval =
361 testInterval(kTicks) + (timeFactor / 5);
362 FunctionScheduler fs;
363 fs.addFunctionUniformDistribution([&] { total += 2; },
366 "UniformDistribution",
367 std::chrono::milliseconds(0));
380 TEST(FunctionScheduler, ExponentialBackoff) {
382 int expectedInterval = 0;
383 int nextInterval = 2;
384 FunctionScheduler fs;
385 fs.addFunctionGenericDistribution(
387 [&expectedInterval, nextInterval]() mutable {
388 expectedInterval = nextInterval;
389 nextInterval *= nextInterval;
390 return testInterval(expectedInterval);
392 "ExponentialBackoff",
394 std::chrono::milliseconds(0));
398 delay(expectedInterval);
400 delay(expectedInterval);
407 TEST(FunctionScheduler, GammaIntervalDistribution) {
409 int expectedInterval = 0;
410 FunctionScheduler fs;
411 std::default_random_engine generator(folly::Random::rand32());
412 // The alpha and beta arguments are selected, somewhat randomly, to be 2.0.
413 // These values do not matter much in this test, as we are not testing the
414 // std::gamma_distribution itself...
415 std::gamma_distribution<double> gamma(2.0, 2.0);
416 fs.addFunctionGenericDistribution(
418 [&expectedInterval, generator, gamma]() mutable {
420 getTicksWithinRange(static_cast<int>(gamma(generator)), 2, 10);
421 return testInterval(expectedInterval);
424 "gamma(2.0,2.0)*100ms",
425 std::chrono::milliseconds(0));
429 delay(expectedInterval);
431 delay(expectedInterval);