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 | |
25 | using namespace std::chrono; |
26 | using folly::SerialExecutor; |
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 | } |
158 | |