Commit 1e13bc0f authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook Github Bot

Let Future check before adding a continuation

Summary:
[Folly] Let Future check before adding a continuation whether a continuation has already been added.

Requires making wait behave more like timed-wait; alternatively, it would be possible to add a reverse state transition, but there seems not to be a need.

Reviewed By: marshallcline

Differential Revision: D8344923

fbshipit-source-id: f077fb4b83b92c96af9a83e1b1479c4cb41b048a
parent 24d559b8
...@@ -244,6 +244,13 @@ void FutureBase<T>::throwIfInvalid() const { ...@@ -244,6 +244,13 @@ void FutureBase<T>::throwIfInvalid() const {
} }
} }
template <class T>
void FutureBase<T>::throwIfContinued() const {
if (!core_ || core_->hasCallback()) {
throw_exception<FutureAlreadyContinued>();
}
}
template <class T> template <class T>
Optional<Try<T>> FutureBase<T>::poll() { Optional<Try<T>> FutureBase<T>::poll() {
auto& core = getCore(); auto& core = getCore();
...@@ -259,6 +266,7 @@ void FutureBase<T>::raise(exception_wrapper exception) { ...@@ -259,6 +266,7 @@ void FutureBase<T>::raise(exception_wrapper exception) {
template <class T> template <class T>
template <class F> template <class F>
void FutureBase<T>::setCallback_(F&& func) { void FutureBase<T>::setCallback_(F&& func) {
throwIfContinued();
getCore().setCallback(std::forward<F>(func)); getCore().setCallback(std::forward<F>(func));
} }
...@@ -1807,9 +1815,15 @@ void waitImpl(FutureType& f) { ...@@ -1807,9 +1815,15 @@ void waitImpl(FutureType& f) {
return; return;
} }
FutureBatonType baton; Promise<T> promise;
f.setCallback_([&](const Try<T>& /* t */) { baton.post(); }); auto ret = promise.getSemiFuture();
baton.wait(); auto baton = std::make_shared<FutureBatonType>();
f.setCallback_([baton, promise = std::move(promise)](Try<T>&& t) mutable {
promise.setTry(std::move(t));
baton->post();
});
convertFuture(std::move(ret), f);
baton->wait();
assert(f.isReady()); assert(f.isReady());
} }
......
...@@ -57,6 +57,11 @@ class FOLLY_EXPORT FutureInvalid : public FutureException { ...@@ -57,6 +57,11 @@ class FOLLY_EXPORT FutureInvalid : public FutureException {
FutureInvalid() : FutureException("Future invalid") {} FutureInvalid() : FutureException("Future invalid") {}
}; };
class FOLLY_EXPORT FutureAlreadyContinued : public FutureException {
public:
FutureAlreadyContinued() : FutureException("Future already continued") {}
};
class FOLLY_EXPORT FutureNotReady : public FutureException { class FOLLY_EXPORT FutureNotReady : public FutureException {
public: public:
FutureNotReady() : FutureException("Future not ready") {} FutureNotReady() : FutureException("Future not ready") {}
...@@ -386,6 +391,7 @@ class FutureBase { ...@@ -386,6 +391,7 @@ class FutureBase {
void detach(); void detach();
void throwIfInvalid() const; void throwIfInvalid() const;
void throwIfContinued() const;
void assign(FutureBase<T>&& other) noexcept; void assign(FutureBase<T>&& other) noexcept;
...@@ -1789,6 +1795,7 @@ class Future : private futures::detail::FutureBase<T> { ...@@ -1789,6 +1795,7 @@ class Future : private futures::detail::FutureBase<T> {
friend class FutureSplitter; friend class FutureSplitter;
using Base::setExecutor; using Base::setExecutor;
using Base::throwIfContinued;
using Base::throwIfInvalid; using Base::throwIfInvalid;
using typename Base::corePtr; using typename Base::corePtr;
......
...@@ -209,6 +209,14 @@ class Core final { ...@@ -209,6 +209,14 @@ class Core final {
Core(Core&&) noexcept = delete; Core(Core&&) noexcept = delete;
Core& operator=(Core&&) = delete; Core& operator=(Core&&) = delete;
/// May call from any thread
bool hasCallback() const noexcept {
constexpr auto allowed = State::OnlyCallback | State::Done;
auto const ans = State() != (fsm_.getState() & allowed);
assert(!ans || !!callback_); // callback_ must exist in this state
return ans;
}
/// May call from any thread /// May call from any thread
/// ///
/// True if state is OnlyResult or Done. /// True if state is OnlyResult or Done.
......
...@@ -352,7 +352,10 @@ TEST(Wait, WaitPlusThen) { ...@@ -352,7 +352,10 @@ TEST(Wait, WaitPlusThen) {
EXPECT_FALSE(f.isReady()); // deterministically passes in practice EXPECT_FALSE(f.isReady()); // deterministically passes in practice
f.wait(); f.wait();
EXPECT_THROW(f.then([](auto&&) {}), std::logic_error); EXPECT_TRUE(f.isReady());
auto continuation = 0;
EXPECT_NO_THROW(f.then([&](auto&& v) { continuation = v; }));
EXPECT_EQ(continuation, 42);
t.join(); t.join();
} }
...@@ -436,9 +439,12 @@ TEST(Wait, WaitPlusThen) { ...@@ -436,9 +439,12 @@ TEST(Wait, WaitPlusThen) {
EXPECT_FALSE(f.isReady()); // deterministically passes in practice EXPECT_FALSE(f.isReady()); // deterministically passes in practice
f.wait(); f.wait();
EXPECT_TRUE(f.isReady());
auto continuation = 0;
InlineExecutor e; InlineExecutor e;
auto f2 = std::move(f).via(&e); auto f2 = std::move(f).via(&e);
EXPECT_THROW(f2.then([](auto&&) {}), std::logic_error); EXPECT_NO_THROW(f2.then([&](auto&& v) { continuation = v; }));
EXPECT_EQ(continuation, 42);
t.join(); t.join();
} }
......
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