Commit 0cf8295c authored by Lewis Baker's avatar Lewis Baker Committed by Facebook Github Bot

Add folly::coro::semi_await_result_t<T>

Summary:
- Change folly::coro::co_viaIfAsync to be a customization-point object.
- Modify co_viaIfAsync() to call a .viaIfAsync() member function if one exists.
  Otherwise it calls the co_viaIfAsync() function found by ADL.
- Add FOLLY_DEFINE_CPO() helper macro to folly/Portability.h
  Borrows some techniques from range-v3 to support CPOs in different
  compilers and C++ versions.
- Redefined co_viaIfAsync() CPO in terms of this macro.
  This allows it to be callable as folly::coro::co_viaIfAsync() while
  still allowing other types in folly::coro namespace to define friend
  functions that customise the behaviour of this customisation point
  without causing conflicts.
- Add folly::coro::is_semi_awaitable<T> metafunction.
- Add folly::coro::semi_await_result_t<T> metafunction.

Reviewed By: yfeldblum

Differential Revision: D14016389

fbshipit-source-id: 63998993b2fa8560700f81666a910dee31f3ba72
parent 12f18243
......@@ -457,6 +457,31 @@ constexpr auto kCpplibVer = 0;
#define FOLLY_STORAGE_CONSTEXPR constexpr
#endif
// Helpers for portably defining customisation-point objects (CPOs).
//
// The customisation-point object must be placed in a nested namespace to
// avoid potential conflicts with customisations defined as friend-functions
// of types defined in the same namespace as the CPO.
//
// In C++17 and later we can define the object using 'inline constexpr' to
// avoid ODR issues. However, prior to that we need to use the StaticConst<T>
// helper to ensure that there is only a single instance of the CPO created
// and then we need to put a named reference to this object in an anonymous
// namespace to avoid duplicate symbol definitions.
#if __cpp_inline_variables >= 201606L
#define FOLLY_DEFINE_CPO(Type, Name) \
namespace __hidden { \
inline constexpr Type Name{}; \
} \
using namespace __hidden;
#else
#include <folly/detail/StaticConst.h>
#define FOLLY_DEFINE_CPO(Type, Name) \
namespace { \
constexpr auto& Name = ::folly::detail::StaticConst<Type>::value; \
}
#endif
#if __cpp_coroutines >= 201703L && __has_include(<experimental/coroutine>)
#define FOLLY_HAS_COROUTINES 1
#elif _MSC_VER && _RESUMABLE_FUNCTIONS_SUPPORTED
......
......@@ -25,6 +25,7 @@
#include <folly/Traits.h>
#include <folly/Utility.h>
#include <folly/detail/StaticConst.h>
#include <folly/detail/TypeList.h>
#include <folly/functional/Invoke.h>
#include <folly/lang/Exception.h>
......@@ -192,14 +193,6 @@ struct IsInstanceOf<U<Ts...>, U> : std::true_type {};
template <class T>
using Not = Bool<!T::value>;
template <class T>
struct StaticConst {
static constexpr T value{};
};
template <class T>
constexpr T StaticConst<T>::value;
template <class Then>
decltype(auto) if_constexpr(std::true_type, Then then) {
return then(Identity{});
......
/*
* Copyright 2019-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
namespace folly {
namespace detail {
// The StaticConst<T> class is a helper for defining constexpr objects at
// namespace scope in an ODR-safe and initialisation-order fiasco-safe way
// in C++ versions earlier than C++17.
//
// For example, see the FOLLY_DEFINE_CPO() macro in folly/Portability.h for
// usage in defining customisation-point objects (CPOs).
template <typename T>
struct StaticConst {
static constexpr T value{};
};
template <typename T>
constexpr T StaticConst<T>::value;
} // namespace detail
} // namespace folly
......@@ -299,11 +299,7 @@ class SharedMutexFair {
explicit LockOperation(SharedMutexFair& mutex) noexcept : mutex_(mutex) {}
auto viaIfAsync(folly::Executor* executor) const {
return co_viaIfAsync(executor, Awaiter{mutex_});
}
friend auto co_viaIfAsync(folly::Executor* executor, LockOperation lockOp) {
return lockOp.viaIfAsync(executor);
return folly::coro::co_viaIfAsync(executor, Awaiter{mutex_});
}
private:
......
......@@ -89,8 +89,8 @@ class TaskPromiseBase {
template <typename Awaitable>
auto await_transform(Awaitable&& awaitable) noexcept {
using folly::coro::co_viaIfAsync;
return co_viaIfAsync(executor_.get(), static_cast<Awaitable&&>(awaitable));
return folly::coro::co_viaIfAsync(
executor_.get(), static_cast<Awaitable&&>(awaitable));
}
auto await_transform(co_current_executor_t) noexcept {
......@@ -324,6 +324,7 @@ class FOLLY_NODISCARD Task {
using promise_type = detail::TaskPromise<T>;
private:
class Awaiter;
using handle_t = std::experimental::coroutine_handle<promise_type>;
public:
......@@ -363,19 +364,16 @@ class FOLLY_NODISCARD Task {
});
}
friend auto co_viaIfAsync(Executor* executor, Task<T>&& t) noexcept {
// Child task inherits the awaiting task's executor
t.coro_.promise().executor_ = getKeepAliveToken(executor);
return Awaiter{std::exchange(t.coro_, {})};
}
private:
friend class detail::TaskPromiseBase;
friend class detail::TaskPromise<T>;
Task(handle_t coro) noexcept : coro_(coro) {}
handle_t coro_;
};
template <typename T>
auto detail::TaskPromiseBase::await_transform(Task<T>&& t) noexcept {
using handle_t = std::experimental::coroutine_handle<detail::TaskPromise<T>>;
class Awaiter {
public:
explicit Awaiter(handle_t coro) noexcept : coro_(coro) {}
......@@ -411,10 +409,16 @@ auto detail::TaskPromiseBase::await_transform(Task<T>&& t) noexcept {
handle_t coro_;
};
Task(handle_t coro) noexcept : coro_(coro) {}
handle_t coro_;
};
template <typename T>
auto detail::TaskPromiseBase::await_transform(Task<T>&& t) noexcept {
// Child task inherits the awaiting task's executor
t.coro_.promise().executor_ = executor_.copyDummy();
return Awaiter{std::exchange(t.coro_, {})};
return typename Task<T>::Awaiter{std::exchange(t.coro_, {})};
}
template <typename T>
......
......@@ -19,6 +19,7 @@
#include <memory>
#include <folly/Executor.h>
#include <folly/Traits.h>
#include <folly/experimental/coro/Traits.h>
#include <folly/io/async/Request.h>
......@@ -305,14 +306,34 @@ auto operator co_await(const ViaIfAsyncAwaitable<Awaitable>& awaitable)
awaitable.executor_, awaitable.awaitable_};
}
/// Returns a new awaitable that will resume execution of the awaiting coroutine
/// on a specified executor in the case that the operation does not complete
/// synchronously.
///
/// If the operation completes synchronously then the awaiting coroutine
/// will continue execution on the current thread without transitioning
/// execution to the specified executor.
template <typename Awaitable>
namespace detail {
template <typename SemiAwaitable, typename = void>
struct HasViaIfAsyncMethod : std::false_type {};
template <typename SemiAwaitable>
struct HasViaIfAsyncMethod<
SemiAwaitable,
void_t<decltype(std::declval<SemiAwaitable>().viaIfAsync(
std::declval<folly::Executor*>()))>> : std::true_type {};
namespace adl {
template <typename SemiAwaitable>
auto co_viaIfAsync(
folly::Executor* executor,
SemiAwaitable&&
awaitable) noexcept(noexcept(static_cast<SemiAwaitable&&>(awaitable)
.viaIfAsync(executor)))
-> decltype(static_cast<SemiAwaitable&&>(awaitable).viaIfAsync(executor)) {
return static_cast<SemiAwaitable&&>(awaitable).viaIfAsync(executor);
}
template <
typename Awaitable,
std::enable_if_t<
is_awaitable_v<Awaitable> && !HasViaIfAsyncMethod<Awaitable>::value,
int> = 0>
auto co_viaIfAsync(folly::Executor* executor, Awaitable&& awaitable)
-> ViaIfAsyncAwaitable<Awaitable> {
static_assert(
......@@ -322,5 +343,46 @@ auto co_viaIfAsync(folly::Executor* executor, Awaitable&& awaitable)
static_cast<Awaitable&&>(awaitable)};
}
struct ViaIfAsyncFunction {
template <typename Awaitable>
auto operator()(folly::Executor* executor, Awaitable&& awaitable) const
noexcept(noexcept(
co_viaIfAsync(executor, static_cast<Awaitable&&>(awaitable))))
-> decltype(
co_viaIfAsync(executor, static_cast<Awaitable&&>(awaitable))) {
return co_viaIfAsync(executor, static_cast<Awaitable&&>(awaitable));
}
};
} // namespace adl
} // namespace detail
/// Returns a new awaitable that will resume execution of the awaiting coroutine
/// on a specified executor in the case that the operation does not complete
/// synchronously.
///
/// If the operation completes synchronously then the awaiting coroutine
/// will continue execution on the current thread without transitioning
/// execution to the specified executor.
FOLLY_DEFINE_CPO(detail::adl::ViaIfAsyncFunction, co_viaIfAsync)
template <typename T, typename = void>
struct is_semi_awaitable : std::false_type {};
template <typename T>
struct is_semi_awaitable<
T,
void_t<decltype(folly::coro::co_viaIfAsync(
std::declval<folly::Executor*>(),
std::declval<T>()))>> : std::true_type {};
template <typename T>
constexpr bool is_semi_awaitable_v = is_semi_awaitable<T>::value;
template <typename T>
using semi_await_result_t = await_result_t<decltype(folly::coro::co_viaIfAsync(
std::declval<folly::Executor*>(),
std::declval<T>()))>;
} // namespace coro
} // namespace folly
......@@ -23,12 +23,48 @@
#include <folly/experimental/coro/Baton.h>
#include <folly/experimental/coro/BlockingWait.h>
#include <folly/experimental/coro/Mutex.h>
#include <folly/experimental/coro/SharedMutex.h>
#include <folly/experimental/coro/Task.h>
#include <folly/experimental/coro/detail/InlineTask.h>
#include <folly/portability/GTest.h>
#include <type_traits>
using namespace folly;
static_assert(
std::is_same<
folly::coro::semi_await_result_t<folly::coro::Task<void>>,
void>::value,
"");
static_assert(
std::is_same<
folly::coro::semi_await_result_t<folly::coro::Task<int>>,
int>::value,
"");
static_assert(
std::is_same<
folly::coro::semi_await_result_t<folly::coro::detail::InlineTask<void>>,
void>::value,
"");
static_assert(
std::is_same<
folly::coro::semi_await_result_t<folly::coro::detail::InlineTask<int>>,
int>::value,
"");
static_assert(
std::is_same<folly::coro::semi_await_result_t<folly::coro::Baton&>, void>::
value,
"");
static_assert(
std::is_same<
folly::coro::semi_await_result_t<decltype(
std::declval<folly::coro::SharedMutex&>().co_scoped_lock_shared())>,
folly::coro::SharedLock<folly::coro::SharedMutex>>::value,
"");
namespace {
const RequestToken testToken1("corotest1");
......@@ -265,7 +301,8 @@ TEST(Task, RequestContextSideEffectsArePreserved) {
// HACK: Need to use co_viaIfAsync() to ensure request context is preserved
// across suspend-point.
co_await co_viaIfAsync(&folly::InlineExecutor::instance(), baton);
co_await folly::coro::co_viaIfAsync(
&folly::InlineExecutor::instance(), baton);
EXPECT_NE(RequestContext::get()->getContextData(testToken1), nullptr);
......
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