1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
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 #include <folly/CancellationToken.h>
20 #include <folly/Chrono.h>
21 #include <folly/executors/ManualExecutor.h>
22 #include <folly/experimental/coro/Baton.h>
23 #include <folly/experimental/coro/BlockingWait.h>
24 #include <folly/experimental/coro/Collect.h>
25 #include <folly/experimental/coro/Coroutine.h>
26 #include <folly/experimental/coro/CurrentExecutor.h>
27 #include <folly/experimental/coro/DetachOnCancel.h>
28 #include <folly/experimental/coro/Invoke.h>
29 #include <folly/experimental/coro/Sleep.h>
30 #include <folly/experimental/coro/Task.h>
31 #include <folly/experimental/coro/TimedWait.h>
32 #include <folly/experimental/coro/WithCancellation.h>
33 #include <folly/fibers/Semaphore.h>
34 #include <folly/futures/Future.h>
35 #include <folly/io/async/ScopedEventBaseThread.h>
36 #include <folly/lang/Assume.h>
37 #include <folly/portability/GTest.h>
38
39 #if FOLLY_HAS_COROUTINES
40
41 using namespace folly;
42
43 class CoroTest : public testing::Test {};
44
TEST_F(CoroTest,Basic)45 TEST_F(CoroTest, Basic) {
46 ManualExecutor executor;
47 auto task42 = []() -> coro::Task<int> { co_return 42; };
48 auto future = task42().scheduleOn(&executor).start();
49
50 EXPECT_FALSE(future.isReady());
51
52 executor.drain();
53
54 EXPECT_TRUE(future.isReady());
55 EXPECT_EQ(42, std::move(future).get());
56 }
57
TEST_F(CoroTest,BasicSemiFuture)58 TEST_F(CoroTest, BasicSemiFuture) {
59 ManualExecutor executor;
60 auto task42 = []() -> coro::Task<int> { co_return 42; };
61 auto future = task42().semi().via(&executor);
62
63 EXPECT_FALSE(future.isReady());
64
65 executor.drain();
66
67 EXPECT_TRUE(future.isReady());
68 EXPECT_EQ(42, std::move(future).get());
69 }
70
TEST_F(CoroTest,BasicFuture)71 TEST_F(CoroTest, BasicFuture) {
72 ManualExecutor executor;
73
74 auto task42 = []() -> coro::Task<int> { co_return 42; };
75 auto future = task42().scheduleOn(&executor).start();
76
77 EXPECT_FALSE(future.isReady());
78
79 EXPECT_EQ(42, std::move(future).via(&executor).getVia(&executor));
80 }
81
taskVoid()82 coro::Task<void> taskVoid() {
83 auto task42 = []() -> coro::Task<int> { co_return 42; };
84 (void)co_await task42();
85 co_return;
86 }
87
TEST_F(CoroTest,Basic2)88 TEST_F(CoroTest, Basic2) {
89 ManualExecutor executor;
90 auto future = taskVoid().scheduleOn(&executor).start();
91
92 EXPECT_FALSE(future.isReady());
93
94 executor.drain();
95
96 EXPECT_TRUE(future.isReady());
97 }
98
TEST_F(CoroTest,TaskOfMoveOnly)99 TEST_F(CoroTest, TaskOfMoveOnly) {
100 auto f = []() -> coro::Task<std::unique_ptr<int>> {
101 co_return std::make_unique<int>(123);
102 };
103
104 auto p = coro::blockingWait(f());
105 EXPECT_TRUE(p);
106 EXPECT_EQ(123, *p);
107 }
108
taskSleep()109 coro::Task<void> taskSleep() {
110 (void)co_await coro::sleep(std::chrono::seconds{1});
111 co_return;
112 }
113
TEST_F(CoroTest,Sleep)114 TEST_F(CoroTest, Sleep) {
115 ScopedEventBaseThread evbThread;
116
117 auto startTime = std::chrono::steady_clock::now();
118 auto task = taskSleep().scheduleOn(evbThread.getEventBase());
119
120 coro::blockingWait(std::move(task));
121
122 // The total time should be roughly 1 second. Some builds, especially
123 // optimized ones, may result in slightly less than 1 second, so we perform
124 // rounding here.
125 auto totalTime = std::chrono::steady_clock::now() - startTime;
126 EXPECT_GE(
127 chrono::round<std::chrono::seconds>(totalTime), std::chrono::seconds{1});
128 }
129
TEST_F(CoroTest,ExecutorKeepAlive)130 TEST_F(CoroTest, ExecutorKeepAlive) {
131 auto future = [] {
132 ScopedEventBaseThread evbThread;
133
134 return taskSleep().scheduleOn(evbThread.getEventBase()).start();
135 }();
136 EXPECT_TRUE(future.isReady());
137 }
138
TEST_F(CoroTest,ExecutorKeepAliveDummy)139 TEST_F(CoroTest, ExecutorKeepAliveDummy) {
140 struct CountingExecutor : public ManualExecutor {
141 bool keepAliveAcquire() noexcept override {
142 ++keepAliveCounter;
143 return true;
144 }
145 void keepAliveRelease() noexcept override { --keepAliveCounter; }
146
147 size_t keepAliveCounter{0};
148 };
149
150 struct ExecutorRec {
151 static coro::Task<void> go(int depth) {
152 if (depth == 0) {
153 co_return;
154 }
155
156 auto executor =
157 dynamic_cast<CountingExecutor*>(co_await coro::co_current_executor);
158 DCHECK(executor);
159
160 // Note, extra keep-alives are being kept by the Futures.
161 EXPECT_EQ(3, executor->keepAliveCounter);
162
163 co_await go(depth - 1);
164 }
165 };
166
167 CountingExecutor executor;
168 ExecutorRec::go(42).scheduleOn(&executor).start().via(&executor).getVia(
169 &executor);
170 }
171
TEST_F(CoroTest,FutureThrow)172 TEST_F(CoroTest, FutureThrow) {
173 auto taskException = []() -> coro::Task<int> {
174 throw std::runtime_error("Test exception");
175 co_return 42;
176 };
177
178 ManualExecutor executor;
179 auto future = taskException().scheduleOn(&executor).start();
180
181 EXPECT_FALSE(future.isReady());
182
183 executor.drain();
184
185 EXPECT_TRUE(future.isReady());
186 EXPECT_THROW(std::move(future).get(), std::runtime_error);
187 }
188
taskRecursion(int depth)189 coro::Task<int> taskRecursion(int depth) {
190 if (depth > 0) {
191 EXPECT_EQ(depth - 1, co_await taskRecursion(depth - 1));
192 } else {
193 (void)co_await coro::sleep(std::chrono::seconds{1});
194 }
195
196 co_return depth;
197 }
198
TEST_F(CoroTest,LargeStack)199 TEST_F(CoroTest, LargeStack) {
200 ScopedEventBaseThread evbThread;
201 auto task = taskRecursion(50000).scheduleOn(evbThread.getEventBase());
202
203 EXPECT_EQ(50000, coro::blockingWait(std::move(task)));
204 }
205
TEST_F(CoroTest,NestedThreads)206 TEST_F(CoroTest, NestedThreads) {
207 auto taskThreadNested = [](std::thread::id threadId) -> coro::Task<void> {
208 EXPECT_EQ(threadId, std::this_thread::get_id());
209 (void)co_await coro::sleep(std::chrono::seconds{1});
210 EXPECT_EQ(threadId, std::this_thread::get_id());
211 co_return;
212 };
213
214 auto taskThread = [&]() -> coro::Task<int> {
215 auto threadId = std::this_thread::get_id();
216
217 // BUG: Under @mode/clang-opt builds this object is placed on the coroutine
218 // frame and the code for the constructor assumes that it is allocated on
219 // a 16-byte boundary. However, when placed in the coroutine frame it can
220 // end up at a location that is not 16-byte aligned. This causes a SIGSEGV
221 // when performing a store to members that uses SSE instructions.
222 folly::ScopedEventBaseThread evbThread;
223
224 co_await taskThreadNested(evbThread.getThreadId())
225 .scheduleOn(evbThread.getEventBase());
226
227 EXPECT_EQ(threadId, std::this_thread::get_id());
228
229 co_return 42;
230 };
231
232 ScopedEventBaseThread evbThread;
233 auto task = taskThread().scheduleOn(evbThread.getEventBase());
234 EXPECT_EQ(42, coro::blockingWait(std::move(task)));
235 }
236
TEST_F(CoroTest,CurrentExecutor)237 TEST_F(CoroTest, CurrentExecutor) {
238 auto taskGetCurrentExecutor = [](Executor* executor) -> coro::Task<int> {
239 auto current = co_await coro::co_current_executor;
240 EXPECT_EQ(executor, current);
241 auto task42 = []() -> coro::Task<int> { co_return 42; };
242 co_return co_await task42().scheduleOn(current);
243 };
244
245 ScopedEventBaseThread evbThread;
246 auto task = taskGetCurrentExecutor(evbThread.getEventBase())
247 .scheduleOn(evbThread.getEventBase());
248 EXPECT_EQ(42, coro::blockingWait(std::move(task)));
249 }
250
TEST_F(CoroTest,TimedWaitFuture)251 TEST_F(CoroTest, TimedWaitFuture) {
252 auto taskTimedWaitFuture = []() -> coro::Task<void> {
253 auto ex = co_await coro::co_current_executor;
254 auto fastFuture = futures::sleep(std::chrono::milliseconds{50})
255 .via(ex)
256 .thenValue([](Unit) { return 42; });
257 auto fastResult = co_await coro::timed_wait(
258 std::move(fastFuture), std::chrono::milliseconds{100});
259 EXPECT_TRUE(fastResult);
260 EXPECT_EQ(42, *fastResult);
261
262 struct ExpectedException : public std::runtime_error {
263 ExpectedException() : std::runtime_error("ExpectedException") {}
264 };
265
266 auto throwingFuture =
267 futures::sleep(std::chrono::milliseconds{50})
268 .via(ex)
269 .thenValue([](Unit) { throw ExpectedException(); });
270 EXPECT_THROW(
271 (void)co_await coro::timed_wait(
272 std::move(throwingFuture), std::chrono::milliseconds{100}),
273 ExpectedException);
274
275 auto promiseFuturePair = folly::makePromiseContract<folly::Unit>(ex);
276 auto lifetimeFuture = std::move(promiseFuturePair.second);
277 auto slowFuture =
278 futures::sleep(std::chrono::milliseconds{200})
279 .via(ex)
280 .thenValue([lifetimePromise =
281 std::move(promiseFuturePair.first)](Unit) mutable {
282 lifetimePromise.setValue();
283 return 42;
284 });
285 auto slowResult = co_await coro::timed_wait(
286 std::move(slowFuture), std::chrono::milliseconds{100});
287 EXPECT_FALSE(slowResult);
288
289 // Ensure that task completes for safe executor lifetimes
290 (void)co_await std::move(lifetimeFuture);
291
292 co_return;
293 };
294
295 coro::blockingWait(taskTimedWaitFuture());
296 }
297
TEST_F(CoroTest,TimedWaitTask)298 TEST_F(CoroTest, TimedWaitTask) {
299 auto taskTimedWaitTask = []() -> coro::Task<void> {
300 auto fastTask = []() -> coro::Task<int> {
301 co_await coro::sleep(std::chrono::milliseconds{50});
302 co_return 42;
303 }();
304 auto fastResult = co_await coro::timed_wait(
305 std::move(fastTask), std::chrono::milliseconds{100});
306 EXPECT_TRUE(fastResult);
307 EXPECT_EQ(42, *fastResult);
308
309 struct ExpectedException : public std::runtime_error {
310 ExpectedException() : std::runtime_error("ExpectedException") {}
311 };
312
313 auto throwingTask = []() -> coro::Task<void> {
314 co_await coro::sleep(std::chrono::milliseconds{50});
315 throw ExpectedException();
316 }();
317 EXPECT_THROW(
318 (void)co_await coro::timed_wait(
319 std::move(throwingTask), std::chrono::milliseconds{100}),
320 ExpectedException);
321
322 auto slowTask = []() -> coro::Task<int> {
323 co_await futures::sleep(std::chrono::milliseconds{200});
324 co_return 42;
325 }();
326 auto slowResult = co_await coro::timed_wait(
327 std::move(slowTask), std::chrono::milliseconds{100});
328 EXPECT_FALSE(slowResult);
329
330 co_return;
331 };
332
333 coro::blockingWait(taskTimedWaitTask());
334 }
335
TEST_F(CoroTest,TimedWaitKeepAlive)336 TEST_F(CoroTest, TimedWaitKeepAlive) {
337 auto start = std::chrono::steady_clock::now();
338 coro::blockingWait([]() -> coro::Task<void> {
339 co_await coro::timed_wait(
340 coro::sleep(std::chrono::milliseconds{100}), std::chrono::seconds{60});
341 co_return;
342 }());
343 auto duration = std::chrono::steady_clock::now() - start;
344 EXPECT_LE(duration, std::chrono::seconds{30});
345 }
346
TEST_F(CoroTest,TimedWaitNonCopyable)347 TEST_F(CoroTest, TimedWaitNonCopyable) {
348 auto task = []() -> coro::Task<std::unique_ptr<int>> {
349 co_return std::make_unique<int>(42);
350 }();
351 EXPECT_EQ(
352 42,
353 **coro::blockingWait(
354 [&]() -> coro::Task<folly::Optional<std::unique_ptr<int>>> {
355 co_return co_await coro::timed_wait(
356 std::move(task), std::chrono::seconds{60});
357 }()));
358 }
359
360 namespace {
361
362 template <int value>
363 struct AwaitableInt {
await_ready__anon4b303c821711::AwaitableInt364 bool await_ready() const { return true; }
365
await_suspend__anon4b303c821711::AwaitableInt366 bool await_suspend(coro::coroutine_handle<>) { assume_unreachable(); }
367
await_resume__anon4b303c821711::AwaitableInt368 int await_resume() { return value; }
369 };
370
371 struct AwaitableWithOperator {};
372
operator co_await(const AwaitableWithOperator &)373 AwaitableInt<42> operator co_await(const AwaitableWithOperator&) {
374 return {};
375 }
376
377 struct AwaitableWithMemberOperator {
operator co_await__anon4b303c821711::AwaitableWithMemberOperator378 AwaitableInt<42> operator co_await() { return {}; }
379 };
380
operator co_await(const AwaitableWithMemberOperator &)381 FOLLY_MAYBE_UNUSED AwaitableInt<24> operator co_await(
382 const AwaitableWithMemberOperator&) {
383 return {};
384 }
385
386 } // namespace
387
TEST_F(CoroTest,AwaitableWithOperator)388 TEST_F(CoroTest, AwaitableWithOperator) {
389 auto taskAwaitableWithOperator = []() -> coro::Task<int> {
390 co_return co_await AwaitableWithOperator();
391 };
392
393 EXPECT_EQ(42, coro::blockingWait(taskAwaitableWithOperator()));
394 }
395
TEST_F(CoroTest,AwaitableWithMemberOperator)396 TEST_F(CoroTest, AwaitableWithMemberOperator) {
397 auto taskAwaitableWithMemberOperator = []() -> coro::Task<int> {
398 co_return co_await AwaitableWithMemberOperator();
399 };
400
401 EXPECT_EQ(42, coro::blockingWait(taskAwaitableWithMemberOperator()));
402 }
403
TEST_F(CoroTest,Baton)404 TEST_F(CoroTest, Baton) {
405 auto taskBaton = [](fibers::Baton& baton) -> coro::Task<int> {
406 co_await baton;
407 co_return 42;
408 };
409
410 ManualExecutor executor;
411 fibers::Baton baton;
412 auto future = taskBaton(baton).scheduleOn(&executor).start();
413
414 EXPECT_FALSE(future.isReady());
415
416 executor.drain();
417
418 EXPECT_FALSE(future.isReady());
419
420 baton.post();
421 executor.drain();
422
423 EXPECT_TRUE(future.isReady());
424 EXPECT_EQ(42, std::move(future).get());
425 }
426
TEST_F(CoroTest,FulfilledFuture)427 TEST_F(CoroTest, FulfilledFuture) {
428 auto taskFuture = [](auto value) -> coro::Task<decltype(value)> {
429 co_return co_await folly::makeFuture(std::move(value));
430 };
431
432 EXPECT_EQ(42, coro::blockingWait(taskFuture(42)));
433 }
434
TEST_F(CoroTest,MoveOnlyReturn)435 TEST_F(CoroTest, MoveOnlyReturn) {
436 auto taskFuture = [](auto value) -> coro::Task<decltype(value)> {
437 co_return co_await folly::makeFuture(std::move(value));
438 };
439
440 EXPECT_EQ(42, *coro::blockingWait(taskFuture(std::make_unique<int>(42))));
441 }
442
TEST_F(CoroTest,co_invoke)443 TEST_F(CoroTest, co_invoke) {
444 ManualExecutor executor;
445 Promise<folly::Unit> p;
446 auto coroFuture =
447 coro::co_invoke([f = p.getSemiFuture()]() mutable -> coro::Task<void> {
448 (void)co_await std::move(f);
449 co_return;
450 })
451 .scheduleOn(&executor)
452 .start();
453 executor.drain();
454 EXPECT_FALSE(coroFuture.isReady());
455
456 p.setValue(folly::unit);
457
458 executor.drain();
459 EXPECT_TRUE(coroFuture.isReady());
460 }
461
TEST_F(CoroTest,Semaphore)462 TEST_F(CoroTest, Semaphore) {
463 static constexpr size_t kTasks = 10;
464 static constexpr size_t kIterations = 10000;
465 static constexpr size_t kNumTokens = 10;
466 static constexpr size_t kNumThreads = 16;
467
468 fibers::Semaphore sem(kNumTokens);
469
470 struct Worker {
471 explicit Worker(fibers::Semaphore& s) : sem(s), t([&] { run(); }) {}
472
473 void run() {
474 folly::EventBase evb;
475
476 {
477 std::shared_ptr<folly::EventBase> completionCounter(
478 &evb, [](folly::EventBase* evb_) { evb_->terminateLoopSoon(); });
479
480 for (size_t i = 0; i < kTasks; ++i) {
481 coro::co_invoke([&, completionCounter]() -> coro::Task<void> {
482 for (size_t j = 0; j < kIterations; ++j) {
483 co_await sem.co_wait();
484 ++counter;
485 sem.signal();
486 --counter;
487
488 EXPECT_LT(counter, kNumTokens);
489 EXPECT_GE(counter, 0);
490 }
491 })
492 .scheduleOn(&evb)
493 .start();
494 }
495 }
496 evb.loopForever();
497 }
498
499 fibers::Semaphore& sem;
500 int counter{0};
501 std::thread t;
502 };
503
504 std::vector<Worker> workers;
505 workers.reserve(kNumThreads);
506 for (size_t i = 0; i < kNumThreads; ++i) {
507 workers.emplace_back(sem);
508 }
509
510 for (auto& worker : workers) {
511 worker.t.join();
512 }
513
514 for (auto& worker : workers) {
515 EXPECT_EQ(0, worker.counter);
516 }
517 }
518
TEST_F(CoroTest,SemaphoreWaitWhenCancellationAlreadyRequested)519 TEST_F(CoroTest, SemaphoreWaitWhenCancellationAlreadyRequested) {
520 folly::coro::blockingWait([&]() -> folly::coro::Task<> {
521 folly::CancellationSource cancelSource;
522 cancelSource.requestCancellation();
523
524 // Run some logic while in an already-cancelled state.
525 co_await folly::coro::co_withCancellation(
526 cancelSource.getToken(), []() -> folly::coro::Task<> {
527 folly::fibers::Semaphore sem{1};
528
529 // If in a signalled state then should complete normally
530 co_await sem.co_wait();
531
532 // And the semaphore should no longer be in the signalled state.
533
534 // But if not signalled then should complete with cancellation
535 // immediately.
536 EXPECT_THROW(co_await sem.co_wait(), folly::OperationCancelled);
537 }());
538 }());
539 }
540
TEST_F(CoroTest,CancelOutstandingSemaphoreWait)541 TEST_F(CoroTest, CancelOutstandingSemaphoreWait) {
542 struct ExpectedError : std::exception {};
543
544 folly::coro::blockingWait([&]() -> folly::coro::Task<> {
545 folly::fibers::Semaphore sem{0};
546 try {
547 co_await folly::coro::collectAll(
548 [&]() -> folly::coro::Task<> {
549 EXPECT_THROW(co_await sem.co_wait(), OperationCancelled);
550 }(),
551 []() -> folly::coro::Task<> {
552 co_await folly::coro::co_reschedule_on_current_executor;
553 // Completing the second task with an error will cause
554 // collectAll() to request cancellation of the other task
555 // whcih should reqeust cancellation of sem.co_wait().
556 co_yield folly::coro::co_error(ExpectedError{});
557 }());
558 } catch (const ExpectedError&) {
559 }
560 }());
561 }
562
TEST_F(CoroTest,CancelOneSemaphoreWaitDoesNotAffectOthers)563 TEST_F(CoroTest, CancelOneSemaphoreWaitDoesNotAffectOthers) {
564 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
565 folly::fibers::Semaphore sem{0};
566
567 folly::CancellationSource cancelSource;
568
569 co_await folly::coro::collectAll(
570 [&]() -> folly::coro::Task<> {
571 EXPECT_THROW(
572 (co_await folly::coro::co_withCancellation(
573 cancelSource.getToken(), sem.co_wait())),
574 OperationCancelled);
575 }(),
576 [&]() -> folly::coro::Task<> { co_await sem.co_wait(); }(),
577 [&]() -> folly::coro::Task<> {
578 co_await folly::coro::co_reschedule_on_current_executor;
579 cancelSource.requestCancellation();
580 sem.signal();
581 }());
582 }());
583 }
584
TEST_F(CoroTest,FutureTry)585 TEST_F(CoroTest, FutureTry) {
586 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
587 auto task42 = []() -> coro::Task<int> { co_return 42; };
588 auto taskException = []() -> coro::Task<int> {
589 throw std::runtime_error("Test exception");
590 co_return 42;
591 };
592
593 {
594 auto result = co_await folly::coro::co_awaitTry(task42().semi());
595 EXPECT_TRUE(result.hasValue());
596 EXPECT_EQ(42, result.value());
597 }
598
599 {
600 auto result = co_await folly::coro::co_awaitTry(taskException().semi());
601 EXPECT_TRUE(result.hasException());
602 }
603
604 {
605 auto result = co_await folly::coro::co_awaitTry(
606 task42().semi().via(co_await folly::coro::co_current_executor));
607 EXPECT_TRUE(result.hasValue());
608 EXPECT_EQ(42, result.value());
609 }
610
611 {
612 auto result =
613 co_await folly::coro::co_awaitTry(taskException().semi().via(
614 co_await folly::coro::co_current_executor));
615 EXPECT_TRUE(result.hasException());
616 }
617 }());
618 }
619
TEST_F(CoroTest,CancellableSleep)620 TEST_F(CoroTest, CancellableSleep) {
621 using namespace std::chrono;
622 using namespace std::chrono_literals;
623
624 CancellationSource cancelSrc;
625
626 auto start = steady_clock::now();
627 EXPECT_THROW(
628 coro::blockingWait([&]() -> coro::Task<void> {
629 co_await coro::collectAll(
630 [&]() -> coro::Task<void> {
631 co_await coro::co_withCancellation(
632 cancelSrc.getToken(), coro::sleep(10s));
633 }(),
634 [&]() -> coro::Task<void> {
635 co_await coro::co_reschedule_on_current_executor;
636 co_await coro::co_reschedule_on_current_executor;
637 co_await coro::co_reschedule_on_current_executor;
638 cancelSrc.requestCancellation();
639 }());
640 }()),
641 OperationCancelled);
642 auto end = steady_clock::now();
643 CHECK((end - start) < 1s);
644 }
645
TEST_F(CoroTest,DefaultConstructible)646 TEST_F(CoroTest, DefaultConstructible) {
647 coro::blockingWait([]() -> coro::Task<void> {
648 struct S {
649 int x = 42;
650 };
651
652 auto taskS = []() -> coro::Task<S> { co_return {}; };
653
654 auto s = co_await taskS();
655 EXPECT_EQ(42, s.x);
656 }());
657 }
658
TEST(Coro,CoReturnTry)659 TEST(Coro, CoReturnTry) {
660 EXPECT_EQ(42, folly::coro::blockingWait([]() -> folly::coro::Task<int> {
661 co_return folly::Try<int>(42);
662 }()));
663
664 struct ExpectedException : public std::runtime_error {
665 ExpectedException() : std::runtime_error("ExpectedException") {}
666 };
667 EXPECT_THROW(
668 folly::coro::blockingWait([]() -> folly::coro::Task<int> {
669 co_return folly::Try<int>(ExpectedException());
670 }()),
671 ExpectedException);
672
673 EXPECT_EQ(42, folly::coro::blockingWait([]() -> folly::coro::Task<int> {
674 folly::Try<int> t(42);
675 co_return t;
676 }()));
677
678 EXPECT_EQ(42, folly::coro::blockingWait([]() -> folly::coro::Task<int> {
679 const folly::Try<int> tConst(42);
680 co_return tConst;
681 }()));
682 }
683
TEST(Coro,CoThrow)684 TEST(Coro, CoThrow) {
685 struct ExpectedException : public std::runtime_error {
686 ExpectedException() : std::runtime_error("ExpectedException") {}
687 };
688 EXPECT_THROW(
689 folly::coro::blockingWait([]() -> folly::coro::Task<int> {
690 co_yield folly::coro::co_error(ExpectedException());
691 ADD_FAILURE() << "unreachable";
692 // Intential lack of co_return statement to check
693 // that compiler treats code after co_yield co_error()
694 // as unreachable.
695 }()),
696 ExpectedException);
697
698 EXPECT_THROW(
699 folly::coro::blockingWait([]() -> folly::coro::Task<void> {
700 co_yield folly::coro::co_error(ExpectedException());
701 ADD_FAILURE() << "unreachable";
702 }()),
703 ExpectedException);
704 }
705
TEST_F(CoroTest,DetachOnCancel)706 TEST_F(CoroTest, DetachOnCancel) {
707 folly::coro::blockingWait([&]() -> folly::coro::Task<> {
708 folly::CancellationSource cancelSource;
709 cancelSource.requestCancellation();
710
711 // Run some logic while in an already-cancelled state.
712 co_await folly::coro::co_withCancellation(
713 cancelSource.getToken(), []() -> folly::coro::Task<> {
714 EXPECT_THROW(
715 co_await folly::coro::detachOnCancel(
716 folly::futures::sleep(std::chrono::seconds{1})
717 .deferValue([](auto) { return 42; })),
718 folly::OperationCancelled);
719 }());
720 }());
721
722 folly::coro::blockingWait([&]() -> folly::coro::Task<> {
723 folly::CancellationSource cancelSource;
724
725 co_await folly::coro::co_withCancellation(
726 cancelSource.getToken(), []() -> folly::coro::Task<> {
727 EXPECT_EQ(
728 42,
729 co_await folly::coro::detachOnCancel(
730 folly::futures::sleep(std::chrono::milliseconds{10})
731 .deferValue([](auto) { return 42; })));
732 }());
733 }());
734
735 folly::coro::blockingWait([&]() -> folly::coro::Task<> {
736 folly::CancellationSource cancelSource;
737
738 co_await folly::coro::co_withCancellation(
739 cancelSource.getToken(), []() -> folly::coro::Task<> {
740 co_await folly::coro::detachOnCancel([]() -> folly::coro::Task<void> {
741 co_await folly::futures::sleep(std::chrono::milliseconds{10});
742 }());
743 }());
744 }());
745 }
746 #endif
747