TEST(FunctionScheduler, CancelAllAndWaitWithOneRunningAndOneWaiting) { folly::Baton<> baton; std::thread th([&baton]() { std::atomic<int> nExecuted(0); FunctionScheduler fs; fs.addFunction( [&nExecuted] { nExecuted++; delay(10); }, testInterval(2), "func0"); fs.addFunction( [&nExecuted] { nExecuted++; delay(10); }, testInterval(2), "func1", testInterval(5)); fs.start(); delay(1); fs.cancelAllFunctionsAndWait(); EXPECT_EQ(nExecuted, 1); baton.post(); }); ASSERT_TRUE(baton.timed_wait(testInterval(15))); th.join(); }
TEST(FunctionScheduler, GammaIntervalDistribution) { int total = 0; int expectedInterval = 0; FunctionScheduler fs; std::default_random_engine generator(folly::Random::rand32()); // The alpha and beta arguments are selected, somewhat randomly, to be 2.0. // These values do not matter much in this test, as we are not testing the // std::gamma_distribution itself... std::gamma_distribution<double> gamma(2.0, 2.0); fs.addFunctionGenericDistribution( [&] { total += 2; }, [&expectedInterval, generator, gamma]() mutable { expectedInterval = getTicksWithinRange(static_cast<int>(gamma(generator)), 2, 10); return testInterval(expectedInterval); }, "GammaDistribution", "gamma(2.0,2.0)*100ms", std::chrono::milliseconds(0)); fs.start(); delay(1); EXPECT_EQ(2, total); delay(expectedInterval); EXPECT_EQ(4, total); delay(expectedInterval); EXPECT_EQ(6, total); fs.shutdown(); delay(2); EXPECT_EQ(6, total); }
TEST(FunctionScheduler, NoFunctions) { FunctionScheduler fs; EXPECT_TRUE(fs.start()); fs.shutdown(); FunctionScheduler fs2; fs2.shutdown(); }
TEST(FunctionScheduler, ExponentialBackoff) { int total = 0; int expectedInterval = 0; int nextInterval = 2; FunctionScheduler fs; fs.addFunctionGenericDistribution( [&] { total += 2; }, [&expectedInterval, nextInterval]() mutable { expectedInterval = nextInterval; nextInterval *= nextInterval; return testInterval(expectedInterval); }, "ExponentialBackoff", "2^n * 100ms", std::chrono::milliseconds(0)); fs.start(); delay(1); EXPECT_EQ(2, total); delay(expectedInterval); EXPECT_EQ(4, total); delay(expectedInterval); EXPECT_EQ(6, total); fs.shutdown(); delay(2); EXPECT_EQ(6, total); }
TEST(FunctionScheduler, AddInvalid) { int total = 0; FunctionScheduler fs; // interval may not be negative EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(-1), "add2"), std::exception); EXPECT_FALSE(fs.cancelFunction("addNoFunc")); }
TEST(FunctionScheduler, SimpleAdd) { int total = 0; FunctionScheduler fs; fs.addFunction([&] { total += 2; }, testInterval(2), "add2"); fs.start(); delay(1); EXPECT_EQ(2, total); fs.shutdown(); delay(2); EXPECT_EQ(2, total); }
TEST(FunctionScheduler, ConcurrentCancelFunctionAndWait) { FunctionScheduler fs; fs.addFunction([] { delay(10); }, testInterval(2), "func"); fs.start(); delay(1); std::thread th1([&fs] { EXPECT_TRUE(fs.cancelFunctionAndWait("func")); }); delay(1); std::thread th2([&fs] { EXPECT_FALSE(fs.cancelFunctionAndWait("func")); }); th1.join(); th2.join(); }
TEST(FunctionScheduler, AddAfterStart) { int total = 0; FunctionScheduler fs; fs.addFunction([&] { total += 2; }, testInterval(2), "add2"); fs.addFunction([&] { total += 3; }, testInterval(2), "add3"); fs.start(); delay(3); EXPECT_EQ(10, total); fs.addFunction([&] { total += 2; }, testInterval(3), "add22"); delay(2); EXPECT_EQ(17, total); }
TEST(FunctionScheduler, AddWhileRunning) { int total = 0; FunctionScheduler fs; fs.start(); delay(1); fs.addFunction([&] { total += 2; }, testInterval(2), "add2"); // The function should be invoked nearly immediately when we add it // and the FunctionScheduler is already running usleep(50000); EXPECT_EQ(2, total); delay(2); EXPECT_EQ(4, total); }
TEST(FunctionScheduler, StartDelay) { int total = 0; FunctionScheduler fs; fs.addFunction([&] { total += 2; }, testInterval(2), "add2", testInterval(2)); fs.addFunction([&] { total += 3; }, testInterval(3), "add3", testInterval(2)); EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(3), "addX", testInterval(-1)), std::exception); fs.start(); delay(1); // t1 EXPECT_EQ(0, total); // t2 : add2 total=2 // t2 : add3 total=5 delay(2); // t3 EXPECT_EQ(5, total); // t4 : add2: total=7 // t5 : add3: total=10 // t6 : add2: total=12 delay(4); // t7 EXPECT_EQ(12, total); fs.cancelFunction("add2"); // t8 : add3: total=15 delay(2); // t9 EXPECT_EQ(15, total); fs.shutdown(); delay(3); EXPECT_EQ(15, total); fs.shutdown(); }
TEST(FunctionScheduler, CancelAllAndWaitWithRunningFunc) { folly::Baton<> baton; std::thread th([&baton]() { FunctionScheduler fs; fs.addFunction([] { delay(10); }, testInterval(2), "func"); fs.start(); delay(1); fs.cancelAllFunctionsAndWait(); baton.post(); }); ASSERT_TRUE(baton.timed_wait(testInterval(15))); th.join(); }
TEST(FunctionScheduler, NoShutdown) { int total = 0; { FunctionScheduler fs; fs.addFunction([&] { total += 2; }, testInterval(1), "add2"); fs.start(); usleep(50000); EXPECT_EQ(2, total); } // Destroyed the FunctionScheduler without calling shutdown. // Everything should have been cleaned up, and the function will no longer // get called. delay(2); EXPECT_EQ(2, total); }
TEST(FunctionScheduler, StartAndShutdown) { FunctionScheduler fs; EXPECT_TRUE(fs.start()); EXPECT_FALSE(fs.start()); EXPECT_TRUE(fs.shutdown()); EXPECT_FALSE(fs.shutdown()); // start again EXPECT_TRUE(fs.start()); EXPECT_FALSE(fs.start()); EXPECT_TRUE(fs.shutdown()); EXPECT_FALSE(fs.shutdown()); }
TEST(FunctionScheduler, ResetFuncWhileRunning) { struct State { boost::barrier barrier_a{2}; boost::barrier barrier_b{2}; boost::barrier barrier_c{2}; boost::barrier barrier_d{2}; bool set = false; size_t count = 0; }; State state; // held by ref auto mv = std::make_shared<size_t>(); // gets moved FunctionScheduler fs; fs.addFunction( [&, mv /* ref + shared_ptr fit in in-situ storage */] { if (!state.set) { // first invocation state.barrier_a.wait(); // ensure that resetFunctionTimer is called in this critical section state.barrier_b.wait(); ++state.count; EXPECT_TRUE(bool(mv)) << "bug repro: mv was moved-out"; state.barrier_c.wait(); // main thread checks count here state.barrier_d.wait(); } else { // subsequent invocations ++state.count; } }, testInterval(3), "nada"); fs.start(); state.barrier_a.wait(); state.set = true; fs.resetFunctionTimer("nada"); EXPECT_EQ(0, state.count) << "sanity check"; state.barrier_b.wait(); // fn thread increments count and checks mv here state.barrier_c.wait(); EXPECT_EQ(1, state.count) << "sanity check"; state.barrier_d.wait(); delay(1); EXPECT_EQ(2, state.count) << "sanity check"; }
TEST(FunctionScheduler, NoSteadyCatchup) { std::atomic<int> ticks(0); FunctionScheduler fs; // fs.setSteady(false); is the default fs.addFunction([&ticks] { if (++ticks == 2) { std::this_thread::sleep_for( std::chrono::milliseconds(200)); } }, milliseconds(5)); fs.start(); std::this_thread::sleep_for(std::chrono::milliseconds(500)); // no steady catch up means we'd tick once for 200ms, then remaining // 300ms / 5 = 60 times EXPECT_LE(ticks.load(), 61); }
TEST(FunctionScheduler, AddWithRunOnce) { int total = 0; FunctionScheduler fs; fs.addFunctionOnce([&] { total += 2; }, "add2"); fs.start(); delay(1); EXPECT_EQ(2, total); delay(2); EXPECT_EQ(2, total); fs.addFunctionOnce([&] { total += 2; }, "add2"); delay(1); EXPECT_EQ(4, total); delay(2); EXPECT_EQ(4, total); fs.shutdown(); }
TEST(FunctionScheduler, SteadyCatchup) { std::atomic<int> ticks(0); FunctionScheduler fs; fs.setSteady(true); fs.addFunction([&ticks] { if (++ticks == 2) { std::this_thread::sleep_for( std::chrono::milliseconds(200)); } }, milliseconds(5)); fs.start(); std::this_thread::sleep_for(std::chrono::milliseconds(500)); // tick every 5ms. Despite tick == 2 is slow, later ticks should be fast // enough to catch back up to schedule EXPECT_NEAR(100, ticks.load(), 10); }
TEST(FunctionScheduler, ResetFunc) { int total = 0; FunctionScheduler fs; fs.addFunction([&] { total += 2; }, testInterval(3), "add2"); fs.addFunction([&] { total += 3; }, testInterval(3), "add3"); fs.start(); delay(1); EXPECT_EQ(5, total); EXPECT_FALSE(fs.resetFunctionTimer("NON_EXISTING")); EXPECT_TRUE(fs.resetFunctionTimer("add2")); delay(1); // t2: after the reset, add2 should have been invoked immediately EXPECT_EQ(7, total); usleep(150000); // t3.5: add3 should have been invoked. add2 should not EXPECT_EQ(10, total); delay(1); // t4.5: add2 should have been invoked once more (it was reset at t1) EXPECT_EQ(12, total); }
TEST(FunctionScheduler, UniformDistribution) { int total = 0; const int kTicks = 2; std::chrono::milliseconds minInterval = testInterval(kTicks) - (timeFactor / 5); std::chrono::milliseconds maxInterval = testInterval(kTicks) + (timeFactor / 5); FunctionScheduler fs; fs.addFunctionUniformDistribution([&] { total += 2; }, minInterval, maxInterval, "UniformDistribution", std::chrono::milliseconds(0)); fs.start(); delay(1); EXPECT_EQ(2, total); delay(kTicks); EXPECT_EQ(4, total); delay(kTicks); EXPECT_EQ(6, total); fs.shutdown(); delay(2); EXPECT_EQ(6, total); }
TEST(FunctionScheduler, AddCancel) { int total = 0; FunctionScheduler fs; fs.addFunction([&] { total += 2; }, testInterval(2), "add2"); fs.start(); delay(1); EXPECT_EQ(2, total); delay(2); EXPECT_EQ(4, total); EXPECT_TRUE(fs.cancelFunction("add2")); EXPECT_FALSE(fs.cancelFunction("NO SUCH FUNC")); delay(2); EXPECT_EQ(4, total); fs.addFunction([&] { total += 1; }, testInterval(2), "add2"); EXPECT_FALSE(fs.start()); // already running delay(1); EXPECT_EQ(5, total); delay(2); EXPECT_EQ(6, total); fs.shutdown(); }
TEST(FunctionScheduler, ShutdownStart) { int total = 0; FunctionScheduler fs; fs.addFunction([&] { total += 2; }, testInterval(2), "add2"); fs.start(); delay(1); fs.shutdown(); fs.start(); delay(1); EXPECT_EQ(4, total); EXPECT_FALSE(fs.cancelFunction("add3")); // non existing delay(2); EXPECT_EQ(6, total); }
TEST(FunctionScheduler, AddMultiple) { int total = 0; FunctionScheduler fs; fs.addFunction([&] { total += 2; }, testInterval(2), "add2"); fs.addFunction([&] { total += 3; }, testInterval(3), "add3"); EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(2), "add2"), std::invalid_argument); // function name already exists fs.start(); delay(1); EXPECT_EQ(5, total); delay(4); EXPECT_EQ(12, total); EXPECT_TRUE(fs.cancelFunction("add2")); delay(2); EXPECT_EQ(15, total); fs.shutdown(); delay(3); EXPECT_EQ(15, total); fs.shutdown(); }
TEST(FunctionScheduler, cancelFunctionAndWait) { int total = 0; FunctionScheduler fs; fs.addFunction( [&] { delay(5); total += 2; }, testInterval(100), "add2"); fs.start(); delay(1); EXPECT_EQ(0, total); // add2 is still sleeping EXPECT_TRUE(fs.cancelFunctionAndWait("add2")); EXPECT_EQ(2, total); // add2 should have completed EXPECT_FALSE(fs.cancelFunction("add2")); // add2 has been canceled fs.shutdown(); }
TEST(FunctionScheduler, AddCancel2) { int total = 0; FunctionScheduler fs; // Test adds and cancels while the scheduler is stopped EXPECT_FALSE(fs.cancelFunction("add2")); fs.addFunction([&] { total += 1; }, testInterval(2), "add2"); EXPECT_TRUE(fs.cancelFunction("add2")); EXPECT_FALSE(fs.cancelFunction("add2")); fs.addFunction([&] { total += 2; }, testInterval(2), "add2"); fs.addFunction([&] { total += 3; }, testInterval(3), "add3"); EXPECT_EQ(0, total); fs.start(); delay(1); EXPECT_EQ(5, total); // Cancel add2 while the scheduler is running EXPECT_TRUE(fs.cancelFunction("add2")); EXPECT_FALSE(fs.cancelFunction("add2")); EXPECT_FALSE(fs.cancelFunction("bogus")); delay(3); EXPECT_EQ(8, total); EXPECT_TRUE(fs.cancelFunction("add3")); // Test a function that cancels itself int selfCancelCount = 0; fs.addFunction( [&] { ++selfCancelCount; if (selfCancelCount > 2) { fs.cancelFunction("selfCancel"); } }, testInterval(1), "selfCancel", testInterval(1)); delay(4); EXPECT_EQ(3, selfCancelCount); EXPECT_FALSE(fs.cancelFunction("selfCancel")); // Test a function that schedules another function int adderCount = 0; int fn2Count = 0; auto fn2 = [&] { ++fn2Count; }; auto fnAdder = [&] { ++adderCount; if (adderCount == 2) { fs.addFunction(fn2, testInterval(3), "fn2", testInterval(2)); } }; fs.addFunction(fnAdder, testInterval(4), "adder"); // t0: adder fires delay(1); // t1 EXPECT_EQ(1, adderCount); EXPECT_EQ(0, fn2Count); // t4: adder fires, schedules fn2 delay(4); // t5 EXPECT_EQ(2, adderCount); EXPECT_EQ(0, fn2Count); // t6: fn2 fires delay(2); // t7 EXPECT_EQ(2, adderCount); EXPECT_EQ(1, fn2Count); // t8: adder fires // t9: fn2 fires delay(3); // t10 EXPECT_EQ(3, adderCount); EXPECT_EQ(2, fn2Count); EXPECT_TRUE(fs.cancelFunction("fn2")); EXPECT_TRUE(fs.cancelFunction("adder")); delay(5); // t10 EXPECT_EQ(3, adderCount); EXPECT_EQ(2, fn2Count); EXPECT_EQ(8, total); EXPECT_EQ(3, selfCancelCount); }
TEST(FunctionScheduler, StartThrows) { FunctionScheduler fs; PThreadCreateFailure fail; EXPECT_ANY_THROW(fs.start()); EXPECT_NO_THROW(fs.shutdown()); }