From a95a6976020356513a4c0dc8a3c8557c0b2b4496 Mon Sep 17 00:00:00 2001 From: Lee Howes Date: Wed, 4 Oct 2017 19:59:14 -0700 Subject: [PATCH] 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 --- folly/Unit.h | 4 + folly/futures/Future-inl.h | 444 +++++++++++++++++--------- folly/futures/Future-pre.h | 18 ++ folly/futures/Future.cpp | 7 +- folly/futures/Future.h | 299 ++++++++++------- folly/futures/Promise.h | 4 + folly/futures/test/ExecutorTest.cpp | 5 +- folly/futures/test/SemiFutureTest.cpp | 213 ++++++++++++ 8 files changed, 732 insertions(+), 262 deletions(-) create mode 100644 folly/futures/test/SemiFutureTest.cpp diff --git a/folly/Unit.h b/folly/Unit.h index 0f90ff6e..dfcce1d3 100644 --- a/folly/Unit.h +++ b/folly/Unit.h @@ -43,7 +43,11 @@ struct Unit { template struct Lift : std::conditional::value, Unit, T> {}; template + using LiftT = typename Lift::type; + template struct Drop : std::conditional::value, void, T> {}; + template + using DropT = typename Drop::type; constexpr bool operator==(const Unit& /*other*/) const { return true; diff --git a/folly/futures/Future-inl.h b/folly/futures/Future-inl.h index 3f2f54fb..18130baf 100644 --- a/folly/futures/Future-inl.h +++ b/folly/futures/Future-inl.h @@ -134,21 +134,235 @@ inline auto makeCoreCallbackState(Promise&& p, F&& f) noexcept( } // namespace futures template -Future Future::makeEmpty() { - return Future(futures::detail::EmptyConstruct{}); +SemiFuture::type> makeSemiFuture(T&& t) { + return makeSemiFuture(Try::type>(std::forward(t))); +} + +inline SemiFuture makeSemiFuture() { + return makeSemiFuture(Unit{}); +} + +// makeSemiFutureWith(SemiFuture()) -> SemiFuture +template +typename std::enable_if< + isSemiFuture::type>::value, + typename std::result_of::type>::type +makeSemiFutureWith(F&& func) { + using InnerType = + typename isSemiFuture::type>::Inner; + try { + return std::forward(func)(); + } catch (std::exception& e) { + return makeSemiFuture( + exception_wrapper(std::current_exception(), e)); + } catch (...) { + return makeSemiFuture( + exception_wrapper(std::current_exception())); + } +} + +// makeSemiFutureWith(T()) -> SemiFuture +// makeSemiFutureWith(void()) -> SemiFuture +template +typename std::enable_if< + !(isSemiFuture::type>::value), + SemiFuture::type>>>::type +makeSemiFutureWith(F&& func) { + using LiftedResult = Unit::LiftT::type>; + return makeSemiFuture( + makeTryWith([&func]() mutable { return std::forward(func)(); })); +} + +template +SemiFuture makeSemiFuture(std::exception_ptr const& e) { + return makeSemiFuture(Try(e)); +} + +template +SemiFuture makeSemiFuture(exception_wrapper ew) { + return makeSemiFuture(Try(std::move(ew))); +} + +template +typename std:: + enable_if::value, SemiFuture>::type + makeSemiFuture(E const& e) { + return makeSemiFuture(Try(make_exception_wrapper(e))); } template -Future::Future(Future&& other) noexcept : core_(other.core_) { +SemiFuture makeSemiFuture(Try&& t) { + return SemiFuture(new futures::detail::Core(std::move(t))); +} + +template +SemiFuture SemiFuture::makeEmpty() { + return SemiFuture(futures::detail::EmptyConstruct{}); +} + +template +SemiFuture::SemiFuture(SemiFuture&& other) noexcept : core_(other.core_) { other.core_ = nullptr; } template -Future& Future::operator=(Future&& other) noexcept { +SemiFuture& SemiFuture::operator=(SemiFuture&& other) noexcept { std::swap(core_, other.core_); return *this; } +template +SemiFuture::SemiFuture(Future&& other) noexcept : core_(other.core_) { + other.core_ = nullptr; +} + +template +SemiFuture& SemiFuture::operator=(Future&& other) noexcept { + std::swap(core_, other.core_); + return *this; +} + +template +template +SemiFuture::SemiFuture(T2&& val) + : core_(new futures::detail::Core(Try(std::forward(val)))) {} + +template +template +SemiFuture::SemiFuture( + typename std::enable_if::value>::type*) + : core_(new futures::detail::Core(Try(T()))) {} + +template +template < + class... Args, + typename std::enable_if::value, int>:: + type> +SemiFuture::SemiFuture(in_place_t, Args&&... args) + : core_( + new futures::detail::Core(in_place, std::forward(args)...)) { +} + +template +SemiFuture::~SemiFuture() { + detach(); +} + +template +typename std::add_lvalue_reference::type SemiFuture::value() { + throwIfInvalid(); + + return core_->getTry().value(); +} + +template +typename std::add_lvalue_reference::type SemiFuture::value() const { + throwIfInvalid(); + + return core_->getTry().value(); +} + +template +inline Future SemiFuture::via(Executor* executor, int8_t priority) && { + throwIfInvalid(); + + setExecutor(executor, priority); + + auto newFuture = Future(core_); + core_ = nullptr; + return newFuture; +} + +template +inline Future SemiFuture::via(Executor* executor, int8_t priority) & { + throwIfInvalid(); + Promise p; + auto f = p.getFuture(); + auto func = [p = std::move(p)](Try&& t) mutable { + p.setTry(std::move(t)); + }; + using R = futures::detail::callableResult; + thenImplementation(std::move(func), typename R::Arg()); + return std::move(f).via(executor, priority); +} + +template +bool SemiFuture::isReady() const { + throwIfInvalid(); + return core_->ready(); +} + +template +bool SemiFuture::hasValue() { + return getTry().hasValue(); +} + +template +bool SemiFuture::hasException() { + return getTry().hasException(); +} + +template +void SemiFuture::detach() { + if (core_) { + core_->detachFuture(); + core_ = nullptr; + } +} + +template +Try& SemiFuture::getTry() { + throwIfInvalid(); + + return core_->getTry(); +} + +template +void SemiFuture::throwIfInvalid() const { + if (!core_) + throwNoState(); +} + +template +Optional> SemiFuture::poll() { + Optional> o; + if (core_->ready()) { + o = std::move(core_->getTry()); + } + return o; +} + +template +void SemiFuture::raise(exception_wrapper exception) { + core_->raise(std::move(exception)); +} + +template +template +void SemiFuture::setCallback_(F&& func) { + throwIfInvalid(); + core_->setCallback(std::forward(func)); +} + +template +SemiFuture::SemiFuture(futures::detail::EmptyConstruct) noexcept + : core_(nullptr) {} + +template +Future Future::makeEmpty() { + return Future(futures::detail::EmptyConstruct{}); +} + +template +Future::Future(Future&& other) noexcept + : SemiFuture(std::move(other)) {} + +template +Future& Future::operator=(Future&& other) noexcept { + SemiFuture::operator=(SemiFuture{std::move(other)}); + return *this; +} + template template < class T2, @@ -183,15 +397,15 @@ Future& Future::operator=(Future&& other) { std::move(other).then([](T2&& v) { return T(std::move(v)); })); } +// TODO: isSemiFuture template template -Future::Future(T2&& val) - : core_(new futures::detail::Core(Try(std::forward(val)))) {} +Future::Future(T2&& val) : SemiFuture(std::forward(val)) {} template template Future::Future(typename std::enable_if::value>::type*) - : core_(new futures::detail::Core(Try(T()))) {} + : SemiFuture() {} template template < @@ -199,34 +413,10 @@ template < typename std::enable_if::value, int>:: type> Future::Future(in_place_t, Args&&... args) - : core_( - new futures::detail::Core(in_place, std::forward(args)...)) { -} + : SemiFuture(in_place, std::forward(args)...) {} template Future::~Future() { - detach(); -} - -template -void Future::detach() { - if (core_) { - core_->detachFuture(); - core_ = nullptr; - } -} - -template -void Future::throwIfInvalid() const { - if (!core_) - throwNoState(); -} - -template -template -void Future::setCallback_(F&& func) { - throwIfInvalid(); - core_->setCallback(std::forward(func)); } // unwrap @@ -248,20 +438,20 @@ Future::unwrap() { template template typename std::enable_if::type -Future::thenImplementation( +SemiFuture::thenImplementation( F&& func, futures::detail::argResult) { static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument"); typedef typename R::ReturnsFuture::Inner B; - throwIfInvalid(); + this->throwIfInvalid(); Promise 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::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(func))](Try && t) mutable { + std::move(p), std::forward(func))](Try&& t) mutable { + if (!isTry && t.hasException()) { state.setException(std::move(t.exception())); } else { @@ -302,7 +493,6 @@ Future::thenImplementation( [&] { return state.invoke(t.template get()...); })); } }); - return f; } @@ -311,24 +501,23 @@ Future::thenImplementation( template template typename std::enable_if::type -Future::thenImplementation( +SemiFuture::thenImplementation( F&& func, futures::detail::argResult) { static_assert(sizeof...(Args) <= 1, "Then must take zero/one argument"); typedef typename R::ReturnsFuture::Inner B; - - throwIfInvalid(); + this->throwIfInvalid(); Promise 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(func))](Try && t) mutable { + std::move(p), std::forward(func))](Try&& t) mutable { if (!isTry && t.hasException()) { state.setException(std::move(t.exception())); } else { @@ -353,6 +542,7 @@ Future::then(R(Caller::*func)(Args...), Caller *instance) { typedef typename std::remove_cv::FirstArg>::type>::type FirstArg; + return then([instance, func](Try&& t){ return (instance->*func)(t.template get::value, Args>()...); }); @@ -380,12 +570,12 @@ Future::onError(F&& func) { "Return type of onError callback must be T or Future"); Promise 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(func))](Try && t) mutable { + std::move(p), std::forward(func))](Try&& t) mutable { if (auto e = t.template tryGetExceptionObject()) { state.setTry(makeTryWith([&] { return state.invoke(*e); })); } else { @@ -416,9 +606,9 @@ Future::onError(F&& func) { Promise p; auto f = p.getFuture(); - setCallback_( + this->setCallback_( [state = futures::detail::makeCoreCallbackState( - std::move(p), std::forward(func))](Try && t) mutable { + std::move(p), std::forward(func))](Try&& t) mutable { if (auto e = t.template tryGetExceptionObject()) { auto tf2 = state.tryInvoke(*e); if (tf2.hasException()) { @@ -466,7 +656,7 @@ Future::onError(F&& func) { Promise p; auto f = p.getFuture(); - setCallback_( + this->setCallback_( [state = futures::detail::makeCoreCallbackState( std::move(p), std::forward(func))](Try t) mutable { if (t.hasException()) { @@ -501,9 +691,9 @@ Future::onError(F&& func) { Promise p; auto f = p.getFuture(); - setCallback_( + this->setCallback_( [state = futures::detail::makeCoreCallbackState( - std::move(p), std::forward(func))](Try && t) mutable { + std::move(p), std::forward(func))](Try&& t) mutable { if (t.hasException()) { state.setTry(makeTryWith( [&] { return state.invoke(std::move(t.exception())); })); @@ -515,60 +705,11 @@ Future::onError(F&& func) { return f; } -template -typename std::add_lvalue_reference::type Future::value() { - throwIfInvalid(); - - return core_->getTry().value(); -} - -template -typename std::add_lvalue_reference::type Future::value() const { - throwIfInvalid(); - - return core_->getTry().value(); -} - -template -Try& Future::getTry() { - throwIfInvalid(); - - return core_->getTry(); -} - template Try& Future::getTryVia(DrivableExecutor* e) { return waitVia(e).getTry(); } -template -Optional> Future::poll() { - Optional> o; - if (core_->ready()) { - o = std::move(core_->getTry()); - } - return o; -} - -template -inline Future Future::via(Executor* executor, int8_t priority) && { - throwIfInvalid(); - - setExecutor(executor, priority); - - return std::move(*this); -} - -template -inline Future Future::via(Executor* executor, int8_t priority) & { - throwIfInvalid(); - - Promise p; - auto f = p.getFuture(); - then([p = std::move(p)](Try && t) mutable { p.setTry(std::move(t)); }); - return std::move(f).via(executor, priority); -} - template auto via(Executor* x, Func&& func) -> Future()())>::Inner> { @@ -577,28 +718,8 @@ auto via(Executor* x, Func&& func) } template -bool Future::isReady() const { - throwIfInvalid(); - return core_->ready(); -} - -template -bool Future::hasValue() { - return getTry().hasValue(); -} - -template -bool Future::hasException() { - return getTry().hasException(); -} - -template -void Future::raise(exception_wrapper exception) { - core_->raise(std::move(exception)); -} - -template -Future::Future(futures::detail::EmptyConstruct) noexcept : core_(nullptr) {} +Future::Future(futures::detail::EmptyConstruct) noexcept + : SemiFuture(futures::detail::EmptyConstruct{}) {} // makeFuture @@ -607,8 +728,7 @@ Future::type> makeFuture(T&& t) { return makeFuture(Try::type>(std::forward(t))); } -inline // for multiple translation units -Future makeFuture() { +inline Future makeFuture() { return makeFuture(Unit{}); } @@ -634,10 +754,9 @@ makeFutureWith(F&& func) { template typename std::enable_if< !(isFuture::type>::value), - Future::type>::type>>::type + Future::type>>>::type makeFutureWith(F&& func) { - using LiftedResult = - typename Unit::Lift::type>::type; + using LiftedResult = Unit::LiftT::type>; return makeFuture( makeTryWith([&func]() mutable { return std::forward(func)(); })); } @@ -1104,7 +1223,7 @@ Future Future::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 Future::delayed(Duration dur, Timekeeper* tk) { namespace futures { namespace detail { -template -void waitImpl(Future& f) { +template +void waitImpl(FutureType& f) { // short-circuit if there's nothing to do if (f.isReady()) return; @@ -1132,8 +1251,8 @@ void waitImpl(Future& f) { assert(f.isReady()); } -template -void waitImpl(Future& f, Duration dur) { +template +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& f, DrivableExecutor* e) { } // namespace detail } // namespace futures +template +SemiFuture& SemiFuture::wait() & { + futures::detail::waitImpl(*this); + return *this; +} + +template +SemiFuture&& SemiFuture::wait() && { + futures::detail::waitImpl(*this); + return std::move(*this); +} + +template +SemiFuture& SemiFuture::wait(Duration dur) & { + futures::detail::waitImpl(*this, dur); + return *this; +} + +template +SemiFuture&& SemiFuture::wait(Duration dur) && { + futures::detail::waitImpl(*this, dur); + return std::move(*this); +} + +template +T SemiFuture::get() { + return std::move(wait().value()); +} + +template +T SemiFuture::get(Duration dur) { + wait(dur); + if (this->isReady()) { + return std::move(this->value()); + } else { + throwTimedOut(); + } +} + template Future& Future::wait() & { futures::detail::waitImpl(*this); @@ -1205,21 +1363,6 @@ Future&& Future::waitVia(DrivableExecutor* e) && { return std::move(*this); } -template -T Future::get() { - return std::move(wait().value()); -} - -template -T Future::get(Duration dur) { - wait(dur); - if (isReady()) { - return std::move(value()); - } else { - throwTimedOut(); - } -} - template T Future::getVia(DrivableExecutor* e) { return std::move(waitVia(e).value()); @@ -1527,5 +1670,4 @@ extern template class Future; extern template class Future; extern template class Future; extern template class Future; - } // namespace folly diff --git a/folly/futures/Future-pre.h b/folly/futures/Future-pre.h index 8d1fd98f..339e6a12 100644 --- a/folly/futures/Future-pre.h +++ b/folly/futures/Future-pre.h @@ -22,6 +22,24 @@ namespace folly { template class Promise; +template +class SemiFuture; + +template +struct isSemiFuture : std::false_type { + using Inner = typename Unit::Lift::type; +}; + +template +struct isSemiFuture> : std::true_type { + typedef T Inner; +}; + +template +struct isSemiFuture> : std::true_type { + typedef T Inner; +}; + template struct isFuture : std::false_type { using Inner = typename Unit::Lift::type; diff --git a/folly/futures/Future.cpp b/folly/futures/Future.cpp index e53331b4..dc419433 100644 --- a/folly/futures/Future.cpp +++ b/folly/futures/Future.cpp @@ -21,13 +21,18 @@ namespace folly { // Instantiate the most common Future types to save compile time +template class SemiFuture; +template class SemiFuture; +template class SemiFuture; +template class SemiFuture; +template class SemiFuture; +template class SemiFuture; template class Future; template class Future; template class Future; template class Future; template class Future; template class Future; - } namespace folly { namespace futures { diff --git a/folly/futures/Future.h b/folly/futures/Future.h index 2d5d4cbc..1c8faffd 100644 --- a/folly/futures/Future.h +++ b/folly/futures/Future.h @@ -42,62 +42,45 @@ namespace folly { template -class Future { +class Future; + +template +class SemiFuture { public: typedef T value_type; - static Future makeEmpty(); // equivalent to moved-from + static SemiFuture 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::type>::value && - std::is_constructible::value && - std::is_convertible::value, - int>::type = 0> - /* implicit */ Future(Future&&); - template < - class T2, - typename std::enable_if< - !std::is_same::type>::value && - std::is_constructible::value && - !std::is_convertible::value, - int>::type = 0> - explicit Future(Future&&); - template < - class T2, - typename std::enable_if< - !std::is_same::type>::value && - std::is_constructible::value, - int>::type = 0> - Future& operator=(Future&&); + // safe move-constructabilty from Future + /* implicit */ SemiFuture(Future&&) noexcept; + SemiFuture& operator=(Future&&) noexcept; /// Construct a Future from a value (perfect forwarding) - template ::type>::value>::type> - /* implicit */ Future(T2&& val); + template < + class T2 = T, + typename = typename std::enable_if< + !isFuture::type>::value>::type> + /* implicit */ SemiFuture(T2&& val); template - /* implicit */ Future( + /* implicit */ SemiFuture( typename std::enable_if::value>::type* = nullptr); template < class... Args, typename std::enable_if::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& 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& getTryVia(DrivableExecutor* e); - /// If the promise has been fulfilled, return an Optional with the Try. /// Otherwise return an empty Optional. /// Note that this moves the Try 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& wait() &; + + /// Overload of wait() for rvalue Futures + SemiFuture&& wait() &&; + + /// Block until this Future is complete or until the given Duration passes. + /// Returns a reference to this Future + SemiFuture& wait(Duration) &; + + /// Overload of wait(Duration) for rvalue Futures + SemiFuture&& 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 + void setCallback_(F&& func); + + bool isActive() { + return core_->isActive(); + } + + template + void raise(E&& exception) { + raise(make_exception_wrapper::type>( + std::forward(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* 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; + template + friend class SemiFuture; + + template + friend SemiFuture makeSemiFuture(Try&&); + + 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){ return t.value(); }); + template + typename std::enable_if::type + thenImplementation(F&& func, futures::detail::argResult); + + // Variant: returns a Future + // e.g. f.then([](Try t){ return makeFuture(t); }); + template + typename std::enable_if::type + thenImplementation(F&& func, futures::detail::argResult); +}; + +template +class Future : public SemiFuture { + public: + typedef T value_type; + + static Future 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::type>::value && + std::is_constructible::value && + std::is_convertible::value, + int>::type = 0> + /* implicit */ Future(Future&&); + template < + class T2, + typename std::enable_if< + !std::is_same::type>::value && + std::is_constructible::value && + !std::is_convertible::value, + int>::type = 0> + explicit Future(Future&&); + template < + class T2, + typename std::enable_if< + !std::is_same::type>::value && + std::is_constructible::value, + int>::type = 0> + Future& operator=(Future&&); + + /// Construct a Future from a value (perfect forwarding) + template < + class T2 = T, + typename = typename std::enable_if< + !isFuture::type>::value && + !isSemiFuture::type>::value>::type> + /* implicit */ Future(T2&& val); + + template + /* implicit */ Future( + typename std::enable_if::value>::type* = nullptr); + + template < + class... Args, + typename std::enable_if::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& 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 R::Return then(F&& func) { - return thenImplementation(std::forward(func), typename R::Arg()); + return this->template thenImplementation( + std::forward(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 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), std::forward(args)...) .via(oldX); } @@ -315,60 +446,28 @@ class Future { template Future 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 - 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& activate() & { - core_->activate(); + this->core_->activate(); return *this; } FOLLY_DEPRECATED("do not use") Future& deactivate() & { - core_->deactivate(); + this->core_->deactivate(); return *this; } FOLLY_DEPRECATED("do not use") Future activate() && { - core_->activate(); + this->core_->activate(); return std::move(*this); } FOLLY_DEPRECATED("do not use") Future deactivate() && { - core_->deactivate(); + this->core_->deactivate(); return std::move(*this); } - bool isActive() { - return core_->isActive(); - } - - template - void raise(E&& exception) { - raise(make_exception_wrapper::type>( - std::forward(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 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(fn)) .thenMulti(std::forward(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 semi() { + return SemiFuture{std::move(*this)}; + } + protected: typedef futures::detail::Core* corePtr; - // shared core state object - corePtr core_; - - explicit - Future(corePtr obj) : core_(obj) {} + explicit Future(corePtr obj) : SemiFuture(obj) {} explicit Future(futures::detail::EmptyConstruct) noexcept; - void detach(); - - void throwIfInvalid() const; - friend class Promise; template friend class Future; + friend class SemiFuture; template friend Future makeFuture(Try&&); @@ -516,23 +614,6 @@ class Future { /// predicate behaves like std::function template friend Future whileDo(P&& predicate, F&& thunk); - - // Variant: returns a value - // e.g. f.then([](Try t){ return t.value(); }); - template - typename std::enable_if::type - thenImplementation(F&& func, futures::detail::argResult); - - // Variant: returns a Future - // e.g. f.then([](Try t){ return makeFuture(t); }); - template - typename std::enable_if::type - thenImplementation(F&& func, futures::detail::argResult); - - Executor* getExecutor() { return core_->getExecutor(); } - void setExecutor(Executor* x, int8_t priority = Executor::MID_PRI) { - core_->setExecutor(x, priority); - } }; } // namespace folly diff --git a/folly/futures/Promise.h b/folly/futures/Promise.h index 42477263..6802d4d0 100644 --- a/folly/futures/Promise.h +++ b/folly/futures/Promise.h @@ -23,6 +23,8 @@ namespace folly { // forward declaration +template +class SemiFuture; template class Future; namespace futures { @@ -107,6 +109,8 @@ class Promise { private: typedef typename Future::corePtr corePtr; + template + friend class SemiFuture; template friend class Future; template friend class futures::detail::CoreCallbackState; diff --git a/folly/futures/test/ExecutorTest.cpp b/folly/futures/test/ExecutorTest.cpp index d4b24e09..67514872 100644 --- a/folly/futures/test/ExecutorTest.cpp +++ b/folly/futures/test/ExecutorTest.cpp @@ -232,9 +232,12 @@ TEST(Executor, RunnablePtr) { TEST(Executor, ThrowableThen) { InlineExecutor x; + auto f = Future().then([]() { throw std::runtime_error("Faildog"); }); + + /* auto f = Future().via(&x).then([](){ throw std::runtime_error("Faildog"); - }); + });*/ EXPECT_THROW(f.value(), std::exception); } diff --git a/folly/futures/test/SemiFutureTest.cpp b/folly/futures/test/SemiFutureTest.cpp new file mode 100644 index 00000000..d3cb1a26 --- /dev/null +++ b/folly/futures/test/SemiFutureTest.cpp @@ -0,0 +1,213 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace folly; + +#define EXPECT_TYPE(x, T) EXPECT_TRUE((std::is_same::value)) + +typedef FutureException eggs_t; +static eggs_t eggs("eggs"); + +// Future + +TEST(SemiFuture, makeEmpty) { + auto f = SemiFuture::makeEmpty(); + EXPECT_THROW(f.isReady(), NoState); +} + +TEST(SemiFuture, futureDefaultCtor) { + SemiFuture(); +} + +TEST(SemiFuture, makeSemiFutureWithUnit) { + int count = 0; + SemiFuture fu = makeSemiFutureWith([&] { count++; }); + EXPECT_EQ(1, count); +} + +namespace { +SemiFuture onErrorHelperEggs(const eggs_t&) { + return makeSemiFuture(10); +} +SemiFuture onErrorHelperGeneric(const std::exception&) { + return makeSemiFuture(20); +} +} // namespace + +TEST(SemiFuture, special) { + EXPECT_FALSE(std::is_copy_constructible>::value); + EXPECT_FALSE(std::is_copy_assignable>::value); + EXPECT_TRUE(std::is_move_constructible>::value); + EXPECT_TRUE(std::is_move_assignable>::value); +} + +TEST(SemiFuture, value) { + auto f = makeSemiFuture(std::unique_ptr(new int(42))); + auto up = std::move(f.value()); + EXPECT_EQ(42, *up); + + EXPECT_THROW(makeSemiFuture(eggs).value(), eggs_t); +} + +TEST(SemiFuture, hasException) { + EXPECT_TRUE(makeSemiFuture(eggs).getTry().hasException()); + EXPECT_FALSE(makeSemiFuture(42).getTry().hasException()); +} + +TEST(SemiFuture, hasValue) { + EXPECT_TRUE(makeSemiFuture(42).getTry().hasValue()); + EXPECT_FALSE(makeSemiFuture(eggs).getTry().hasValue()); +} + +TEST(SemiFuture, makeSemiFuture) { + EXPECT_TYPE(makeSemiFuture(42), SemiFuture); + EXPECT_EQ(42, makeSemiFuture(42).value()); + + EXPECT_TYPE(makeSemiFuture(42), SemiFuture); + EXPECT_EQ(42, makeSemiFuture(42).value()); + + auto fun = [] { return 42; }; + EXPECT_TYPE(makeSemiFutureWith(fun), SemiFuture); + EXPECT_EQ(42, makeSemiFutureWith(fun).value()); + + auto funf = [] { return makeSemiFuture(43); }; + EXPECT_TYPE(makeSemiFutureWith(funf), SemiFuture); + EXPECT_EQ(43, makeSemiFutureWith(funf).value()); + + auto failfun = []() -> int { throw eggs; }; + EXPECT_TYPE(makeSemiFutureWith(failfun), SemiFuture); + EXPECT_NO_THROW(makeSemiFutureWith(failfun)); + EXPECT_THROW(makeSemiFutureWith(failfun).value(), eggs_t); + + auto failfunf = []() -> SemiFuture { throw eggs; }; + EXPECT_TYPE(makeSemiFutureWith(failfunf), SemiFuture); + EXPECT_NO_THROW(makeSemiFutureWith(failfunf)); + EXPECT_THROW(makeSemiFutureWith(failfunf).value(), eggs_t); + + EXPECT_TYPE(makeSemiFuture(), SemiFuture); +} + +TEST(SemiFuture, Constructor) { + auto f1 = []() -> SemiFuture { return SemiFuture(3); }(); + EXPECT_EQ(f1.value(), 3); + auto f2 = []() -> SemiFuture { return SemiFuture(); }(); + EXPECT_NO_THROW(f2.value()); +} + +TEST(SemiFuture, ImplicitConstructor) { + auto f1 = []() -> SemiFuture { return 3; }(); + EXPECT_EQ(f1.value(), 3); +} + +TEST(SemiFuture, InPlaceConstructor) { + auto f = SemiFuture>(in_place, 5, 3.2); + EXPECT_EQ(5, f.value().first); +} + +TEST(SemiFuture, makeSemiFutureNoThrow) { + makeSemiFuture().value(); +} + +TEST(SemiFuture, ConstructSemiFutureFromEmptyFuture) { + auto f = SemiFuture{Future::makeEmpty()}; + EXPECT_THROW(f.isReady(), NoState); +} + +TEST(SemiFuture, ConstructSemiFutureFromFutureDefaultCtor) { + SemiFuture(Future{}); +} + +TEST(SemiFuture, MakeSemiFutureFromFutureWithUnit) { + int count = 0; + SemiFuture fu = SemiFuture{makeFutureWith([&] { count++; })}; + EXPECT_EQ(1, count); +} + +TEST(SemiFuture, MakeSemiFutureFromFutureWithValue) { + auto f = SemiFuture>{ + makeFuture(std::unique_ptr(new int(42)))}; + auto up = std::move(f.value()); + EXPECT_EQ(42, *up); +} + +TEST(SemiFuture, MakeSemiFutureFromReadyFuture) { + Promise p; + auto f = SemiFuture{p.getFuture()}; + EXPECT_FALSE(f.isReady()); + p.setValue(42); + EXPECT_TRUE(f.isReady()); +} + +TEST(SemiFuture, MakeSemiFutureFromNotReadyFuture) { + Promise p; + auto f = SemiFuture{p.getFuture()}; + EXPECT_THROW(f.value(), eggs_t); +} + +TEST(SemiFuture, MakeFutureFromSemiFuture) { + folly::EventBase e; + Promise p; + std::atomic result{0}; + auto f = SemiFuture{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 p; + std::atomic result{0}; + auto f = SemiFuture{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); +} -- 2.34.1