2 * Copyright 2015 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>
23 #include <gtest/gtest.h>
25 using namespace folly;
26 using std::chrono::milliseconds;
31 * Helper functions for controlling how long this test takes.
33 * Using larger intervals here will make the tests less flaky when run on
34 * heavily loaded systems. However, this will also make the tests take longer
37 static const auto timeFactor = std::chrono::milliseconds(100);
38 std::chrono::milliseconds testInterval(int n) { return n * timeFactor; }
39 int getTicksWithinRange(int n, int min, int max) {
46 std::chrono::microseconds usec(n * timeFactor);
50 } // unnamed namespace
52 TEST(FunctionScheduler, SimpleAdd) {
55 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
64 TEST(FunctionScheduler, AddCancel) {
67 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
73 EXPECT_TRUE(fs.cancelFunction("add2"));
74 EXPECT_FALSE(fs.cancelFunction("NO SUCH FUNC"));
77 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
78 EXPECT_FALSE(fs.start()); // already running
86 TEST(FunctionScheduler, AddCancel2) {
90 // Test adds and cancels while the scheduler is stopped
91 EXPECT_FALSE(fs.cancelFunction("add2"));
92 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
93 EXPECT_TRUE(fs.cancelFunction("add2"));
94 EXPECT_FALSE(fs.cancelFunction("add2"));
95 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
96 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
103 // Cancel add2 while the scheduler is running
104 EXPECT_TRUE(fs.cancelFunction("add2"));
105 EXPECT_FALSE(fs.cancelFunction("add2"));
106 EXPECT_FALSE(fs.cancelFunction("bogus"));
110 EXPECT_TRUE(fs.cancelFunction("add3"));
112 // Test a function that cancels itself
113 int selfCancelCount = 0;
117 if (selfCancelCount > 2) {
118 fs.cancelFunction("selfCancel");
121 testInterval(1), "selfCancel", testInterval(1));
123 EXPECT_EQ(3, selfCancelCount);
124 EXPECT_FALSE(fs.cancelFunction("selfCancel"));
126 // Test a function that schedules another function
129 auto fn2 = [&] { ++fn2Count; };
132 if (adderCount == 2) {
133 fs.addFunction(fn2, testInterval(3), "fn2", testInterval(2));
136 fs.addFunction(fnAdder, testInterval(4), "adder");
139 EXPECT_EQ(1, adderCount);
140 EXPECT_EQ(0, fn2Count);
141 // t4: adder fires, schedules fn2
143 EXPECT_EQ(2, adderCount);
144 EXPECT_EQ(0, fn2Count);
147 EXPECT_EQ(2, adderCount);
148 EXPECT_EQ(1, fn2Count);
152 EXPECT_EQ(3, adderCount);
153 EXPECT_EQ(2, fn2Count);
154 EXPECT_TRUE(fs.cancelFunction("fn2"));
155 EXPECT_TRUE(fs.cancelFunction("adder"));
157 EXPECT_EQ(3, adderCount);
158 EXPECT_EQ(2, fn2Count);
161 EXPECT_EQ(3, selfCancelCount);
164 TEST(FunctionScheduler, AddMultiple) {
166 FunctionScheduler fs;
167 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
168 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
169 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(2), "add2"),
170 std::invalid_argument); // function name already exists
176 EXPECT_EQ(12, total);
177 EXPECT_TRUE(fs.cancelFunction("add2"));
179 EXPECT_EQ(15, total);
182 EXPECT_EQ(15, total);
186 TEST(FunctionScheduler, AddAfterStart) {
188 FunctionScheduler fs;
189 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
190 fs.addFunction([&] { total += 3; }, testInterval(2), "add3");
193 EXPECT_EQ(10, total);
194 fs.addFunction([&] { total += 2; }, testInterval(3), "add22");
196 EXPECT_EQ(17, total);
199 TEST(FunctionScheduler, ShutdownStart) {
201 FunctionScheduler fs;
202 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
209 EXPECT_FALSE(fs.cancelFunction("add3")); // non existing
214 TEST(FunctionScheduler, AddInvalid) {
216 FunctionScheduler fs;
217 // interval may not be negative
218 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(-1), "add2"),
219 std::invalid_argument);
221 EXPECT_FALSE(fs.cancelFunction("addNoFunc"));
224 TEST(FunctionScheduler, NoFunctions) {
225 FunctionScheduler fs;
226 EXPECT_TRUE(fs.start());
228 FunctionScheduler fs2;
232 TEST(FunctionScheduler, AddWhileRunning) {
234 FunctionScheduler fs;
237 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
238 // The function should be invoked nearly immediately when we add it
239 // and the FunctionScheduler is already running
246 TEST(FunctionScheduler, NoShutdown) {
249 FunctionScheduler fs;
250 fs.addFunction([&] { total += 2; }, testInterval(1), "add2");
255 // Destroyed the FunctionScheduler without calling shutdown.
256 // Everything should have been cleaned up, and the function will no longer
262 TEST(FunctionScheduler, StartDelay) {
264 FunctionScheduler fs;
265 fs.addFunction([&] { total += 2; }, testInterval(2), "add2",
267 fs.addFunction([&] { total += 3; }, testInterval(3), "add3",
269 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(3),
270 "addX", testInterval(-1)),
271 std::invalid_argument);
279 // t4 : add2: total=7
280 // t5 : add3: total=10
281 // t6 : add2: total=12
283 EXPECT_EQ(12, total);
284 fs.cancelFunction("add2");
285 // t8 : add3: total=15
287 EXPECT_EQ(15, total);
290 EXPECT_EQ(15, total);
294 TEST(FunctionScheduler, NoSteadyCatchup) {
295 std::atomic<int> ticks(0);
296 FunctionScheduler fs;
297 // fs.setSteady(false); is the default
298 fs.addFunction([&ticks] {
300 std::this_thread::sleep_for(
301 std::chrono::milliseconds(200));
306 std::this_thread::sleep_for(std::chrono::milliseconds(500));
308 // no steady catch up means we'd tick once for 200ms, then remaining
309 // 300ms / 5 = 60 times
310 EXPECT_LE(ticks.load(), 61);
313 TEST(FunctionScheduler, SteadyCatchup) {
314 std::atomic<int> ticks(0);
315 FunctionScheduler fs;
317 fs.addFunction([&ticks] {
319 std::this_thread::sleep_for(
320 std::chrono::milliseconds(200));
326 std::this_thread::sleep_for(std::chrono::milliseconds(500));
328 // tick every 5ms. Despite tick == 2 is slow, later ticks should be fast
329 // enough to catch back up to schedule
330 EXPECT_NEAR(100, ticks.load(), 10);
333 TEST(FunctionScheduler, UniformDistribution) {
335 const int kTicks = 2;
336 std::chrono::milliseconds minInterval =
337 testInterval(kTicks) - (timeFactor / 5);
338 std::chrono::milliseconds maxInterval =
339 testInterval(kTicks) + (timeFactor / 5);
340 FunctionScheduler fs;
341 fs.addFunctionUniformDistribution([&] { total += 2; },
344 "UniformDistribution",
345 std::chrono::milliseconds(0));
358 TEST(FunctionScheduler, ExponentialBackoff) {
360 int expectedInterval = 0;
361 int nextInterval = 2;
362 FunctionScheduler fs;
363 fs.addFunctionGenericDistribution(
365 [&expectedInterval, nextInterval]() mutable {
366 expectedInterval = nextInterval;
367 nextInterval *= nextInterval;
368 return testInterval(expectedInterval);
370 "ExponentialBackoff",
372 std::chrono::milliseconds(0));
376 delay(expectedInterval);
378 delay(expectedInterval);
385 TEST(FunctionScheduler, GammaIntervalDistribution) {
387 int expectedInterval = 0;
388 FunctionScheduler fs;
389 std::default_random_engine generator(folly::Random::rand32());
390 // The alpha and beta arguments are selected, somewhat randomly, to be 2.0.
391 // These values do not matter much in this test, as we are not testing the
392 // std::gamma_distribution itself...
393 std::gamma_distribution<double> gamma(2.0, 2.0);
394 fs.addFunctionGenericDistribution(
396 [&expectedInterval, generator, gamma]() mutable {
398 getTicksWithinRange(static_cast<int>(gamma(generator)), 2, 10);
399 return testInterval(expectedInterval);
402 "gamma(2.0,2.0)*100ms",
403 std::chrono::milliseconds(0));
407 delay(expectedInterval);
409 delay(expectedInterval);