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/Baton.h>
22 #include <folly/Random.h>
23 #include <folly/experimental/FunctionScheduler.h>
24 #include <folly/portability/GTest.h>
26 #if defined(__linux__)
30 using namespace folly;
31 using std::chrono::milliseconds;
36 * Helper functions for controlling how long this test takes.
38 * Using larger intervals here will make the tests less flaky when run on
39 * heavily loaded systems. However, this will also make the tests take longer
42 static const auto timeFactor = std::chrono::milliseconds(100);
43 std::chrono::milliseconds testInterval(int n) { return n * timeFactor; }
44 int getTicksWithinRange(int n, int min, int max) {
51 std::chrono::microseconds usec(n * timeFactor);
57 TEST(FunctionScheduler, StartAndShutdown) {
59 EXPECT_TRUE(fs.start());
60 EXPECT_FALSE(fs.start());
61 EXPECT_TRUE(fs.shutdown());
62 EXPECT_FALSE(fs.shutdown());
64 EXPECT_TRUE(fs.start());
65 EXPECT_FALSE(fs.start());
66 EXPECT_TRUE(fs.shutdown());
67 EXPECT_FALSE(fs.shutdown());
70 TEST(FunctionScheduler, SimpleAdd) {
73 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
82 TEST(FunctionScheduler, AddCancel) {
85 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
91 EXPECT_TRUE(fs.cancelFunction("add2"));
92 EXPECT_FALSE(fs.cancelFunction("NO SUCH FUNC"));
95 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
103 TEST(FunctionScheduler, AddCancel2) {
105 FunctionScheduler fs;
107 // Test adds and cancels while the scheduler is stopped
108 EXPECT_FALSE(fs.cancelFunction("add2"));
109 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
110 EXPECT_TRUE(fs.cancelFunction("add2"));
111 EXPECT_FALSE(fs.cancelFunction("add2"));
112 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
113 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
120 // Cancel add2 while the scheduler is running
121 EXPECT_TRUE(fs.cancelFunction("add2"));
122 EXPECT_FALSE(fs.cancelFunction("add2"));
123 EXPECT_FALSE(fs.cancelFunction("bogus"));
127 EXPECT_TRUE(fs.cancelFunction("add3"));
129 // Test a function that cancels itself
130 int selfCancelCount = 0;
134 if (selfCancelCount > 2) {
135 fs.cancelFunction("selfCancel");
138 testInterval(1), "selfCancel", testInterval(1));
140 EXPECT_EQ(3, selfCancelCount);
141 EXPECT_FALSE(fs.cancelFunction("selfCancel"));
143 // Test a function that schedules another function
146 auto fn2 = [&] { ++fn2Count; };
149 if (adderCount == 2) {
150 fs.addFunction(fn2, testInterval(3), "fn2", testInterval(2));
153 fs.addFunction(fnAdder, testInterval(4), "adder");
156 EXPECT_EQ(1, adderCount);
157 EXPECT_EQ(0, fn2Count);
158 // t4: adder fires, schedules fn2
160 EXPECT_EQ(2, adderCount);
161 EXPECT_EQ(0, fn2Count);
164 EXPECT_EQ(2, adderCount);
165 EXPECT_EQ(1, fn2Count);
169 EXPECT_EQ(3, adderCount);
170 EXPECT_EQ(2, fn2Count);
171 EXPECT_TRUE(fs.cancelFunction("fn2"));
172 EXPECT_TRUE(fs.cancelFunction("adder"));
174 EXPECT_EQ(3, adderCount);
175 EXPECT_EQ(2, fn2Count);
178 EXPECT_EQ(3, selfCancelCount);
181 TEST(FunctionScheduler, AddMultiple) {
183 FunctionScheduler fs;
184 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
185 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
186 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(2), "add2"),
187 std::invalid_argument); // function name already exists
193 EXPECT_EQ(12, total);
194 EXPECT_TRUE(fs.cancelFunction("add2"));
196 EXPECT_EQ(15, total);
199 EXPECT_EQ(15, total);
203 TEST(FunctionScheduler, AddAfterStart) {
205 FunctionScheduler fs;
206 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
207 fs.addFunction([&] { total += 3; }, testInterval(2), "add3");
210 EXPECT_EQ(10, total);
211 fs.addFunction([&] { total += 2; }, testInterval(3), "add22");
213 EXPECT_EQ(17, total);
216 TEST(FunctionScheduler, ShutdownStart) {
218 FunctionScheduler fs;
219 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
226 EXPECT_FALSE(fs.cancelFunction("add3")); // non existing
231 TEST(FunctionScheduler, ResetFunc) {
233 FunctionScheduler fs;
234 fs.addFunction([&] { total += 2; }, testInterval(3), "add2");
235 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
239 EXPECT_FALSE(fs.resetFunctionTimer("NON_EXISTING"));
240 EXPECT_TRUE(fs.resetFunctionTimer("add2"));
242 // t2: after the reset, add2 should have been invoked immediately
245 // t3.5: add3 should have been invoked. add2 should not
246 EXPECT_EQ(10, total);
248 // t4.5: add2 should have been invoked once more (it was reset at t1)
249 EXPECT_EQ(12, total);
252 TEST(FunctionScheduler, AddInvalid) {
254 FunctionScheduler fs;
255 // interval may not be negative
256 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(-1), "add2"),
257 std::invalid_argument);
259 EXPECT_FALSE(fs.cancelFunction("addNoFunc"));
262 TEST(FunctionScheduler, NoFunctions) {
263 FunctionScheduler fs;
264 EXPECT_TRUE(fs.start());
266 FunctionScheduler fs2;
270 TEST(FunctionScheduler, AddWhileRunning) {
272 FunctionScheduler fs;
275 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
276 // The function should be invoked nearly immediately when we add it
277 // and the FunctionScheduler is already running
284 TEST(FunctionScheduler, NoShutdown) {
287 FunctionScheduler fs;
288 fs.addFunction([&] { total += 2; }, testInterval(1), "add2");
293 // Destroyed the FunctionScheduler without calling shutdown.
294 // Everything should have been cleaned up, and the function will no longer
300 TEST(FunctionScheduler, StartDelay) {
302 FunctionScheduler fs;
303 fs.addFunction([&] { total += 2; }, testInterval(2), "add2",
305 fs.addFunction([&] { total += 3; }, testInterval(3), "add3",
307 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(3),
308 "addX", testInterval(-1)),
309 std::invalid_argument);
317 // t4 : add2: total=7
318 // t5 : add3: total=10
319 // t6 : add2: total=12
321 EXPECT_EQ(12, total);
322 fs.cancelFunction("add2");
323 // t8 : add3: total=15
325 EXPECT_EQ(15, total);
328 EXPECT_EQ(15, total);
332 TEST(FunctionScheduler, NoSteadyCatchup) {
333 std::atomic<int> ticks(0);
334 FunctionScheduler fs;
335 // fs.setSteady(false); is the default
336 fs.addFunction([&ticks] {
338 std::this_thread::sleep_for(
339 std::chrono::milliseconds(200));
344 std::this_thread::sleep_for(std::chrono::milliseconds(500));
346 // no steady catch up means we'd tick once for 200ms, then remaining
347 // 300ms / 5 = 60 times
348 EXPECT_LE(ticks.load(), 61);
351 TEST(FunctionScheduler, SteadyCatchup) {
352 std::atomic<int> ticks(0);
353 FunctionScheduler fs;
355 fs.addFunction([&ticks] {
357 std::this_thread::sleep_for(
358 std::chrono::milliseconds(200));
364 std::this_thread::sleep_for(std::chrono::milliseconds(500));
366 // tick every 5ms. Despite tick == 2 is slow, later ticks should be fast
367 // enough to catch back up to schedule
368 EXPECT_NEAR(100, ticks.load(), 10);
371 TEST(FunctionScheduler, UniformDistribution) {
373 const int kTicks = 2;
374 std::chrono::milliseconds minInterval =
375 testInterval(kTicks) - (timeFactor / 5);
376 std::chrono::milliseconds maxInterval =
377 testInterval(kTicks) + (timeFactor / 5);
378 FunctionScheduler fs;
379 fs.addFunctionUniformDistribution([&] { total += 2; },
382 "UniformDistribution",
383 std::chrono::milliseconds(0));
396 TEST(FunctionScheduler, ExponentialBackoff) {
398 int expectedInterval = 0;
399 int nextInterval = 2;
400 FunctionScheduler fs;
401 fs.addFunctionGenericDistribution(
403 [&expectedInterval, nextInterval]() mutable {
404 expectedInterval = nextInterval;
405 nextInterval *= nextInterval;
406 return testInterval(expectedInterval);
408 "ExponentialBackoff",
410 std::chrono::milliseconds(0));
414 delay(expectedInterval);
416 delay(expectedInterval);
423 TEST(FunctionScheduler, GammaIntervalDistribution) {
425 int expectedInterval = 0;
426 FunctionScheduler fs;
427 std::default_random_engine generator(folly::Random::rand32());
428 // The alpha and beta arguments are selected, somewhat randomly, to be 2.0.
429 // These values do not matter much in this test, as we are not testing the
430 // std::gamma_distribution itself...
431 std::gamma_distribution<double> gamma(2.0, 2.0);
432 fs.addFunctionGenericDistribution(
434 [&expectedInterval, generator, gamma]() mutable {
436 getTicksWithinRange(static_cast<int>(gamma(generator)), 2, 10);
437 return testInterval(expectedInterval);
440 "gamma(2.0,2.0)*100ms",
441 std::chrono::milliseconds(0));
445 delay(expectedInterval);
447 delay(expectedInterval);
454 TEST(FunctionScheduler, AddWithRunOnce) {
456 FunctionScheduler fs;
457 fs.addFunctionOnce([&] { total += 2; }, "add2");
464 fs.addFunctionOnce([&] { total += 2; }, "add2");
473 TEST(FunctionScheduler, cancelFunctionAndWait) {
475 FunctionScheduler fs;
486 EXPECT_EQ(0, total); // add2 is still sleeping
488 EXPECT_TRUE(fs.cancelFunctionAndWait("add2"));
489 EXPECT_EQ(2, total); // add2 should have completed
491 EXPECT_FALSE(fs.cancelFunction("add2")); // add2 has been canceled
495 #if defined(__linux__)
498 * A helper class that forces our pthread_create() wrapper to fail when
499 * an PThreadCreateFailure object exists.
501 class PThreadCreateFailure {
503 PThreadCreateFailure() {
506 ~PThreadCreateFailure() {
510 static bool shouldFail() {
511 return forceFailure_ > 0;
515 static std::atomic<int> forceFailure_;
518 std::atomic<int> PThreadCreateFailure::forceFailure_{0};
521 // Replace the system pthread_create() function with our own stub, so we can
522 // trigger failures in the StartThrows() test.
523 extern "C" int pthread_create(
525 const pthread_attr_t* attr,
526 void* (*start_routine)(void*),
528 static const auto realFunction = reinterpret_cast<decltype(&pthread_create)>(
529 dlsym(RTLD_NEXT, "pthread_create"));
530 // For sanity, make sure we didn't find ourself,
531 // since that would cause infinite recursion.
532 CHECK_NE(realFunction, pthread_create);
534 if (PThreadCreateFailure::shouldFail()) {
538 return realFunction(thread, attr, start_routine, arg);
541 TEST(FunctionScheduler, StartThrows) {
542 FunctionScheduler fs;
543 PThreadCreateFailure fail;
544 EXPECT_ANY_THROW(fs.start());
545 EXPECT_NO_THROW(fs.shutdown());
549 TEST(FunctionScheduler, cancelAllFunctionsAndWait) {
551 FunctionScheduler fs;
563 EXPECT_EQ(0, total); // add2 is still sleeping
565 fs.cancelAllFunctionsAndWait();
568 EXPECT_FALSE(fs.cancelFunction("add2")); // add2 has been canceled
572 TEST(FunctionScheduler, CancelAndWaitOnRunningFunc) {
573 folly::Baton<> baton;
574 std::thread th([&baton]() {
575 FunctionScheduler fs;
576 fs.addFunction([] { delay(10); }, testInterval(2), "func");
579 EXPECT_TRUE(fs.cancelFunctionAndWait("func"));
583 ASSERT_TRUE(baton.timed_wait(testInterval(15)));
587 TEST(FunctionScheduler, CancelAllAndWaitWithRunningFunc) {
588 folly::Baton<> baton;
589 std::thread th([&baton]() {
590 FunctionScheduler fs;
591 fs.addFunction([] { delay(10); }, testInterval(2), "func");
594 fs.cancelAllFunctionsAndWait();
598 ASSERT_TRUE(baton.timed_wait(testInterval(15)));
602 TEST(FunctionScheduler, CancelAllAndWaitWithOneRunningAndOneWaiting) {
603 folly::Baton<> baton;
604 std::thread th([&baton]() {
605 std::atomic<int> nExecuted(0);
606 FunctionScheduler fs;
624 fs.cancelAllFunctionsAndWait();
625 EXPECT_EQ(nExecuted, 1);
629 ASSERT_TRUE(baton.timed_wait(testInterval(15)));
633 TEST(FunctionScheduler, ConcurrentCancelFunctionAndWait) {
634 FunctionScheduler fs;
635 fs.addFunction([] { delay(10); }, testInterval(2), "func");
639 std::thread th1([&fs] { EXPECT_TRUE(fs.cancelFunctionAndWait("func")); });
641 std::thread th2([&fs] { EXPECT_FALSE(fs.cancelFunctionAndWait("func")); });