2 * Copyright 2017 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.
21 #include <folly/Random.h>
22 #include <folly/experimental/FunctionScheduler.h>
23 #include <folly/portability/GTest.h>
25 #if defined(__linux__)
29 using namespace folly;
30 using std::chrono::milliseconds;
35 * Helper functions for controlling how long this test takes.
37 * Using larger intervals here will make the tests less flaky when run on
38 * heavily loaded systems. However, this will also make the tests take longer
41 static const auto timeFactor = std::chrono::milliseconds(100);
42 std::chrono::milliseconds testInterval(int n) { return n * timeFactor; }
43 int getTicksWithinRange(int n, int min, int max) {
50 std::chrono::microseconds usec(n * timeFactor);
54 } // unnamed namespace
56 TEST(FunctionScheduler, StartAndShutdown) {
58 EXPECT_TRUE(fs.start());
59 EXPECT_FALSE(fs.start());
60 EXPECT_TRUE(fs.shutdown());
61 EXPECT_FALSE(fs.shutdown());
63 EXPECT_TRUE(fs.start());
64 EXPECT_FALSE(fs.start());
65 EXPECT_TRUE(fs.shutdown());
66 EXPECT_FALSE(fs.shutdown());
69 TEST(FunctionScheduler, SimpleAdd) {
72 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
81 TEST(FunctionScheduler, AddCancel) {
84 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
90 EXPECT_TRUE(fs.cancelFunction("add2"));
91 EXPECT_FALSE(fs.cancelFunction("NO SUCH FUNC"));
94 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
102 TEST(FunctionScheduler, AddCancel2) {
104 FunctionScheduler fs;
106 // Test adds and cancels while the scheduler is stopped
107 EXPECT_FALSE(fs.cancelFunction("add2"));
108 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
109 EXPECT_TRUE(fs.cancelFunction("add2"));
110 EXPECT_FALSE(fs.cancelFunction("add2"));
111 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
112 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
119 // Cancel add2 while the scheduler is running
120 EXPECT_TRUE(fs.cancelFunction("add2"));
121 EXPECT_FALSE(fs.cancelFunction("add2"));
122 EXPECT_FALSE(fs.cancelFunction("bogus"));
126 EXPECT_TRUE(fs.cancelFunction("add3"));
128 // Test a function that cancels itself
129 int selfCancelCount = 0;
133 if (selfCancelCount > 2) {
134 fs.cancelFunction("selfCancel");
137 testInterval(1), "selfCancel", testInterval(1));
139 EXPECT_EQ(3, selfCancelCount);
140 EXPECT_FALSE(fs.cancelFunction("selfCancel"));
142 // Test a function that schedules another function
145 auto fn2 = [&] { ++fn2Count; };
148 if (adderCount == 2) {
149 fs.addFunction(fn2, testInterval(3), "fn2", testInterval(2));
152 fs.addFunction(fnAdder, testInterval(4), "adder");
155 EXPECT_EQ(1, adderCount);
156 EXPECT_EQ(0, fn2Count);
157 // t4: adder fires, schedules fn2
159 EXPECT_EQ(2, adderCount);
160 EXPECT_EQ(0, fn2Count);
163 EXPECT_EQ(2, adderCount);
164 EXPECT_EQ(1, fn2Count);
168 EXPECT_EQ(3, adderCount);
169 EXPECT_EQ(2, fn2Count);
170 EXPECT_TRUE(fs.cancelFunction("fn2"));
171 EXPECT_TRUE(fs.cancelFunction("adder"));
173 EXPECT_EQ(3, adderCount);
174 EXPECT_EQ(2, fn2Count);
177 EXPECT_EQ(3, selfCancelCount);
180 TEST(FunctionScheduler, AddMultiple) {
182 FunctionScheduler fs;
183 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
184 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
185 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(2), "add2"),
186 std::invalid_argument); // function name already exists
192 EXPECT_EQ(12, total);
193 EXPECT_TRUE(fs.cancelFunction("add2"));
195 EXPECT_EQ(15, total);
198 EXPECT_EQ(15, total);
202 TEST(FunctionScheduler, AddAfterStart) {
204 FunctionScheduler fs;
205 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
206 fs.addFunction([&] { total += 3; }, testInterval(2), "add3");
209 EXPECT_EQ(10, total);
210 fs.addFunction([&] { total += 2; }, testInterval(3), "add22");
212 EXPECT_EQ(17, total);
215 TEST(FunctionScheduler, ShutdownStart) {
217 FunctionScheduler fs;
218 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
225 EXPECT_FALSE(fs.cancelFunction("add3")); // non existing
230 TEST(FunctionScheduler, ResetFunc) {
232 FunctionScheduler fs;
233 fs.addFunction([&] { total += 2; }, testInterval(3), "add2");
234 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
238 EXPECT_FALSE(fs.resetFunctionTimer("NON_EXISTING"));
239 EXPECT_TRUE(fs.resetFunctionTimer("add2"));
241 // t2: after the reset, add2 should have been invoked immediately
244 // t3.5: add3 should have been invoked. add2 should not
245 EXPECT_EQ(10, total);
247 // t4.5: add2 should have been invoked once more (it was reset at t1)
248 EXPECT_EQ(12, total);
251 TEST(FunctionScheduler, AddInvalid) {
253 FunctionScheduler fs;
254 // interval may not be negative
255 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(-1), "add2"),
256 std::invalid_argument);
258 EXPECT_FALSE(fs.cancelFunction("addNoFunc"));
261 TEST(FunctionScheduler, NoFunctions) {
262 FunctionScheduler fs;
263 EXPECT_TRUE(fs.start());
265 FunctionScheduler fs2;
269 TEST(FunctionScheduler, AddWhileRunning) {
271 FunctionScheduler fs;
274 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
275 // The function should be invoked nearly immediately when we add it
276 // and the FunctionScheduler is already running
283 TEST(FunctionScheduler, NoShutdown) {
286 FunctionScheduler fs;
287 fs.addFunction([&] { total += 2; }, testInterval(1), "add2");
292 // Destroyed the FunctionScheduler without calling shutdown.
293 // Everything should have been cleaned up, and the function will no longer
299 TEST(FunctionScheduler, StartDelay) {
301 FunctionScheduler fs;
302 fs.addFunction([&] { total += 2; }, testInterval(2), "add2",
304 fs.addFunction([&] { total += 3; }, testInterval(3), "add3",
306 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(3),
307 "addX", testInterval(-1)),
308 std::invalid_argument);
316 // t4 : add2: total=7
317 // t5 : add3: total=10
318 // t6 : add2: total=12
320 EXPECT_EQ(12, total);
321 fs.cancelFunction("add2");
322 // t8 : add3: total=15
324 EXPECT_EQ(15, total);
327 EXPECT_EQ(15, total);
331 TEST(FunctionScheduler, NoSteadyCatchup) {
332 std::atomic<int> ticks(0);
333 FunctionScheduler fs;
334 // fs.setSteady(false); is the default
335 fs.addFunction([&ticks] {
337 std::this_thread::sleep_for(
338 std::chrono::milliseconds(200));
343 std::this_thread::sleep_for(std::chrono::milliseconds(500));
345 // no steady catch up means we'd tick once for 200ms, then remaining
346 // 300ms / 5 = 60 times
347 EXPECT_LE(ticks.load(), 61);
350 TEST(FunctionScheduler, SteadyCatchup) {
351 std::atomic<int> ticks(0);
352 FunctionScheduler fs;
354 fs.addFunction([&ticks] {
356 std::this_thread::sleep_for(
357 std::chrono::milliseconds(200));
363 std::this_thread::sleep_for(std::chrono::milliseconds(500));
365 // tick every 5ms. Despite tick == 2 is slow, later ticks should be fast
366 // enough to catch back up to schedule
367 EXPECT_NEAR(100, ticks.load(), 10);
370 TEST(FunctionScheduler, UniformDistribution) {
372 const int kTicks = 2;
373 std::chrono::milliseconds minInterval =
374 testInterval(kTicks) - (timeFactor / 5);
375 std::chrono::milliseconds maxInterval =
376 testInterval(kTicks) + (timeFactor / 5);
377 FunctionScheduler fs;
378 fs.addFunctionUniformDistribution([&] { total += 2; },
381 "UniformDistribution",
382 std::chrono::milliseconds(0));
395 TEST(FunctionScheduler, ExponentialBackoff) {
397 int expectedInterval = 0;
398 int nextInterval = 2;
399 FunctionScheduler fs;
400 fs.addFunctionGenericDistribution(
402 [&expectedInterval, nextInterval]() mutable {
403 expectedInterval = nextInterval;
404 nextInterval *= nextInterval;
405 return testInterval(expectedInterval);
407 "ExponentialBackoff",
409 std::chrono::milliseconds(0));
413 delay(expectedInterval);
415 delay(expectedInterval);
422 TEST(FunctionScheduler, GammaIntervalDistribution) {
424 int expectedInterval = 0;
425 FunctionScheduler fs;
426 std::default_random_engine generator(folly::Random::rand32());
427 // The alpha and beta arguments are selected, somewhat randomly, to be 2.0.
428 // These values do not matter much in this test, as we are not testing the
429 // std::gamma_distribution itself...
430 std::gamma_distribution<double> gamma(2.0, 2.0);
431 fs.addFunctionGenericDistribution(
433 [&expectedInterval, generator, gamma]() mutable {
435 getTicksWithinRange(static_cast<int>(gamma(generator)), 2, 10);
436 return testInterval(expectedInterval);
439 "gamma(2.0,2.0)*100ms",
440 std::chrono::milliseconds(0));
444 delay(expectedInterval);
446 delay(expectedInterval);
453 TEST(FunctionScheduler, AddWithRunOnce) {
455 FunctionScheduler fs;
456 fs.addFunctionOnce([&] { total += 2; }, "add2");
463 fs.addFunctionOnce([&] { total += 2; }, "add2");
472 TEST(FunctionScheduler, cancelFunctionAndWait) {
474 FunctionScheduler fs;
485 EXPECT_EQ(0, total); // add2 is still sleeping
487 EXPECT_TRUE(fs.cancelFunctionAndWait("add2"));
488 EXPECT_EQ(2, total); // add2 should have completed
490 EXPECT_FALSE(fs.cancelFunction("add2")); // add2 has been canceled
494 #if defined(__linux__)
497 * A helper class that forces our pthread_create() wrapper to fail when
498 * an PThreadCreateFailure object exists.
500 class PThreadCreateFailure {
502 PThreadCreateFailure() {
505 ~PThreadCreateFailure() {
509 static bool shouldFail() {
510 return forceFailure_ > 0;
514 static std::atomic<int> forceFailure_;
517 std::atomic<int> PThreadCreateFailure::forceFailure_{0};
518 } // unnamed namespce
520 // Replace the system pthread_create() function with our own stub, so we can
521 // trigger failures in the StartThrows() test.
522 extern "C" int pthread_create(
524 const pthread_attr_t* attr,
525 void* (*start_routine)(void*),
527 static const auto realFunction = reinterpret_cast<decltype(&pthread_create)>(
528 dlsym(RTLD_NEXT, "pthread_create"));
529 // For sanity, make sure we didn't find ourself,
530 // since that would cause infinite recursion.
531 CHECK_NE(realFunction, pthread_create);
533 if (PThreadCreateFailure::shouldFail()) {
537 return realFunction(thread, attr, start_routine, arg);
540 TEST(FunctionScheduler, StartThrows) {
541 FunctionScheduler fs;
542 PThreadCreateFailure fail;
543 EXPECT_ANY_THROW(fs.start());
544 EXPECT_NO_THROW(fs.shutdown());
548 TEST(FunctionScheduler, cancelAllFunctionsAndWait) {
550 FunctionScheduler fs;
562 EXPECT_EQ(0, total); // add2 is still sleeping
564 fs.cancelAllFunctionsAndWait();
567 EXPECT_FALSE(fs.cancelFunction("add2")); // add2 has been canceled