1/*
2 * Copyright 2017-present Facebook, Inc.
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17#include <chrono>
18
19#include <folly/executors/CPUThreadPoolExecutor.h>
20#include <folly/executors/InlineExecutor.h>
21#include <folly/executors/SerialExecutor.h>
22#include <folly/portability/GTest.h>
23#include <folly/synchronization/Baton.h>
24
25using namespace std::chrono;
26using folly::SerialExecutor;
27
28namespace {
29void burnMs(uint64_t ms) {
30 /* sleep override */ std::this_thread::sleep_for(milliseconds(ms));
31}
32} // namespace
33
34void SimpleTest(std::shared_ptr<folly::Executor> const& parent) {
35 auto executor =
36 SerialExecutor::create(folly::getKeepAliveToken(parent.get()));
37
38 std::vector<int> values;
39 std::vector<int> expected;
40
41 for (int i = 0; i < 20; ++i) {
42 executor->add([i, &values] {
43 // make this extra vulnerable to concurrent execution
44 values.push_back(0);
45 burnMs(10);
46 values.back() = i;
47 });
48 expected.push_back(i);
49 }
50
51 // wait until last task has executed
52 folly::Baton<> finished_baton;
53 executor->add([&finished_baton] { finished_baton.post(); });
54 finished_baton.wait();
55
56 EXPECT_EQ(expected, values);
57}
58
59TEST(SerialExecutor, Simple) {
60 SimpleTest(std::make_shared<folly::CPUThreadPoolExecutor>(4));
61}
62TEST(SerialExecutor, SimpleInline) {
63 SimpleTest(std::make_shared<folly::InlineExecutor>());
64}
65
66// The Afterlife test only works with an asynchronous executor (not the
67// InlineExecutor), because we want execution of tasks to happen after we
68// destroy the SerialExecutor
69TEST(SerialExecutor, Afterlife) {
70 auto cpu_executor = std::make_shared<folly::CPUThreadPoolExecutor>(4);
71 auto executor =
72 SerialExecutor::create(folly::getKeepAliveToken(cpu_executor.get()));
73
74 // block executor until we call start_baton.post()
75 folly::Baton<> start_baton;
76 executor->add([&start_baton] { start_baton.wait(); });
77
78 std::vector<int> values;
79 std::vector<int> expected;
80
81 for (int i = 0; i < 20; ++i) {
82 executor->add([i, &values] {
83 // make this extra vulnerable to concurrent execution
84 values.push_back(0);
85 burnMs(10);
86 values.back() = i;
87 });
88 expected.push_back(i);
89 }
90
91 folly::Baton<> finished_baton;
92 executor->add([&finished_baton] { finished_baton.post(); });
93
94 // destroy SerialExecutor
95 executor.reset();
96
97 // now kick off the tasks
98 start_baton.post();
99
100 // wait until last task has executed
101 finished_baton.wait();
102
103 EXPECT_EQ(expected, values);
104}
105
106void RecursiveAddTest(std::shared_ptr<folly::Executor> const& parent) {
107 auto executor =
108 SerialExecutor::create(folly::getKeepAliveToken(parent.get()));
109
110 folly::Baton<> finished_baton;
111
112 std::vector<int> values;
113 std::vector<int> expected = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}};
114
115 int i = 0;
116 std::function<void()> lambda = [&] {
117 if (i < 10) {
118 // make this extra vulnerable to concurrent execution
119 values.push_back(0);
120 burnMs(10);
121 values.back() = i;
122 executor->add(lambda);
123 } else if (i < 12) {
124 // Below we will post this lambda three times to the executor. When
125 // executed, the lambda will re-post itself during the first ten
126 // executions. Afterwards we do nothing twice (this else-branch), and
127 // then on the 13th execution signal that we are finished.
128 } else {
129 finished_baton.post();
130 }
131 ++i;
132 };
133
134 executor->add(lambda);
135 executor->add(lambda);
136 executor->add(lambda);
137
138 // wait until last task has executed
139 finished_baton.wait();
140
141 EXPECT_EQ(expected, values);
142}
143
144TEST(SerialExecutor, RecursiveAdd) {
145 RecursiveAddTest(std::make_shared<folly::CPUThreadPoolExecutor>(4));
146}
147TEST(SerialExecutor, RecursiveAddInline) {
148 RecursiveAddTest(std::make_shared<folly::InlineExecutor>());
149}
150
151TEST(SerialExecutor, ExecutionThrows) {
152 auto executor = SerialExecutor::create();
153
154 // an empty Func will throw std::bad_function_call when invoked,
155 // but SerialExecutor should catch that exception
156 executor->add(folly::Func{});
157}
158