proxygen
SerialExecutorTest.cpp
Go to the documentation of this file.
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 
24 
25 using namespace std::chrono;
27 
28 namespace {
29 void burnMs(uint64_t ms) {
30  /* sleep override */ std::this_thread::sleep_for(milliseconds(ms));
31 }
32 } // namespace
33 
34 void 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 
59 TEST(SerialExecutor, Simple) {
60  SimpleTest(std::make_shared<folly::CPUThreadPoolExecutor>(4));
61 }
62 TEST(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
69 TEST(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 
106 void 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 
144 TEST(SerialExecutor, RecursiveAdd) {
145  RecursiveAddTest(std::make_shared<folly::CPUThreadPoolExecutor>(4));
146 }
147 TEST(SerialExecutor, RecursiveAddInline) {
148  RecursiveAddTest(std::make_shared<folly::InlineExecutor>());
149 }
150 
151 TEST(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 }
void RecursiveAddTest(std::shared_ptr< folly::Executor > const &parent)
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
PUSHMI_INLINE_VAR constexpr __adl::get_executor_fn executor
TEST(SerialExecutor, Simple)
FOLLY_ALWAYS_INLINE void wait(const WaitOptions &opt=wait_options()) noexcept
Definition: Baton.h:170
void SimpleTest(std::shared_ptr< folly::Executor > const &parent)
void post() noexcept
Definition: Baton.h:123
static Func burnMs(uint64_t ms)
Executor that guarantees serial non-concurrent execution of added tasks.
Executor::KeepAlive< ExecutorT > getKeepAliveToken(ExecutorT *executor)
Definition: Executor.h:200
void reset() noexcept
Definition: Baton.h:96
folly::Function< void()> parent
Definition: AtFork.cpp:34
std::vector< int > values(1'000)