proxygen
CoroTest.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 <folly/Portability.h>
18 
19 #if FOLLY_HAS_COROUTINES
20 
21 #include <folly/Chrono.h>
28 
29 using namespace folly;
30 
31 coro::Task<int> task42() {
32  co_return 42;
33 }
34 
35 TEST(Coro, Basic) {
37  auto future = task42().scheduleOn(&executor).start();
38 
39  EXPECT_FALSE(future.isReady());
40 
41  executor.drive();
42 
43  EXPECT_TRUE(future.isReady());
44  EXPECT_EQ(42, std::move(future).get());
45 }
46 
47 TEST(Coro, BasicFuture) {
49 
50  auto future = task42().scheduleOn(&executor).start();
51 
52  EXPECT_FALSE(future.isReady());
53 
54  EXPECT_EQ(42, std::move(future).via(&executor).getVia(&executor));
55 }
56 
57 coro::Task<void> taskVoid() {
58  (void)co_await task42();
59  co_return;
60 }
61 
62 TEST(Coro, Basic2) {
64  auto future = taskVoid().scheduleOn(&executor).start();
65 
66  EXPECT_FALSE(future.isReady());
67 
68  executor.drive();
69 
70  EXPECT_TRUE(future.isReady());
71 }
72 
73 TEST(Coro, TaskOfMoveOnly) {
74  auto f = []() -> coro::Task<std::unique_ptr<int>> {
75  co_return std::make_unique<int>(123);
76  };
77 
78  auto p = coro::blockingWait(f().scheduleOn(&InlineExecutor::instance()));
79  EXPECT_TRUE(p);
80  EXPECT_EQ(123, *p);
81 }
82 
83 coro::Task<void> taskSleep() {
84  (void)co_await futures::sleep(std::chrono::seconds{1});
85  co_return;
86 }
87 
88 TEST(Coro, Sleep) {
89  ScopedEventBaseThread evbThread;
90 
91  auto startTime = std::chrono::steady_clock::now();
92  auto task = taskSleep().scheduleOn(evbThread.getEventBase());
93 
95 
96  // The total time should be roughly 1 second. Some builds, especially
97  // optimized ones, may result in slightly less than 1 second, so we perform
98  // rounding here.
99  auto totalTime = std::chrono::steady_clock::now() - startTime;
100  EXPECT_GE(
101  chrono::round<std::chrono::seconds>(totalTime), std::chrono::seconds{1});
102 }
103 
104 coro::Task<int> taskException() {
105  throw std::runtime_error("Test exception");
106  co_return 42;
107 }
108 
109 TEST(Coro, Throw) {
111  auto future = taskException().scheduleOn(&executor).start();
112 
113  EXPECT_FALSE(future.isReady());
114 
115  executor.drive();
116 
117  EXPECT_TRUE(future.isReady());
118  EXPECT_THROW(std::move(future).get(), std::runtime_error);
119 }
120 
121 TEST(Coro, FutureThrow) {
123  auto future = taskException().scheduleOn(&executor).start();
124 
125  EXPECT_FALSE(future.isReady());
126 
127  executor.drive();
128 
129  EXPECT_TRUE(future.isReady());
130  EXPECT_THROW(std::move(future).get(), std::runtime_error);
131 }
132 
133 coro::Task<int> taskRecursion(int depth) {
134  if (depth > 0) {
135  EXPECT_EQ(depth - 1, co_await taskRecursion(depth - 1));
136  } else {
137  (void)co_await futures::sleep(std::chrono::seconds{1});
138  }
139 
140  co_return depth;
141 }
142 
143 TEST(Coro, LargeStack) {
144  ScopedEventBaseThread evbThread;
145  auto task = taskRecursion(5000).scheduleOn(evbThread.getEventBase());
146 
147  EXPECT_EQ(5000, coro::blockingWait(std::move(task)));
148 }
149 
150 #if defined(__clang__)
151 #define FOLLY_CORO_DONT_OPTIMISE_ON_CLANG __attribute__((optnone))
152 #else
153 #define FOLLY_CORO_DONT_OPTIMISE_ON_CLANG
154 #endif
155 
156 coro::Task<void> taskThreadNested(std::thread::id threadId) {
157  EXPECT_EQ(threadId, std::this_thread::get_id());
158  (void)co_await futures::sleep(std::chrono::seconds{1});
159  EXPECT_EQ(threadId, std::this_thread::get_id());
160  co_return;
161 }
162 
163 coro::Task<int> taskThread() FOLLY_CORO_DONT_OPTIMISE_ON_CLANG {
164  auto threadId = std::this_thread::get_id();
165 
166  // BUG: Under @mode/clang-opt builds this object is placed on the coroutine
167  // frame and the code for the constructor assumes that it is allocated on
168  // a 16-byte boundary. However, when placed in the coroutine frame it can
169  // end up at a location that is not 16-byte aligned. This causes a SIGSEGV
170  // when performing a store to members that uses SSE instructions.
172 
173  co_await taskThreadNested(evbThread.getThreadId())
174  .scheduleOn(evbThread.getEventBase());
175 
176  EXPECT_EQ(threadId, std::this_thread::get_id());
177 
178  co_return 42;
179 }
180 
181 TEST(Coro, NestedThreads) {
182  ScopedEventBaseThread evbThread;
183  auto task = taskThread().scheduleOn(evbThread.getEventBase());
185 }
186 
187 coro::Task<int> taskGetCurrentExecutor(Executor* executor) {
188  auto currentExecutor = co_await coro::getCurrentExecutor();
189  EXPECT_EQ(executor, currentExecutor);
190  co_return co_await task42().scheduleOn(currentExecutor);
191 }
192 
193 TEST(Coro, CurrentExecutor) {
194  ScopedEventBaseThread evbThread;
195  auto task = taskGetCurrentExecutor(evbThread.getEventBase())
196  .scheduleOn(evbThread.getEventBase());
198 }
199 
200 coro::Task<void> taskTimedWait() {
201  auto fastFuture =
202  futures::sleep(std::chrono::milliseconds{50}).thenValue([](Unit) {
203  return 42;
204  });
205  auto fastResult = co_await coro::timed_wait(
206  std::move(fastFuture), std::chrono::milliseconds{100});
207  EXPECT_TRUE(fastResult);
208  EXPECT_EQ(42, *fastResult);
209 
210  struct ExpectedException : public std::runtime_error {
211  ExpectedException() : std::runtime_error("ExpectedException") {}
212  };
213 
214  auto throwingFuture =
215  futures::sleep(std::chrono::milliseconds{50}).thenValue([](Unit) {
216  throw ExpectedException();
217  });
218  EXPECT_THROW(
219  (void)co_await coro::timed_wait(
220  std::move(throwingFuture), std::chrono::milliseconds{100}),
221  ExpectedException);
222 
223  auto slowFuture =
224  futures::sleep(std::chrono::milliseconds{200}).thenValue([](Unit) {
225  return 42;
226  });
227  auto slowResult = co_await coro::timed_wait(
228  std::move(slowFuture), std::chrono::milliseconds{100});
229  EXPECT_FALSE(slowResult);
230 
231  co_return;
232 }
233 
234 TEST(Coro, TimedWait) {
236  taskTimedWait().scheduleOn(&executor).start().via(&executor).getVia(
237  &executor);
238 }
239 
240 template <int value>
241 struct AwaitableInt {
242  bool await_ready() const {
243  return true;
244  }
245 
246  bool await_suspend(std::experimental::coroutine_handle<>) {
247  LOG(FATAL) << "Should never be called.";
248  }
249 
250  int await_resume() {
251  return value;
252  }
253 };
254 
255 struct AwaitableWithOperator {};
256 
257 AwaitableInt<42> operator co_await(const AwaitableWithOperator&) {
258  return {};
259 }
260 
261 coro::Task<int> taskAwaitableWithOperator() {
262  co_return co_await AwaitableWithOperator();
263 }
264 
265 TEST(Coro, AwaitableWithOperator) {
267  EXPECT_EQ(
268  42,
269  taskAwaitableWithOperator()
270  .scheduleOn(&executor)
271  .start()
272  .via(&executor)
273  .getVia(&executor));
274 }
275 
276 struct AwaitableWithMemberOperator {
277  AwaitableInt<42> operator co_await() {
278  return {};
279  }
280 };
281 
282 AwaitableInt<24> operator co_await(const AwaitableWithMemberOperator&) {
283  return {};
284 }
285 
286 coro::Task<int> taskAwaitableWithMemberOperator() {
287  co_return co_await AwaitableWithMemberOperator();
288 }
289 
290 TEST(Coro, AwaitableWithMemberOperator) {
292  EXPECT_EQ(
293  42,
294  taskAwaitableWithMemberOperator()
295  .scheduleOn(&executor)
296  .start()
297  .via(&executor)
298  .getVia(&executor));
299 }
300 
301 coro::Task<int> taskBaton(fibers::Baton& baton) {
302  co_await baton;
303  co_return 42;
304 }
305 
306 TEST(Coro, Baton) {
308  fibers::Baton baton;
309  auto future = taskBaton(baton).scheduleOn(&executor).start();
310 
311  EXPECT_FALSE(future.isReady());
312 
313  executor.run();
314 
315  EXPECT_FALSE(future.isReady());
316 
317  baton.post();
318  executor.run();
319 
320  EXPECT_TRUE(future.isReady());
321  EXPECT_EQ(42, std::move(future).get());
322 }
323 
324 template <class Type>
325 coro::Task<Type> taskFuture(Type value) {
326  co_return co_await folly::makeFuture<Type>(std::move(value));
327 }
328 
329 TEST(Coro, FulfilledFuture) {
331  auto value =
332  taskFuture(42).scheduleOn(&executor).start().via(&executor).getVia(
333  &executor);
334  EXPECT_EQ(42, value);
335 }
336 
337 TEST(Coro, MoveOnlyReturn) {
339  auto value = taskFuture(std::make_unique<int>(42))
340  .scheduleOn(&executor)
341  .start()
342  .via(&executor)
343  .getVia(&executor);
344  EXPECT_EQ(42, *value);
345 }
346 #endif
auto f
#define EXPECT_THROW(statement, expected_exception)
Definition: gtest.h:1843
#define EXPECT_EQ(val1, val2)
Definition: gtest.h:1922
Future< Unit > sleep(Duration dur, Timekeeper *tk)
Definition: Future.cpp:42
std::chrono::steady_clock::time_point now()
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
STL namespace.
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
TimedWaitAwaitable< std::decay_t< Awaitable > > timed_wait(Awaitable &&awaitable, std::chrono::milliseconds duration)
Definition: Utils.h:151
#define EXPECT_GE(val1, val2)
Definition: gtest.h:1932
PUSHMI_INLINE_VAR constexpr __adl::get_executor_fn executor
void drive() override
Implements DrivableExecutor.
FOLLY_ATTR_VISIBILITY_HIDDEN static FOLLY_ALWAYS_INLINE InlineExecutor & instance() noexcept
std::thread::id getThreadId() const
auto start
#define EXPECT_TRUE(condition)
Definition: gtest.h:1859
uint64_t value(const typename LockFreeRingBuffer< T, Atom >::Cursor &rbcursor)
auto via(Executor *x, Func &&func) -> Future< typename isFutureOrSemiFuture< decltype(std::declval< Func >()())>::Inner >
Definition: Future-inl.h:1290
#define EXPECT_FALSE(condition)
Definition: gtest.h:1862
TEST(SequencedExecutor, CPUThreadPoolExecutor)
auto blockingWait(Awaitable &&awaitable) -> detail::decay_rvalue_reference_t< await_result_t< Awaitable >>
Definition: BlockingWait.h:313