Commit 1f3931f4 authored by Lewis Baker's avatar Lewis Baker Committed by Facebook Github Bot

Add folly::coro::detail::InlineTask

Summary:
Adds a new InlineTask coroutine type that is executor unaware.

*Execution Contexts*
The InlineTask's coroutine will start execution on the execution context of the coroutine that awaits it. When the InlineTask coroutine awaits something internally, it will resume execution on whatever execution context the operation it awaited on completes on. The coroutine awaiting the InlineTask will be resumed on whatever execution context the operation completed on.

It has been put in the `folly::coro::detail` namespace for now to discourage usage outside of the operators exposed from `folly::coro` as it can be easily misused to accidentally run logic on the wrong execution context.

This type is based around the same design as the `std::experimental::task<T>` that was proposed in [P1056R0](http://wg21.link/P1056R0).

Reviewed By: andriigrynenko

Differential Revision: D9388154

fbshipit-source-id: bc94a9bf07459410ef423b4cfd3b57d13688f744
parent a5f943d2
/*
* Copyright 2018-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/ScopeGuard.h>
#include <folly/Try.h>
#include <cassert>
#include <experimental/coroutine>
#include <utility>
namespace folly {
namespace coro {
namespace detail {
/// InlineTask<T> is a coroutine-return type where the coroutine is launched
/// inline in the current execution context when it is co_awaited and the
/// task's continuation is launched inline in the execution context that the
/// task completed on.
///
/// This task type is primarily intended as a building block for certain
/// coroutine operators. It is not intended for general use in application
/// code or in library interfaces exposed to library code as it can easily be
/// abused to accidentally run logic on the wrong execution context.
///
/// For this reason, the InlineTask<T> type has been placed inside the
/// folly::coro::detail namespace to discourage general usage.
template <typename T>
class InlineTask;
class InlineTaskPromiseBase {
struct FinalAwaiter {
bool await_ready() noexcept {
return false;
}
template <typename Promise>
std::experimental::coroutine_handle<> await_suspend(
std::experimental::coroutine_handle<Promise> h) noexcept {
InlineTaskPromiseBase& promise = h.promise();
return promise.continuation_;
}
void await_resume() noexcept {}
};
protected:
InlineTaskPromiseBase() noexcept = default;
InlineTaskPromiseBase(const InlineTaskPromiseBase&) = delete;
InlineTaskPromiseBase(InlineTaskPromiseBase&&) = delete;
InlineTaskPromiseBase& operator=(const InlineTaskPromiseBase&) = delete;
InlineTaskPromiseBase& operator=(InlineTaskPromiseBase&&) = delete;
public:
std::experimental::suspend_always initial_suspend() noexcept {
return {};
}
auto final_suspend() noexcept {
return FinalAwaiter{};
}
void set_continuation(
std::experimental::coroutine_handle<> continuation) noexcept {
assert(!continuation_);
continuation_ = continuation;
}
private:
std::experimental::coroutine_handle<> continuation_;
};
template <typename T>
class InlineTaskPromise : public InlineTaskPromiseBase {
public:
static_assert(
std::is_move_constructible<T>::value,
"InlineTask<T> only supports types that are move-constructible.");
static_assert(
!std::is_rvalue_reference<T>::value,
"InlineTask<T&&> is not supported");
InlineTaskPromise() noexcept = default;
~InlineTaskPromise() = default;
InlineTask<T> get_return_object() noexcept;
template <
typename Value,
std::enable_if_t<std::is_convertible<Value&&, T>::value, int> = 0>
void return_value(Value&& value) noexcept(
std::is_nothrow_constructible<T, Value&&>::value) {
result_.emplace(static_cast<Value&&>(value));
}
// Also provide non-template overload for T&& so that we can do
// 'co_return {arg1, arg2}' as shorthand for 'co_return T{arg1, arg2}'.
void return_value(T&& value) noexcept(
std::is_nothrow_move_constructible<T>::value) {
result_.emplace(static_cast<T&&>(value));
}
void unhandled_exception() noexcept {
result_.emplaceException(
folly::exception_wrapper::from_exception_ptr(std::current_exception()));
}
T result() {
return std::move(result_).value();
}
private:
// folly::Try<T> doesn't support storing reference types so we store a
// std::reference_wrapper instead.
using StorageType = std::conditional_t<
std::is_lvalue_reference<T>::value,
std::reference_wrapper<std::remove_reference_t<T>>,
T>;
folly::Try<StorageType> result_;
};
template <>
class InlineTaskPromise<void> : public InlineTaskPromiseBase {
public:
InlineTaskPromise() noexcept = default;
InlineTask<void> get_return_object() noexcept;
void return_void() noexcept {}
void unhandled_exception() noexcept {
result_.emplaceException(
folly::exception_wrapper::from_exception_ptr(std::current_exception()));
}
void result() {
return result_.value();
}
private:
folly::Try<void> result_;
};
template <typename T>
class InlineTask {
public:
using promise_type = detail::InlineTaskPromise<T>;
private:
using handle_t = std::experimental::coroutine_handle<promise_type>;
public:
InlineTask(InlineTask&& other) noexcept
: coro_(std::exchange(other.coro_, {})) {}
~InlineTask() {
if (coro_) {
coro_.destroy();
}
}
class Awaiter {
public:
~Awaiter() {
if (coro_) {
coro_.destroy();
}
}
bool await_ready() noexcept {
return false;
}
handle_t await_suspend(
std::experimental::coroutine_handle<> awaitingCoroutine) noexcept {
assert(coro_ && !coro_.done());
coro_.promise().set_continuation(awaitingCoroutine);
return coro_;
}
T await_resume() {
auto destroyOnExit =
folly::makeGuard([this] { std::exchange(coro_, {}).destroy(); });
return coro_.promise().result();
}
private:
friend class InlineTask<T>;
explicit Awaiter(handle_t coro) noexcept : coro_(coro) {}
handle_t coro_;
};
Awaiter operator co_await() && {
assert(coro_ && !coro_.done());
return Awaiter{std::exchange(coro_, {})};
}
private:
friend class InlineTaskPromise<T>;
explicit InlineTask(handle_t coro) noexcept : coro_(coro) {}
handle_t coro_;
};
template <typename T>
inline InlineTask<T> InlineTaskPromise<T>::get_return_object() noexcept {
return InlineTask<T>{
std::experimental::coroutine_handle<InlineTaskPromise<T>>::from_promise(
*this)};
}
inline InlineTask<void> InlineTaskPromise<void>::get_return_object() noexcept {
return InlineTask<void>{std::experimental::coroutine_handle<
InlineTaskPromise<void>>::from_promise(*this)};
}
} // namespace detail
} // namespace coro
} // namespace folly
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/Portability.h>
#if FOLLY_HAS_COROUTINES
#include <folly/experimental/coro/Baton.h>
#include <folly/experimental/coro/BlockingWait.h>
#include <folly/experimental/coro/detail/InlineTask.h>
#include <folly/portability/GTest.h>
#include <tuple>
template <typename T>
using InlineTask = folly::coro::detail::InlineTask<T>;
TEST(InlineTask, CallVoidTaskWithoutAwaitingNeverRuns) {
bool hasStarted = false;
auto f = [&]() -> InlineTask<void> {
hasStarted = true;
co_return;
};
{
auto task = f();
EXPECT_FALSE(hasStarted);
}
EXPECT_FALSE(hasStarted);
}
TEST(InlineTask, CallValueTaskWithoutAwaitingNeverRuns) {
bool hasStarted = false;
auto f = [&]() -> InlineTask<int> {
hasStarted = true;
co_return 123;
};
{
auto task = f();
EXPECT_FALSE(hasStarted);
}
EXPECT_FALSE(hasStarted);
}
TEST(InlineTask, CallRefTaskWithoutAwaitingNeverRuns) {
bool hasStarted = false;
int value;
auto f = [&]() -> InlineTask<int&> {
hasStarted = true;
co_return value;
};
{
auto task = f();
EXPECT_FALSE(hasStarted);
}
EXPECT_FALSE(hasStarted);
}
TEST(InlineTask, SimpleVoidTask) {
bool hasRun = false;
auto f = [&]() -> InlineTask<void> {
hasRun = true;
co_return;
};
auto t = f();
EXPECT_FALSE(hasRun);
folly::coro::blockingWait(std::move(t));
EXPECT_TRUE(hasRun);
}
TEST(InlineTask, SimpleValueTask) {
bool hasRun = false;
auto f = [&]() -> InlineTask<int> {
hasRun = true;
co_return 42;
};
auto t = f();
EXPECT_FALSE(hasRun);
EXPECT_EQ(42, folly::coro::blockingWait(std::move(t)));
EXPECT_TRUE(hasRun);
}
TEST(InlineTask, SimpleRefTask) {
bool hasRun = false;
auto f = [&]() -> InlineTask<bool&> {
hasRun = true;
co_return hasRun;
};
auto t = f();
EXPECT_FALSE(hasRun);
auto& result = folly::coro::blockingWait(std::move(t));
EXPECT_TRUE(hasRun);
EXPECT_EQ(&hasRun, &result);
}
struct MoveOnlyType {
int value_;
explicit MoveOnlyType(int value) noexcept : value_(value) {}
MoveOnlyType(MoveOnlyType&& other) noexcept
: value_(std::exchange(other.value_, -1)) {}
MoveOnlyType& operator=(MoveOnlyType&& other) noexcept {
value_ = std::exchange(other.value_, -1);
return *this;
}
~MoveOnlyType() {
value_ = -2;
}
};
struct TypeWithImplicitSingleValueConstructor {
float value_;
/* implicit */ TypeWithImplicitSingleValueConstructor(float x) : value_(x) {}
};
TEST(InlineTask, ReturnValueWithInitializerListSyntax) {
auto f = []() -> InlineTask<TypeWithImplicitSingleValueConstructor> {
co_return{1.23f};
};
auto result = folly::coro::blockingWait(f());
EXPECT_EQ(1.23f, result.value_);
}
struct TypeWithImplicitMultiValueConstructor {
std::string s_;
float x_;
/* implicit */ TypeWithImplicitMultiValueConstructor(
std::string s,
float x) noexcept
: s_(s), x_(x) {}
};
TEST(InlineTask, ReturnValueWithInitializerListSyntax2) {
auto f = []() -> InlineTask<TypeWithImplicitMultiValueConstructor> {
#if 0
// Under clang:
// error: cannot compile this scalar expression yet.
co_return{"hello", 3.1415f};
#else
co_return TypeWithImplicitMultiValueConstructor{"hello", 3.1415f};
#endif
};
auto result = folly::coro::blockingWait(f());
EXPECT_EQ("hello", result.s_);
EXPECT_EQ(3.1415f, result.x_);
}
TEST(InlineTask, TaskOfMoveOnlyType) {
auto f = []() -> InlineTask<MoveOnlyType> { co_return MoveOnlyType{42}; };
auto x = folly::coro::blockingWait(f());
EXPECT_EQ(42, x.value_);
bool executed = false;
auto g = [&]() -> InlineTask<void> {
auto result = co_await f();
EXPECT_EQ(42, result.value_);
executed = true;
};
folly::coro::blockingWait(g());
EXPECT_TRUE(executed);
}
TEST(InlineTask, MoveOnlyTypeNRVO) {
auto f = []() -> InlineTask<MoveOnlyType> {
MoveOnlyType x{10};
// Shouldn't need std::move(x) here, according to
// N4760 15.8.3(3) Copy/move elision
co_return std::move(x);
};
auto x = folly::coro::blockingWait(f());
EXPECT_EQ(10, x.value_);
}
TEST(InlineTask, ReturnLvalueReference) {
int value = 0;
auto f = [&]() -> InlineTask<int&> { co_return value; };
auto& x = folly::coro::blockingWait(f());
EXPECT_EQ(&value, &x);
}
struct MyException : std::exception {};
TEST(InlineTask, ExceptionsPropagateFromVoidTask) {
auto f = []() -> InlineTask<void> {
co_await folly::coro::Baton{true};
throw MyException{};
};
EXPECT_THROW(folly::coro::blockingWait(f()), MyException);
}
TEST(InlineTask, ExceptionsPropagateFromValueTask) {
auto f = []() -> InlineTask<int> {
co_await folly::coro::Baton{true};
throw MyException{};
};
EXPECT_THROW(folly::coro::blockingWait(f()), MyException);
}
TEST(InlineTask, ExceptionsPropagateFromRefTask) {
auto f = []() -> InlineTask<int&> {
co_await folly::coro::Baton{true};
throw MyException{};
};
EXPECT_THROW(folly::coro::blockingWait(f()), MyException);
}
struct ThrowingCopyConstructor {
ThrowingCopyConstructor() noexcept = default;
[[noreturn]] ThrowingCopyConstructor(const ThrowingCopyConstructor&) noexcept(
false) {
throw MyException{};
}
ThrowingCopyConstructor& operator=(const ThrowingCopyConstructor&) = delete;
};
TEST(InlineTask, ExceptionsPropagateFromReturnValueConstructor) {
auto f = []() -> InlineTask<ThrowingCopyConstructor> { co_return{}; };
EXPECT_THROW(folly::coro::blockingWait(f()), MyException);
}
InlineTask<void> recursiveTask(int depth) {
if (depth > 0) {
co_await recursiveTask(depth - 1);
}
}
TEST(InlineTask, DeepRecursionDoesntStackOverflow) {
folly::coro::blockingWait(recursiveTask(500000));
}
InlineTask<int> recursiveValueTask(int depth) {
if (depth > 0) {
co_return co_await recursiveValueTask(depth - 1) + 1;
}
co_return 0;
}
TEST(InlineTask, DeepRecursionOfValueTaskDoesntStackOverflow) {
EXPECT_EQ(500000, folly::coro::blockingWait(recursiveValueTask(500000)));
}
InlineTask<void> recursiveThrowingTask(int depth) {
if (depth > 0) {
co_await recursiveThrowingTask(depth - 1);
}
throw MyException{};
}
TEST(InlineTask, DeepRecursionOfExceptions) {
EXPECT_THROW(
folly::coro::blockingWait(recursiveThrowingTask(50000)), MyException);
}
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment