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>
19 #include <gtest/gtest.h>
21 using namespace folly;
22 using std::chrono::milliseconds;
27 * Helper functions for controlling how long this test takes.
29 * Using larger intervals here will make the tests less flaky when run on
30 * heavily loaded systems. However, this will also make the tests take longer
33 static const auto timeFactor = std::chrono::milliseconds(100);
34 std::chrono::milliseconds testInterval(int n) {
35 return n * timeFactor;
38 std::chrono::microseconds usec(n * timeFactor);
42 } // unnamed namespace
44 TEST(FunctionScheduler, SimpleAdd) {
47 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
56 TEST(FunctionScheduler, AddCancel) {
59 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
65 EXPECT_TRUE(fs.cancelFunction("add2"));
66 EXPECT_FALSE(fs.cancelFunction("NO SUCH FUNC"));
69 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
70 EXPECT_FALSE(fs.start()); // already running
78 TEST(FunctionScheduler, AddCancel2) {
82 // Test adds and cancels while the scheduler is stopped
83 EXPECT_FALSE(fs.cancelFunction("add2"));
84 fs.addFunction([&] { total += 1; }, testInterval(2), "add2");
85 EXPECT_TRUE(fs.cancelFunction("add2"));
86 EXPECT_FALSE(fs.cancelFunction("add2"));
87 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
88 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
95 // Cancel add2 while the scheduler is running
96 EXPECT_TRUE(fs.cancelFunction("add2"));
97 EXPECT_FALSE(fs.cancelFunction("add2"));
98 EXPECT_FALSE(fs.cancelFunction("bogus"));
102 EXPECT_TRUE(fs.cancelFunction("add3"));
104 // Test a function that cancels itself
105 int selfCancelCount = 0;
109 if (selfCancelCount > 2) {
110 fs.cancelFunction("selfCancel");
113 testInterval(1), "selfCancel", testInterval(1));
115 EXPECT_EQ(3, selfCancelCount);
116 EXPECT_FALSE(fs.cancelFunction("selfCancel"));
118 // Test a function that schedules another function
121 auto fn2 = [&] { ++fn2Count; };
124 if (adderCount == 2) {
125 fs.addFunction(fn2, testInterval(3), "fn2", testInterval(2));
128 fs.addFunction(fnAdder, testInterval(4), "adder");
131 EXPECT_EQ(1, adderCount);
132 EXPECT_EQ(0, fn2Count);
133 // t4: adder fires, schedules fn2
135 EXPECT_EQ(2, adderCount);
136 EXPECT_EQ(0, fn2Count);
139 EXPECT_EQ(2, adderCount);
140 EXPECT_EQ(1, fn2Count);
144 EXPECT_EQ(3, adderCount);
145 EXPECT_EQ(2, fn2Count);
146 EXPECT_TRUE(fs.cancelFunction("fn2"));
147 EXPECT_TRUE(fs.cancelFunction("adder"));
149 EXPECT_EQ(3, adderCount);
150 EXPECT_EQ(2, fn2Count);
153 EXPECT_EQ(3, selfCancelCount);
156 TEST(FunctionScheduler, AddMultiple) {
158 FunctionScheduler fs;
159 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
160 fs.addFunction([&] { total += 3; }, testInterval(3), "add3");
161 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(2), "add2"),
162 std::invalid_argument); // function name already exists
168 EXPECT_EQ(12, total);
169 EXPECT_TRUE(fs.cancelFunction("add2"));
171 EXPECT_EQ(15, total);
174 EXPECT_EQ(15, total);
178 TEST(FunctionScheduler, AddAfterStart) {
180 FunctionScheduler fs;
181 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
182 fs.addFunction([&] { total += 3; }, testInterval(2), "add3");
185 EXPECT_EQ(10, total);
186 fs.addFunction([&] { total += 2; }, testInterval(3), "add22");
188 EXPECT_EQ(17, total);
191 TEST(FunctionScheduler, ShutdownStart) {
193 FunctionScheduler fs;
194 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
201 EXPECT_FALSE(fs.cancelFunction("add3")); // non existing
206 TEST(FunctionScheduler, AddInvalid) {
208 FunctionScheduler fs;
209 // interval may not be negative
210 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(-1), "add2"),
211 std::invalid_argument);
213 EXPECT_FALSE(fs.cancelFunction("addNoFunc"));
216 TEST(FunctionScheduler, NoFunctions) {
217 FunctionScheduler fs;
218 EXPECT_TRUE(fs.start());
220 FunctionScheduler fs2;
224 TEST(FunctionScheduler, AddWhileRunning) {
226 FunctionScheduler fs;
229 fs.addFunction([&] { total += 2; }, testInterval(2), "add2");
230 // The function should be invoked nearly immediately when we add it
231 // and the FunctionScheduler is already running
238 TEST(FunctionScheduler, NoShutdown) {
241 FunctionScheduler fs;
242 fs.addFunction([&] { total += 2; }, testInterval(1), "add2");
247 // Destroyed the FunctionScheduler without calling shutdown.
248 // Everything should have been cleaned up, and the function will no longer
254 TEST(FunctionScheduler, StartDelay) {
256 FunctionScheduler fs;
257 fs.addFunction([&] { total += 2; }, testInterval(2), "add2",
259 fs.addFunction([&] { total += 3; }, testInterval(3), "add3",
261 EXPECT_THROW(fs.addFunction([&] { total += 2; }, testInterval(3),
262 "addX", testInterval(-1)),
263 std::invalid_argument);
271 // t4 : add2: total=7
272 // t5 : add3: total=10
273 // t6 : add2: total=12
275 EXPECT_EQ(12, total);
276 fs.cancelFunction("add2");
277 // t8 : add3: total=15
279 EXPECT_EQ(15, total);
282 EXPECT_EQ(15, total);
286 TEST(FunctionScheduler, NoSteadyCatchup) {
287 std::atomic<int> ticks(0);
288 FunctionScheduler fs;
289 // fs.setSteady(false); is the default
290 fs.addFunction([&ticks] {
292 std::this_thread::sleep_for(
293 std::chrono::milliseconds(200));
298 std::this_thread::sleep_for(std::chrono::milliseconds(500));
300 // no steady catch up means we'd tick once for 200ms, then remaining
301 // 300ms / 5 = 60 times
302 EXPECT_LE(ticks.load(), 61);
305 TEST(FunctionScheduler, SteadyCatchup) {
306 std::atomic<int> ticks(0);
307 FunctionScheduler fs;
309 fs.addFunction([&ticks] {
311 std::this_thread::sleep_for(
312 std::chrono::milliseconds(200));
318 std::this_thread::sleep_for(std::chrono::milliseconds(500));
320 // tick every 5ms. Despite tick == 2 is slow, later ticks should be fast
321 // enough to catch back up to schedule
322 EXPECT_NEAR(100, ticks.load(), 10);