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 { ...@@ -43,7 +43,11 @@ struct Unit {
template <typename T> template <typename T>
struct Lift : std::conditional<std::is_same<T, void>::value, Unit, T> {}; struct Lift : std::conditional<std::is_same<T, void>::value, Unit, T> {};
template <typename 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> {}; 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 { constexpr bool operator==(const Unit& /*other*/) const {
return true; return true;
......
This diff is collapsed.
...@@ -22,6 +22,24 @@ namespace folly { ...@@ -22,6 +22,24 @@ namespace folly {
template <class> class Promise; 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> template <typename T>
struct isFuture : std::false_type { struct isFuture : std::false_type {
using Inner = typename Unit::Lift<T>::type; using Inner = typename Unit::Lift<T>::type;
......
...@@ -21,13 +21,18 @@ ...@@ -21,13 +21,18 @@
namespace folly { namespace folly {
// Instantiate the most common Future types to save compile time // 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<Unit>;
template class Future<bool>; template class Future<bool>;
template class Future<int>; template class Future<int>;
template class Future<int64_t>; template class Future<int64_t>;
template class Future<std::string>; template class Future<std::string>;
template class Future<double>; template class Future<double>;
} }
namespace folly { namespace futures { namespace folly { namespace futures {
......
This diff is collapsed.
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
namespace folly { namespace folly {
// forward declaration // forward declaration
template <class T>
class SemiFuture;
template <class T> class Future; template <class T> class Future;
namespace futures { namespace futures {
...@@ -107,6 +109,8 @@ class Promise { ...@@ -107,6 +109,8 @@ class Promise {
private: private:
typedef typename Future<T>::corePtr corePtr; typedef typename Future<T>::corePtr corePtr;
template <class>
friend class SemiFuture;
template <class> friend class Future; template <class> friend class Future;
template <class, class> template <class, class>
friend class futures::detail::CoreCallbackState; friend class futures::detail::CoreCallbackState;
......
...@@ -232,9 +232,12 @@ TEST(Executor, RunnablePtr) { ...@@ -232,9 +232,12 @@ TEST(Executor, RunnablePtr) {
TEST(Executor, ThrowableThen) { TEST(Executor, ThrowableThen) {
InlineExecutor x; InlineExecutor x;
auto f = Future<Unit>().then([]() { throw std::runtime_error("Faildog"); });
/*
auto f = Future<Unit>().via(&x).then([](){ auto f = Future<Unit>().via(&x).then([](){
throw std::runtime_error("Faildog"); throw std::runtime_error("Faildog");
}); });*/
EXPECT_THROW(f.value(), std::exception); 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