Commit a95a6976 authored by Lee Howes's avatar Lee Howes Committed by Facebook Github Bot

Add SemiFuture class.

Summary:
Offer a clean separation between future as a vocabulary type for crossing
library interfaces, and future as a type to manage continuations.

The principle is that if we want an API with clean separation of execution it
should both return and receive SemiFutures. The returned SemiFuture would
only be awaitable, but not continuable. If the caller wants to enqueue a
continuation then it is efficiently convertable into a folly::Future by
calling .via.

This means that an API a) Does not have to take an executor to ensure it
returns a valid future. That can be deferred to the caller or the caller's
caller/target for true separation. b) The API can ensure that its own executor
is not exposed to the caller, while still having a clean API.

Obviously if some API wants to allow returning of a continuable future, then it
can also provide an executor-taking API.

Reviewed By: yfeldblum

Differential Revision: D5769284

fbshipit-source-id: 46241d1274bf7b1698a7d28a47cff2a65a983740
parent eab6da6a
......@@ -43,7 +43,11 @@ struct Unit {
template <typename T>
struct Lift : std::conditional<std::is_same<T, void>::value, Unit, T> {};
template <typename T>
using LiftT = typename Lift<T>::type;
template <typename T>
struct Drop : std::conditional<std::is_same<T, Unit>::value, void, T> {};
template <typename T>
using DropT = typename Drop<T>::type;
constexpr bool operator==(const Unit& /*other*/) const {
return true;
......
......@@ -134,21 +134,235 @@ inline auto makeCoreCallbackState(Promise<T>&& p, F&& f) noexcept(
} // namespace futures
template <class T>
Future<T> Future<T>::makeEmpty() {
return Future<T>(futures::detail::EmptyConstruct{});
SemiFuture<typename std::decay<T>::type> makeSemiFuture(T&& t) {
return makeSemiFuture(Try<typename std::decay<T>::type>(std::forward<T>(t)));
}
inline SemiFuture<Unit> makeSemiFuture() {
return makeSemiFuture(Unit{});
}
// makeSemiFutureWith(SemiFuture<T>()) -> SemiFuture<T>
template <class F>
typename std::enable_if<
isSemiFuture<typename std::result_of<F()>::type>::value,
typename std::result_of<F()>::type>::type
makeSemiFutureWith(F&& func) {
using InnerType =
typename isSemiFuture<typename std::result_of<F()>::type>::Inner;
try {
return std::forward<F>(func)();
} catch (std::exception& e) {
return makeSemiFuture<InnerType>(
exception_wrapper(std::current_exception(), e));
} catch (...) {
return makeSemiFuture<InnerType>(
exception_wrapper(std::current_exception()));
}
}
// makeSemiFutureWith(T()) -> SemiFuture<T>
// makeSemiFutureWith(void()) -> SemiFuture<Unit>
template <class F>
typename std::enable_if<
!(isSemiFuture<typename std::result_of<F()>::type>::value),
SemiFuture<Unit::LiftT<typename std::result_of<F()>::type>>>::type
makeSemiFutureWith(F&& func) {
using LiftedResult = Unit::LiftT<typename std::result_of<F()>::type>;
return makeSemiFuture<LiftedResult>(
makeTryWith([&func]() mutable { return std::forward<F>(func)(); }));
}
template <class T>
SemiFuture<T> makeSemiFuture(std::exception_ptr const& e) {
return makeSemiFuture(Try<T>(e));
}
template <class T>
SemiFuture<T> makeSemiFuture(exception_wrapper ew) {
return makeSemiFuture(Try<T>(std::move(ew)));
}
template <class T, class E>
typename std::
enable_if<std::is_base_of<std::exception, E>::value, SemiFuture<T>>::type
makeSemiFuture(E const& e) {
return makeSemiFuture(Try<T>(make_exception_wrapper<E>(e)));
}
template <class T>
Future<T>::Future(Future<T>&& other) noexcept : core_(other.core_) {
SemiFuture<T> makeSemiFuture(Try<T>&& t) {
return SemiFuture<T>(new futures::detail::Core<T>(std::move(t)));
}
template <class T>
SemiFuture<T> SemiFuture<T>::makeEmpty() {
return SemiFuture<T>(futures::detail::EmptyConstruct{});
}
template <class T>
SemiFuture<T>::SemiFuture(SemiFuture<T>&& other) noexcept : core_(other.core_) {
other.core_ = nullptr;
}
template <class T>
Future<T>& Future<T>::operator=(Future<T>&& other) noexcept {
SemiFuture<T>& SemiFuture<T>::operator=(SemiFuture<T>&& other) noexcept {
std::swap(core_, other.core_);
return *this;
}
template <class T>
SemiFuture<T>::SemiFuture(Future<T>&& other) noexcept : core_(other.core_) {
other.core_ = nullptr;
}
template <class T>
SemiFuture<T>& SemiFuture<T>::operator=(Future<T>&& other) noexcept {
std::swap(core_, other.core_);
return *this;
}
template <class T>
template <class T2, typename>
SemiFuture<T>::SemiFuture(T2&& val)
: core_(new futures::detail::Core<T>(Try<T>(std::forward<T2>(val)))) {}
template <class T>
template <typename T2>
SemiFuture<T>::SemiFuture(
typename std::enable_if<std::is_same<Unit, T2>::value>::type*)
: core_(new futures::detail::Core<T>(Try<T>(T()))) {}
template <class T>
template <
class... Args,
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
type>
SemiFuture<T>::SemiFuture(in_place_t, Args&&... args)
: core_(
new futures::detail::Core<T>(in_place, std::forward<Args>(args)...)) {
}
template <class T>
SemiFuture<T>::~SemiFuture() {
detach();
}
template <class T>
typename std::add_lvalue_reference<T>::type SemiFuture<T>::value() {
throwIfInvalid();
return core_->getTry().value();
}
template <class T>
typename std::add_lvalue_reference<const T>::type SemiFuture<T>::value() const {
throwIfInvalid();
return core_->getTry().value();
}
template <class T>
inline Future<T> SemiFuture<T>::via(Executor* executor, int8_t priority) && {
throwIfInvalid();
setExecutor(executor, priority);
auto newFuture = Future<T>(core_);
core_ = nullptr;
return newFuture;
}
template <class T>
inline Future<T> SemiFuture<T>::via(Executor* executor, int8_t priority) & {
throwIfInvalid();
Promise<T> p;
auto f = p.getFuture();
auto func = [p = std::move(p)](Try<T>&& t) mutable {
p.setTry(std::move(t));
};
using R = futures::detail::callableResult<T, decltype(func)>;
thenImplementation<decltype(func), R>(std::move(func), typename R::Arg());
return std::move(f).via(executor, priority);
}
template <class T>
bool SemiFuture<T>::isReady() const {
throwIfInvalid();
return core_->ready();
}
template <class T>
bool SemiFuture<T>::hasValue() {
return getTry().hasValue();
}
template <class T>
bool SemiFuture<T>::hasException() {
return getTry().hasException();
}
template <class T>
void SemiFuture<T>::detach() {
if (core_) {
core_->detachFuture();
core_ = nullptr;
}
}
template <class T>
Try<T>& SemiFuture<T>::getTry() {
throwIfInvalid();
return core_->getTry();
}
template <class T>
void SemiFuture<T>::throwIfInvalid() const {
if (!core_)
throwNoState();
}
template <class T>
Optional<Try<T>> SemiFuture<T>::poll() {
Optional<Try<T>> o;
if (core_->ready()) {
o = std::move(core_->getTry());
}
return o;
}
template <class T>
void SemiFuture<T>::raise(exception_wrapper exception) {
core_->raise(std::move(exception));
}
template <class T>
template <class F>
void SemiFuture<T>::setCallback_(F&& func) {
throwIfInvalid();
core_->setCallback(std::forward<F>(func));
}
template <class T>
SemiFuture<T>::SemiFuture(futures::detail::EmptyConstruct) noexcept
: core_(nullptr) {}
template <class T>
Future<T> Future<T>::makeEmpty() {
return Future<T>(futures::detail::EmptyConstruct{});
}
template <class T>
Future<T>::Future(Future<T>&& other) noexcept
: SemiFuture<T>(std::move(other)) {}
template <class T>
Future<T>& Future<T>::operator=(Future<T>&& other) noexcept {
SemiFuture<T>::operator=(SemiFuture<T>{std::move(other)});
return *this;
}
template <class T>
template <
class T2,
......@@ -183,15 +397,15 @@ Future<T>& Future<T>::operator=(Future<T2>&& other) {
std::move(other).then([](T2&& v) { return T(std::move(v)); }));
}
// TODO: isSemiFuture
template <class T>
template <class T2, typename>
Future<T>::Future(T2&& val)
: core_(new futures::detail::Core<T>(Try<T>(std::forward<T2>(val)))) {}
Future<T>::Future(T2&& val) : SemiFuture<T>(std::forward<T2>(val)) {}
template <class T>
template <typename T2>
Future<T>::Future(typename std::enable_if<std::is_same<Unit, T2>::value>::type*)
: core_(new futures::detail::Core<T>(Try<T>(T()))) {}
: SemiFuture<T>() {}
template <class T>
template <
......@@ -199,34 +413,10 @@ template <
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
type>
Future<T>::Future(in_place_t, Args&&... args)
: core_(
new futures::detail::Core<T>(in_place, std::forward<Args>(args)...)) {
}
: SemiFuture<T>(in_place, std::forward<Args>(args)...) {}
template <class T>
Future<T>::~Future() {
detach();
}
template <class T>
void Future<T>::detach() {
if (core_) {
core_->detachFuture();
core_ = nullptr;
}
}
template <class T>
void Future<T>::throwIfInvalid() const {
if (!core_)
throwNoState();
}
template <class T>
template <class F>
void Future<T>::setCallback_(F&& func) {
throwIfInvalid();
core_->setCallback(std::forward<F>(func));
}
// unwrap
......@@ -248,20 +438,20 @@ Future<T>::unwrap() {
template <class T>
template <typename F, typename R, bool isTry, typename... Args>
typename std::enable_if<!R::ReturnsFuture::value, typename R::Return>::type
Future<T>::thenImplementation(
SemiFuture<T>::thenImplementation(
F&& func,
futures::detail::argResult<isTry, F, Args...>) {
static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument");
typedef typename R::ReturnsFuture::Inner B;
throwIfInvalid();
this->throwIfInvalid();
Promise<B> p;
p.core_->setInterruptHandlerNoLock(core_->getInterruptHandler());
p.core_->setInterruptHandlerNoLock(this->core_->getInterruptHandler());
// grab the Future now before we lose our handle on the Promise
auto f = p.getFuture();
f.core_->setExecutorNoLock(getExecutor());
f.core_->setExecutorNoLock(this->getExecutor());
/* This is a bit tricky.
......@@ -292,9 +482,10 @@ Future<T>::thenImplementation(
in some circumstances, but I think it should be explicit not implicit
in the destruction of the Future used to create it.
*/
setCallback_(
this->setCallback_(
[state = futures::detail::makeCoreCallbackState(
std::move(p), std::forward<F>(func))](Try<T> && t) mutable {
std::move(p), std::forward<F>(func))](Try<T>&& t) mutable {
if (!isTry && t.hasException()) {
state.setException(std::move(t.exception()));
} else {
......@@ -302,7 +493,6 @@ Future<T>::thenImplementation(
[&] { return state.invoke(t.template get<isTry, Args>()...); }));
}
});
return f;
}
......@@ -311,24 +501,23 @@ Future<T>::thenImplementation(
template <class T>
template <typename F, typename R, bool isTry, typename... Args>
typename std::enable_if<R::ReturnsFuture::value, typename R::Return>::type
Future<T>::thenImplementation(
SemiFuture<T>::thenImplementation(
F&& func,
futures::detail::argResult<isTry, F, Args...>) {
static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument");
typedef typename R::ReturnsFuture::Inner B;
throwIfInvalid();
this->throwIfInvalid();
Promise<B> p;
p.core_->setInterruptHandlerNoLock(core_->getInterruptHandler());
p.core_->setInterruptHandlerNoLock(this->core_->getInterruptHandler());
// grab the Future now before we lose our handle on the Promise
auto f = p.getFuture();
f.core_->setExecutorNoLock(getExecutor());
f.core_->setExecutorNoLock(this->getExecutor());
setCallback_(
this->setCallback_(
[state = futures::detail::makeCoreCallbackState(
std::move(p), std::forward<F>(func))](Try<T> && t) mutable {
std::move(p), std::forward<F>(func))](Try<T>&& t) mutable {
if (!isTry && t.hasException()) {
state.setException(std::move(t.exception()));
} else {
......@@ -353,6 +542,7 @@ Future<T>::then(R(Caller::*func)(Args...), Caller *instance) {
typedef typename std::remove_cv<typename std::remove_reference<
typename futures::detail::ArgType<Args...>::FirstArg>::type>::type
FirstArg;
return then([instance, func](Try<T>&& t){
return (instance->*func)(t.template get<isTry<FirstArg>::value, Args>()...);
});
......@@ -380,12 +570,12 @@ Future<T>::onError(F&& func) {
"Return type of onError callback must be T or Future<T>");
Promise<T> p;
p.core_->setInterruptHandlerNoLock(core_->getInterruptHandler());
p.core_->setInterruptHandlerNoLock(this->core_->getInterruptHandler());
auto f = p.getFuture();
setCallback_(
this->setCallback_(
[state = futures::detail::makeCoreCallbackState(
std::move(p), std::forward<F>(func))](Try<T> && t) mutable {
std::move(p), std::forward<F>(func))](Try<T>&& t) mutable {
if (auto e = t.template tryGetExceptionObject<Exn>()) {
state.setTry(makeTryWith([&] { return state.invoke(*e); }));
} else {
......@@ -416,9 +606,9 @@ Future<T>::onError(F&& func) {
Promise<T> p;
auto f = p.getFuture();
setCallback_(
this->setCallback_(
[state = futures::detail::makeCoreCallbackState(
std::move(p), std::forward<F>(func))](Try<T> && t) mutable {
std::move(p), std::forward<F>(func))](Try<T>&& t) mutable {
if (auto e = t.template tryGetExceptionObject<Exn>()) {
auto tf2 = state.tryInvoke(*e);
if (tf2.hasException()) {
......@@ -466,7 +656,7 @@ Future<T>::onError(F&& func) {
Promise<T> p;
auto f = p.getFuture();
setCallback_(
this->setCallback_(
[state = futures::detail::makeCoreCallbackState(
std::move(p), std::forward<F>(func))](Try<T> t) mutable {
if (t.hasException()) {
......@@ -501,9 +691,9 @@ Future<T>::onError(F&& func) {
Promise<T> p;
auto f = p.getFuture();
setCallback_(
this->setCallback_(
[state = futures::detail::makeCoreCallbackState(
std::move(p), std::forward<F>(func))](Try<T> && t) mutable {
std::move(p), std::forward<F>(func))](Try<T>&& t) mutable {
if (t.hasException()) {
state.setTry(makeTryWith(
[&] { return state.invoke(std::move(t.exception())); }));
......@@ -515,60 +705,11 @@ Future<T>::onError(F&& func) {
return f;
}
template <class T>
typename std::add_lvalue_reference<T>::type Future<T>::value() {
throwIfInvalid();
return core_->getTry().value();
}
template <class T>
typename std::add_lvalue_reference<const T>::type Future<T>::value() const {
throwIfInvalid();
return core_->getTry().value();
}
template <class T>
Try<T>& Future<T>::getTry() {
throwIfInvalid();
return core_->getTry();
}
template <class T>
Try<T>& Future<T>::getTryVia(DrivableExecutor* e) {
return waitVia(e).getTry();
}
template <class T>
Optional<Try<T>> Future<T>::poll() {
Optional<Try<T>> o;
if (core_->ready()) {
o = std::move(core_->getTry());
}
return o;
}
template <class T>
inline Future<T> Future<T>::via(Executor* executor, int8_t priority) && {
throwIfInvalid();
setExecutor(executor, priority);
return std::move(*this);
}
template <class T>
inline Future<T> Future<T>::via(Executor* executor, int8_t priority) & {
throwIfInvalid();
Promise<T> p;
auto f = p.getFuture();
then([p = std::move(p)](Try<T> && t) mutable { p.setTry(std::move(t)); });
return std::move(f).via(executor, priority);
}
template <class Func>
auto via(Executor* x, Func&& func)
-> Future<typename isFuture<decltype(std::declval<Func>()())>::Inner> {
......@@ -577,28 +718,8 @@ auto via(Executor* x, Func&& func)
}
template <class T>
bool Future<T>::isReady() const {
throwIfInvalid();
return core_->ready();
}
template <class T>
bool Future<T>::hasValue() {
return getTry().hasValue();
}
template <class T>
bool Future<T>::hasException() {
return getTry().hasException();
}
template <class T>
void Future<T>::raise(exception_wrapper exception) {
core_->raise(std::move(exception));
}
template <class T>
Future<T>::Future(futures::detail::EmptyConstruct) noexcept : core_(nullptr) {}
Future<T>::Future(futures::detail::EmptyConstruct) noexcept
: SemiFuture<T>(futures::detail::EmptyConstruct{}) {}
// makeFuture
......@@ -607,8 +728,7 @@ Future<typename std::decay<T>::type> makeFuture(T&& t) {
return makeFuture(Try<typename std::decay<T>::type>(std::forward<T>(t)));
}
inline // for multiple translation units
Future<Unit> makeFuture() {
inline Future<Unit> makeFuture() {
return makeFuture(Unit{});
}
......@@ -634,10 +754,9 @@ makeFutureWith(F&& func) {
template <class F>
typename std::enable_if<
!(isFuture<typename std::result_of<F()>::type>::value),
Future<typename Unit::Lift<typename std::result_of<F()>::type>::type>>::type
Future<Unit::LiftT<typename std::result_of<F()>::type>>>::type
makeFutureWith(F&& func) {
using LiftedResult =
typename Unit::Lift<typename std::result_of<F()>::type>::type;
using LiftedResult = Unit::LiftT<typename std::result_of<F()>::type>;
return makeFuture<LiftedResult>(
makeTryWith([&func]() mutable { return std::forward<F>(func)(); }));
}
......@@ -1104,7 +1223,7 @@ Future<T> Future<T>::within(Duration dur, E e, Timekeeper* tk) {
}
});
return ctx->promise.getFuture().via(getExecutor());
return ctx->promise.getFuture().via(this->getExecutor());
}
// delayed
......@@ -1121,8 +1240,8 @@ Future<T> Future<T>::delayed(Duration dur, Timekeeper* tk) {
namespace futures {
namespace detail {
template <class T>
void waitImpl(Future<T>& f) {
template <class FutureType, typename T = typename FutureType::value_type>
void waitImpl(FutureType& f) {
// short-circuit if there's nothing to do
if (f.isReady()) return;
......@@ -1132,8 +1251,8 @@ void waitImpl(Future<T>& f) {
assert(f.isReady());
}
template <class T>
void waitImpl(Future<T>& f, Duration dur) {
template <class FutureType, typename T = typename FutureType::value_type>
void waitImpl(FutureType& f, Duration dur) {
// short-circuit if there's nothing to do
if (f.isReady()) {
return;
......@@ -1169,6 +1288,45 @@ void waitViaImpl(Future<T>& f, DrivableExecutor* e) {
} // namespace detail
} // namespace futures
template <class T>
SemiFuture<T>& SemiFuture<T>::wait() & {
futures::detail::waitImpl(*this);
return *this;
}
template <class T>
SemiFuture<T>&& SemiFuture<T>::wait() && {
futures::detail::waitImpl(*this);
return std::move(*this);
}
template <class T>
SemiFuture<T>& SemiFuture<T>::wait(Duration dur) & {
futures::detail::waitImpl(*this, dur);
return *this;
}
template <class T>
SemiFuture<T>&& SemiFuture<T>::wait(Duration dur) && {
futures::detail::waitImpl(*this, dur);
return std::move(*this);
}
template <class T>
T SemiFuture<T>::get() {
return std::move(wait().value());
}
template <class T>
T SemiFuture<T>::get(Duration dur) {
wait(dur);
if (this->isReady()) {
return std::move(this->value());
} else {
throwTimedOut();
}
}
template <class T>
Future<T>& Future<T>::wait() & {
futures::detail::waitImpl(*this);
......@@ -1205,21 +1363,6 @@ Future<T>&& Future<T>::waitVia(DrivableExecutor* e) && {
return std::move(*this);
}
template <class T>
T Future<T>::get() {
return std::move(wait().value());
}
template <class T>
T Future<T>::get(Duration dur) {
wait(dur);
if (isReady()) {
return std::move(value());
} else {
throwTimedOut();
}
}
template <class T>
T Future<T>::getVia(DrivableExecutor* e) {
return std::move(waitVia(e).value());
......@@ -1527,5 +1670,4 @@ extern template class Future<int>;
extern template class Future<int64_t>;
extern template class Future<std::string>;
extern template class Future<double>;
} // namespace folly
......@@ -22,6 +22,24 @@ namespace folly {
template <class> class Promise;
template <class T>
class SemiFuture;
template <typename T>
struct isSemiFuture : std::false_type {
using Inner = typename Unit::Lift<T>::type;
};
template <typename T>
struct isSemiFuture<SemiFuture<T>> : std::true_type {
typedef T Inner;
};
template <typename T>
struct isSemiFuture<Future<T>> : std::true_type {
typedef T Inner;
};
template <typename T>
struct isFuture : std::false_type {
using Inner = typename Unit::Lift<T>::type;
......
......@@ -21,13 +21,18 @@
namespace folly {
// Instantiate the most common Future types to save compile time
template class SemiFuture<Unit>;
template class SemiFuture<bool>;
template class SemiFuture<int>;
template class SemiFuture<int64_t>;
template class SemiFuture<std::string>;
template class SemiFuture<double>;
template class Future<Unit>;
template class Future<bool>;
template class Future<int>;
template class Future<int64_t>;
template class Future<std::string>;
template class Future<double>;
}
namespace folly { namespace futures {
......
......@@ -42,62 +42,45 @@
namespace folly {
template <class T>
class Future {
class Future;
template <class T>
class SemiFuture {
public:
typedef T value_type;
static Future<T> makeEmpty(); // equivalent to moved-from
static SemiFuture<T> makeEmpty(); // equivalent to moved-from
// not copyable
Future(Future const&) = delete;
Future& operator=(Future const&) = delete;
SemiFuture(SemiFuture const&) = delete;
SemiFuture& operator=(SemiFuture const&) = delete;
// movable
Future(Future&&) noexcept;
Future& operator=(Future&&) noexcept;
SemiFuture(SemiFuture&&) noexcept;
SemiFuture& operator=(SemiFuture&&) noexcept;
// converting move
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value &&
std::is_convertible<T2&&, T>::value,
int>::type = 0>
/* implicit */ Future(Future<T2>&&);
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value &&
!std::is_convertible<T2&&, T>::value,
int>::type = 0>
explicit Future(Future<T2>&&);
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value,
int>::type = 0>
Future& operator=(Future<T2>&&);
// safe move-constructabilty from Future
/* implicit */ SemiFuture(Future<T>&&) noexcept;
SemiFuture& operator=(Future<T>&&) noexcept;
/// Construct a Future from a value (perfect forwarding)
template <class T2 = T, typename =
typename std::enable_if<
!isFuture<typename std::decay<T2>::type>::value>::type>
/* implicit */ Future(T2&& val);
template <
class T2 = T,
typename = typename std::enable_if<
!isFuture<typename std::decay<T2>::type>::value>::type>
/* implicit */ SemiFuture(T2&& val);
template <class T2 = T>
/* implicit */ Future(
/* implicit */ SemiFuture(
typename std::enable_if<std::is_same<Unit, T2>::value>::type* = nullptr);
template <
class... Args,
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
type = 0>
explicit Future(in_place_t, Args&&... args);
explicit SemiFuture(in_place_t, Args&&... args);
~Future();
~SemiFuture();
/** Return the reference to result. Should not be called if !isReady().
Will rethrow the exception if an exception has been
......@@ -151,11 +134,6 @@ class Future {
/** A reference to the Try of the value */
Try<T>& getTry();
/// Call e->drive() repeatedly until the future is fulfilled. Examples
/// of DrivableExecutor include EventBase and ManualExecutor. Returns a
/// reference to the Try of the value.
Try<T>& getTryVia(DrivableExecutor* e);
/// If the promise has been fulfilled, return an Optional with the Try<T>.
/// Otherwise return an empty Optional.
/// Note that this moves the Try<T> out.
......@@ -170,6 +148,158 @@ class Future {
/// exception).
T get(Duration dur);
/// Block until this Future is complete. Returns a reference to this Future.
SemiFuture<T>& wait() &;
/// Overload of wait() for rvalue Futures
SemiFuture<T>&& wait() &&;
/// Block until this Future is complete or until the given Duration passes.
/// Returns a reference to this Future
SemiFuture<T>& wait(Duration) &;
/// Overload of wait(Duration) for rvalue Futures
SemiFuture<T>&& wait(Duration) &&;
/// This is not the method you're looking for.
///
/// This needs to be public because it's used by make* and when*, and it's
/// not worth listing all those and their fancy template signatures as
/// friends. But it's not for public consumption.
template <class F>
void setCallback_(F&& func);
bool isActive() {
return core_->isActive();
}
template <class E>
void raise(E&& exception) {
raise(make_exception_wrapper<typename std::remove_reference<E>::type>(
std::forward<E>(exception)));
}
/// Raise an interrupt. If the promise holder has an interrupt
/// handler it will be called and potentially stop asynchronous work from
/// being done. This is advisory only - a promise holder may not set an
/// interrupt handler, or may do anything including ignore. But, if you know
/// your future supports this the most likely result is stopping or
/// preventing the asynchronous operation (if in time), and the promise
/// holder setting an exception on the future. (That may happen
/// asynchronously, of course.)
void raise(exception_wrapper interrupt);
void cancel() {
raise(FutureCancellation());
}
protected:
typedef futures::detail::Core<T>* corePtr;
// shared core state object
corePtr core_;
explicit SemiFuture(corePtr obj) : core_(obj) {}
explicit SemiFuture(futures::detail::EmptyConstruct) noexcept;
void detach();
void throwIfInvalid() const;
friend class Promise<T>;
template <class>
friend class SemiFuture;
template <class T2>
friend SemiFuture<T2> makeSemiFuture(Try<T2>&&);
Executor* getExecutor() {
return core_->getExecutor();
}
void setExecutor(Executor* x, int8_t priority = Executor::MID_PRI) {
core_->setExecutor(x, priority);
}
// Variant: returns a value
// e.g. f.then([](Try<T> t){ return t.value(); });
template <typename F, typename R, bool isTry, typename... Args>
typename std::enable_if<!R::ReturnsFuture::value, typename R::Return>::type
thenImplementation(F&& func, futures::detail::argResult<isTry, F, Args...>);
// Variant: returns a Future
// e.g. f.then([](Try<T> t){ return makeFuture<T>(t); });
template <typename F, typename R, bool isTry, typename... Args>
typename std::enable_if<R::ReturnsFuture::value, typename R::Return>::type
thenImplementation(F&& func, futures::detail::argResult<isTry, F, Args...>);
};
template <class T>
class Future : public SemiFuture<T> {
public:
typedef T value_type;
static Future<T> makeEmpty(); // equivalent to moved-from
// not copyable
Future(Future const&) = delete;
Future& operator=(Future const&) = delete;
// movable
Future(Future&&) noexcept;
Future& operator=(Future&&) noexcept;
// converting move
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value &&
std::is_convertible<T2&&, T>::value,
int>::type = 0>
/* implicit */ Future(Future<T2>&&);
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value &&
!std::is_convertible<T2&&, T>::value,
int>::type = 0>
explicit Future(Future<T2>&&);
template <
class T2,
typename std::enable_if<
!std::is_same<T, typename std::decay<T2>::type>::value &&
std::is_constructible<T, T2&&>::value,
int>::type = 0>
Future& operator=(Future<T2>&&);
/// Construct a Future from a value (perfect forwarding)
template <
class T2 = T,
typename = typename std::enable_if<
!isFuture<typename std::decay<T2>::type>::value &&
!isSemiFuture<typename std::decay<T2>::type>::value>::type>
/* implicit */ Future(T2&& val);
template <class T2 = T>
/* implicit */ Future(
typename std::enable_if<std::is_same<Unit, T2>::value>::type* = nullptr);
template <
class... Args,
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
type = 0>
explicit Future(in_place_t, Args&&... args);
~Future();
/// Call e->drive() repeatedly until the future is fulfilled. Examples
/// of DrivableExecutor include EventBase and ManualExecutor. Returns a
/// reference to the Try of the value.
Try<T>& getTryVia(DrivableExecutor* e);
/// Call e->drive() repeatedly until the future is fulfilled. Examples
/// of DrivableExecutor include EventBase and ManualExecutor. Returns the
/// value (moved out), or throws the exception.
......@@ -204,7 +334,8 @@ class Future {
*/
template <typename F, typename R = futures::detail::callableResult<T, F>>
typename R::Return then(F&& func) {
return thenImplementation<F, R>(std::forward<F>(func), typename R::Arg());
return this->template thenImplementation<F, R>(
std::forward<F>(func), typename R::Arg());
}
/// Variant where func is an member function
......@@ -235,8 +366,8 @@ class Future {
/// via x, and c executes via the same executor (if any) that f had.
template <class Executor, class Arg, class... Args>
auto then(Executor* x, Arg&& arg, Args&&... args) {
auto oldX = getExecutor();
setExecutor(x);
auto oldX = this->getExecutor();
this->setExecutor(x);
return this->then(std::forward<Arg>(arg), std::forward<Args>(args)...)
.via(oldX);
}
......@@ -315,60 +446,28 @@ class Future {
template <class F>
Future<T> onTimeout(Duration, F&& func, Timekeeper* = nullptr);
/// This is not the method you're looking for.
///
/// This needs to be public because it's used by make* and when*, and it's
/// not worth listing all those and their fancy template signatures as
/// friends. But it's not for public consumption.
template <class F>
void setCallback_(F&& func);
/// A Future's callback is executed when all three of these conditions have
/// become true: it has a value (set by the Promise), it has a callback (set
/// by then), and it is active (active by default).
///
/// Inactive Futures will activate upon destruction.
FOLLY_DEPRECATED("do not use") Future<T>& activate() & {
core_->activate();
this->core_->activate();
return *this;
}
FOLLY_DEPRECATED("do not use") Future<T>& deactivate() & {
core_->deactivate();
this->core_->deactivate();
return *this;
}
FOLLY_DEPRECATED("do not use") Future<T> activate() && {
core_->activate();
this->core_->activate();
return std::move(*this);
}
FOLLY_DEPRECATED("do not use") Future<T> deactivate() && {
core_->deactivate();
this->core_->deactivate();
return std::move(*this);
}
bool isActive() {
return core_->isActive();
}
template <class E>
void raise(E&& exception) {
raise(make_exception_wrapper<typename std::remove_reference<E>::type>(
std::forward<E>(exception)));
}
/// Raise an interrupt. If the promise holder has an interrupt
/// handler it will be called and potentially stop asynchronous work from
/// being done. This is advisory only - a promise holder may not set an
/// interrupt handler, or may do anything including ignore. But, if you know
/// your future supports this the most likely result is stopping or
/// preventing the asynchronous operation (if in time), and the promise
/// holder setting an exception on the future. (That may happen
/// asynchronously, of course.)
void raise(exception_wrapper interrupt);
void cancel() {
raise(FutureCancellation());
}
/// Throw TimedOut if this Future does not complete within the given
/// duration from now. The optional Timeekeeper is as with futures::sleep().
Future<T> within(Duration, Timekeeper* = nullptr);
......@@ -455,8 +554,8 @@ class Future {
auto thenMultiWithExecutor(Executor* x, Callback&& fn, Callbacks&&... fns) {
// thenMultiExecutor with two callbacks is
// via(x).then(a).thenMulti(b, ...).via(oldX)
auto oldX = getExecutor();
setExecutor(x);
auto oldX = this->getExecutor();
this->setExecutor(x);
return then(std::forward<Callback>(fn))
.thenMulti(std::forward<Callbacks>(fns)...)
.via(oldX);
......@@ -473,23 +572,22 @@ class Future {
return then([]{ return Unit{}; });
}
// Convert this Future to a SemiFuture to safely export from a library
// without exposing a continuation interface
SemiFuture<T> semi() {
return SemiFuture<T>{std::move(*this)};
}
protected:
typedef futures::detail::Core<T>* corePtr;
// shared core state object
corePtr core_;
explicit
Future(corePtr obj) : core_(obj) {}
explicit Future(corePtr obj) : SemiFuture<T>(obj) {}
explicit Future(futures::detail::EmptyConstruct) noexcept;
void detach();
void throwIfInvalid() const;
friend class Promise<T>;
template <class> friend class Future;
friend class SemiFuture<T>;
template <class T2>
friend Future<T2> makeFuture(Try<T2>&&);
......@@ -516,23 +614,6 @@ class Future {
/// predicate behaves like std::function<bool(void)>
template <class P, class F>
friend Future<Unit> whileDo(P&& predicate, F&& thunk);
// Variant: returns a value
// e.g. f.then([](Try<T> t){ return t.value(); });
template <typename F, typename R, bool isTry, typename... Args>
typename std::enable_if<!R::ReturnsFuture::value, typename R::Return>::type
thenImplementation(F&& func, futures::detail::argResult<isTry, F, Args...>);
// Variant: returns a Future
// e.g. f.then([](Try<T> t){ return makeFuture<T>(t); });
template <typename F, typename R, bool isTry, typename... Args>
typename std::enable_if<R::ReturnsFuture::value, typename R::Return>::type
thenImplementation(F&& func, futures::detail::argResult<isTry, F, Args...>);
Executor* getExecutor() { return core_->getExecutor(); }
void setExecutor(Executor* x, int8_t priority = Executor::MID_PRI) {
core_->setExecutor(x, priority);
}
};
} // namespace folly
......
......@@ -23,6 +23,8 @@
namespace folly {
// forward declaration
template <class T>
class SemiFuture;
template <class T> class Future;
namespace futures {
......@@ -107,6 +109,8 @@ class Promise {
private:
typedef typename Future<T>::corePtr corePtr;
template <class>
friend class SemiFuture;
template <class> friend class Future;
template <class, class>
friend class futures::detail::CoreCallbackState;
......
......@@ -232,9 +232,12 @@ TEST(Executor, RunnablePtr) {
TEST(Executor, ThrowableThen) {
InlineExecutor x;
auto f = Future<Unit>().then([]() { throw std::runtime_error("Faildog"); });
/*
auto f = Future<Unit>().via(&x).then([](){
throw std::runtime_error("Faildog");
});
});*/
EXPECT_THROW(f.value(), std::exception);
}
......
/*
* Copyright 2017 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/Baton.h>
#include <folly/Executor.h>
#include <folly/Memory.h>
#include <folly/Unit.h>
#include <folly/dynamic.h>
#include <folly/futures/Future.h>
#include <folly/io/async/EventBase.h>
#include <folly/portability/GTest.h>
#include <algorithm>
#include <atomic>
#include <memory>
#include <numeric>
#include <string>
#include <thread>
#include <type_traits>
using namespace folly;
#define EXPECT_TYPE(x, T) EXPECT_TRUE((std::is_same<decltype(x), T>::value))
typedef FutureException eggs_t;
static eggs_t eggs("eggs");
// Future
TEST(SemiFuture, makeEmpty) {
auto f = SemiFuture<int>::makeEmpty();
EXPECT_THROW(f.isReady(), NoState);
}
TEST(SemiFuture, futureDefaultCtor) {
SemiFuture<Unit>();
}
TEST(SemiFuture, makeSemiFutureWithUnit) {
int count = 0;
SemiFuture<Unit> fu = makeSemiFutureWith([&] { count++; });
EXPECT_EQ(1, count);
}
namespace {
SemiFuture<int> onErrorHelperEggs(const eggs_t&) {
return makeSemiFuture(10);
}
SemiFuture<int> onErrorHelperGeneric(const std::exception&) {
return makeSemiFuture(20);
}
} // namespace
TEST(SemiFuture, special) {
EXPECT_FALSE(std::is_copy_constructible<SemiFuture<int>>::value);
EXPECT_FALSE(std::is_copy_assignable<SemiFuture<int>>::value);
EXPECT_TRUE(std::is_move_constructible<SemiFuture<int>>::value);
EXPECT_TRUE(std::is_move_assignable<SemiFuture<int>>::value);
}
TEST(SemiFuture, value) {
auto f = makeSemiFuture(std::unique_ptr<int>(new int(42)));
auto up = std::move(f.value());
EXPECT_EQ(42, *up);
EXPECT_THROW(makeSemiFuture<int>(eggs).value(), eggs_t);
}
TEST(SemiFuture, hasException) {
EXPECT_TRUE(makeSemiFuture<int>(eggs).getTry().hasException());
EXPECT_FALSE(makeSemiFuture(42).getTry().hasException());
}
TEST(SemiFuture, hasValue) {
EXPECT_TRUE(makeSemiFuture(42).getTry().hasValue());
EXPECT_FALSE(makeSemiFuture<int>(eggs).getTry().hasValue());
}
TEST(SemiFuture, makeSemiFuture) {
EXPECT_TYPE(makeSemiFuture(42), SemiFuture<int>);
EXPECT_EQ(42, makeSemiFuture(42).value());
EXPECT_TYPE(makeSemiFuture<float>(42), SemiFuture<float>);
EXPECT_EQ(42, makeSemiFuture<float>(42).value());
auto fun = [] { return 42; };
EXPECT_TYPE(makeSemiFutureWith(fun), SemiFuture<int>);
EXPECT_EQ(42, makeSemiFutureWith(fun).value());
auto funf = [] { return makeSemiFuture<int>(43); };
EXPECT_TYPE(makeSemiFutureWith(funf), SemiFuture<int>);
EXPECT_EQ(43, makeSemiFutureWith(funf).value());
auto failfun = []() -> int { throw eggs; };
EXPECT_TYPE(makeSemiFutureWith(failfun), SemiFuture<int>);
EXPECT_NO_THROW(makeSemiFutureWith(failfun));
EXPECT_THROW(makeSemiFutureWith(failfun).value(), eggs_t);
auto failfunf = []() -> SemiFuture<int> { throw eggs; };
EXPECT_TYPE(makeSemiFutureWith(failfunf), SemiFuture<int>);
EXPECT_NO_THROW(makeSemiFutureWith(failfunf));
EXPECT_THROW(makeSemiFutureWith(failfunf).value(), eggs_t);
EXPECT_TYPE(makeSemiFuture(), SemiFuture<Unit>);
}
TEST(SemiFuture, Constructor) {
auto f1 = []() -> SemiFuture<int> { return SemiFuture<int>(3); }();
EXPECT_EQ(f1.value(), 3);
auto f2 = []() -> SemiFuture<Unit> { return SemiFuture<Unit>(); }();
EXPECT_NO_THROW(f2.value());
}
TEST(SemiFuture, ImplicitConstructor) {
auto f1 = []() -> SemiFuture<int> { return 3; }();
EXPECT_EQ(f1.value(), 3);
}
TEST(SemiFuture, InPlaceConstructor) {
auto f = SemiFuture<std::pair<int, double>>(in_place, 5, 3.2);
EXPECT_EQ(5, f.value().first);
}
TEST(SemiFuture, makeSemiFutureNoThrow) {
makeSemiFuture().value();
}
TEST(SemiFuture, ConstructSemiFutureFromEmptyFuture) {
auto f = SemiFuture<int>{Future<int>::makeEmpty()};
EXPECT_THROW(f.isReady(), NoState);
}
TEST(SemiFuture, ConstructSemiFutureFromFutureDefaultCtor) {
SemiFuture<Unit>(Future<Unit>{});
}
TEST(SemiFuture, MakeSemiFutureFromFutureWithUnit) {
int count = 0;
SemiFuture<Unit> fu = SemiFuture<Unit>{makeFutureWith([&] { count++; })};
EXPECT_EQ(1, count);
}
TEST(SemiFuture, MakeSemiFutureFromFutureWithValue) {
auto f = SemiFuture<std::unique_ptr<int>>{
makeFuture(std::unique_ptr<int>(new int(42)))};
auto up = std::move(f.value());
EXPECT_EQ(42, *up);
}
TEST(SemiFuture, MakeSemiFutureFromReadyFuture) {
Promise<int> p;
auto f = SemiFuture<int>{p.getFuture()};
EXPECT_FALSE(f.isReady());
p.setValue(42);
EXPECT_TRUE(f.isReady());
}
TEST(SemiFuture, MakeSemiFutureFromNotReadyFuture) {
Promise<int> p;
auto f = SemiFuture<int>{p.getFuture()};
EXPECT_THROW(f.value(), eggs_t);
}
TEST(SemiFuture, MakeFutureFromSemiFuture) {
folly::EventBase e;
Promise<int> p;
std::atomic<int> result{0};
auto f = SemiFuture<int>{p.getFuture()};
auto future = std::move(f).via(&e).then([&](int value) {
result = value;
return value;
});
e.loop();
EXPECT_EQ(result, 0);
EXPECT_FALSE(future.isReady());
p.setValue(42);
e.loop();
EXPECT_TRUE(future.isReady());
ASSERT_EQ(future.value(), 42);
ASSERT_EQ(result, 42);
}
TEST(SemiFuture, MakeFutureFromSemiFutureLValue) {
folly::EventBase e;
Promise<int> p;
std::atomic<int> result{0};
auto f = SemiFuture<int>{p.getFuture()};
auto future = f.via(&e).then([&](int value) {
result = value;
return value;
});
e.loop();
EXPECT_EQ(result, 0);
EXPECT_FALSE(future.isReady());
p.setValue(42);
e.loop();
EXPECT_TRUE(future.isReady());
ASSERT_EQ(future.value(), 42);
ASSERT_EQ(result, 42);
}
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