Add SemiFuture class.
authorLee Howes <lwh@fb.com>
Thu, 5 Oct 2017 02:59:14 +0000 (19:59 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Thu, 5 Oct 2017 03:09:08 +0000 (20:09 -0700)
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
folly/futures/Future-inl.h
folly/futures/Future-pre.h
folly/futures/Future.cpp
folly/futures/Future.h
folly/futures/Promise.h
folly/futures/test/ExecutorTest.cpp
folly/futures/test/SemiFutureTest.cpp [new file with mode: 0644]

index 0f90ff6e40f8b7669307b6a67ff66659906033b1..dfcce1d3ff37b13ca20d9fff9008f483f3302b08 100644 (file)
@@ -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;
index 3f2f54fbf6e015149d161c4dcfb5b55ce666c777..18130baf7c1a5ac6a802d2e994a7568424563e0e 100644 (file)
@@ -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
index 8d1fd98f04940bf3de21bc2b199e62b9fa031907..339e6a12a4caabb441a04a5c521ff8fcfe2d76e1 100644 (file)
@@ -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;
index e53331b45bd500899c4164bad2fa9f4f8bc9715f..dc419433ff84c9f00106e532a23e2fe6fd2915b1 100644 (file)
 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 {
index 2d5d4cbc44fa79aa0ea8370df5d78c755e9a7cf1..1c8faffdb7fddd8ffbecb44a41ef2291f66629d5 100644 (file)
 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
index 42477263fabc6c75862304807543668f8ba06c89..6802d4d00cb3d8e00ecfa538fe990c17d83edc01 100644 (file)
@@ -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;
index d4b24e0974cf567cb838d9620494ce3efac2232c..6751487206cc36606bdff2f02ab90e550aaea4eb 100644 (file)
@@ -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);
 }
 
diff --git a/folly/futures/test/SemiFutureTest.cpp b/folly/futures/test/SemiFutureTest.cpp
new file mode 100644 (file)
index 0000000..d3cb1a2
--- /dev/null
@@ -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 <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);
+}