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; ...@@ -457,6 +457,31 @@ constexpr auto kCpplibVer = 0;
#define FOLLY_STORAGE_CONSTEXPR constexpr #define FOLLY_STORAGE_CONSTEXPR constexpr
#endif #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>) #if __cpp_coroutines >= 201703L && __has_include(<experimental/coroutine>)
#define FOLLY_HAS_COROUTINES 1 #define FOLLY_HAS_COROUTINES 1
#elif _MSC_VER && _RESUMABLE_FUNCTIONS_SUPPORTED #elif _MSC_VER && _RESUMABLE_FUNCTIONS_SUPPORTED
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <folly/Traits.h> #include <folly/Traits.h>
#include <folly/Utility.h> #include <folly/Utility.h>
#include <folly/detail/StaticConst.h>
#include <folly/detail/TypeList.h> #include <folly/detail/TypeList.h>
#include <folly/functional/Invoke.h> #include <folly/functional/Invoke.h>
#include <folly/lang/Exception.h> #include <folly/lang/Exception.h>
...@@ -192,14 +193,6 @@ struct IsInstanceOf<U<Ts...>, U> : std::true_type {}; ...@@ -192,14 +193,6 @@ struct IsInstanceOf<U<Ts...>, U> : std::true_type {};
template <class T> template <class T>
using Not = Bool<!T::value>; 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> template <class Then>
decltype(auto) if_constexpr(std::true_type, Then then) { decltype(auto) if_constexpr(std::true_type, Then then) {
return then(Identity{}); 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 { ...@@ -299,11 +299,7 @@ class SharedMutexFair {
explicit LockOperation(SharedMutexFair& mutex) noexcept : mutex_(mutex) {} explicit LockOperation(SharedMutexFair& mutex) noexcept : mutex_(mutex) {}
auto viaIfAsync(folly::Executor* executor) const { auto viaIfAsync(folly::Executor* executor) const {
return co_viaIfAsync(executor, Awaiter{mutex_}); return folly::coro::co_viaIfAsync(executor, Awaiter{mutex_});
}
friend auto co_viaIfAsync(folly::Executor* executor, LockOperation lockOp) {
return lockOp.viaIfAsync(executor);
} }
private: private:
......
...@@ -89,8 +89,8 @@ class TaskPromiseBase { ...@@ -89,8 +89,8 @@ class TaskPromiseBase {
template <typename Awaitable> template <typename Awaitable>
auto await_transform(Awaitable&& awaitable) noexcept { auto await_transform(Awaitable&& awaitable) noexcept {
using folly::coro::co_viaIfAsync; return folly::coro::co_viaIfAsync(
return co_viaIfAsync(executor_.get(), static_cast<Awaitable&&>(awaitable)); executor_.get(), static_cast<Awaitable&&>(awaitable));
} }
auto await_transform(co_current_executor_t) noexcept { auto await_transform(co_current_executor_t) noexcept {
...@@ -324,6 +324,7 @@ class FOLLY_NODISCARD Task { ...@@ -324,6 +324,7 @@ class FOLLY_NODISCARD Task {
using promise_type = detail::TaskPromise<T>; using promise_type = detail::TaskPromise<T>;
private: private:
class Awaiter;
using handle_t = std::experimental::coroutine_handle<promise_type>; using handle_t = std::experimental::coroutine_handle<promise_type>;
public: public:
...@@ -363,19 +364,16 @@ class FOLLY_NODISCARD Task { ...@@ -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: private:
friend class detail::TaskPromiseBase; friend class detail::TaskPromiseBase;
friend class detail::TaskPromise<T>; 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 { class Awaiter {
public: public:
explicit Awaiter(handle_t coro) noexcept : coro_(coro) {} explicit Awaiter(handle_t coro) noexcept : coro_(coro) {}
...@@ -411,10 +409,16 @@ auto detail::TaskPromiseBase::await_transform(Task<T>&& t) noexcept { ...@@ -411,10 +409,16 @@ auto detail::TaskPromiseBase::await_transform(Task<T>&& t) noexcept {
handle_t coro_; 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 // Child task inherits the awaiting task's executor
t.coro_.promise().executor_ = executor_.copyDummy(); t.coro_.promise().executor_ = executor_.copyDummy();
return typename Task<T>::Awaiter{std::exchange(t.coro_, {})};
return Awaiter{std::exchange(t.coro_, {})};
} }
template <typename T> template <typename T>
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <memory> #include <memory>
#include <folly/Executor.h> #include <folly/Executor.h>
#include <folly/Traits.h>
#include <folly/experimental/coro/Traits.h> #include <folly/experimental/coro/Traits.h>
#include <folly/io/async/Request.h> #include <folly/io/async/Request.h>
...@@ -305,14 +306,34 @@ auto operator co_await(const ViaIfAsyncAwaitable<Awaitable>& awaitable) ...@@ -305,14 +306,34 @@ auto operator co_await(const ViaIfAsyncAwaitable<Awaitable>& awaitable)
awaitable.executor_, awaitable.awaitable_}; awaitable.executor_, awaitable.awaitable_};
} }
/// Returns a new awaitable that will resume execution of the awaiting coroutine namespace detail {
/// on a specified executor in the case that the operation does not complete
/// synchronously. template <typename SemiAwaitable, typename = void>
/// struct HasViaIfAsyncMethod : std::false_type {};
/// If the operation completes synchronously then the awaiting coroutine
/// will continue execution on the current thread without transitioning template <typename SemiAwaitable>
/// execution to the specified executor. struct HasViaIfAsyncMethod<
template <typename Awaitable> 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) auto co_viaIfAsync(folly::Executor* executor, Awaitable&& awaitable)
-> ViaIfAsyncAwaitable<Awaitable> { -> ViaIfAsyncAwaitable<Awaitable> {
static_assert( static_assert(
...@@ -322,5 +343,46 @@ auto co_viaIfAsync(folly::Executor* executor, Awaitable&& awaitable) ...@@ -322,5 +343,46 @@ auto co_viaIfAsync(folly::Executor* executor, Awaitable&& awaitable)
static_cast<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 coro
} // namespace folly } // namespace folly
...@@ -23,12 +23,48 @@ ...@@ -23,12 +23,48 @@
#include <folly/experimental/coro/Baton.h> #include <folly/experimental/coro/Baton.h>
#include <folly/experimental/coro/BlockingWait.h> #include <folly/experimental/coro/BlockingWait.h>
#include <folly/experimental/coro/Mutex.h> #include <folly/experimental/coro/Mutex.h>
#include <folly/experimental/coro/SharedMutex.h>
#include <folly/experimental/coro/Task.h> #include <folly/experimental/coro/Task.h>
#include <folly/experimental/coro/detail/InlineTask.h> #include <folly/experimental/coro/detail/InlineTask.h>
#include <folly/portability/GTest.h> #include <folly/portability/GTest.h>
#include <type_traits>
using namespace folly; 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 { namespace {
const RequestToken testToken1("corotest1"); const RequestToken testToken1("corotest1");
...@@ -265,7 +301,8 @@ TEST(Task, RequestContextSideEffectsArePreserved) { ...@@ -265,7 +301,8 @@ TEST(Task, RequestContextSideEffectsArePreserved) {
// HACK: Need to use co_viaIfAsync() to ensure request context is preserved // HACK: Need to use co_viaIfAsync() to ensure request context is preserved
// across suspend-point. // 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); 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