Commit bb76b0f7 authored by Phil Willoughby's avatar Phil Willoughby Committed by Facebook Github Bot

Replace the future-splitting system

Summary:
The previous mechanism in SharedPromise doesn't work if the lifetime of the
SharedPromise object ends before the Future which it was splitting is
completed.

This variation on the same theme doesn't have that problem.

Reviewed By: spacedentist

Differential Revision: D4339670

fbshipit-source-id: f619762bff1390481715575b3e638ec26b5c4edd
parent 1ce2015a
...@@ -161,6 +161,7 @@ nobase_follyinclude_HEADERS = \ ...@@ -161,6 +161,7 @@ nobase_follyinclude_HEADERS = \
futures/Future.h \ futures/Future.h \
futures/Future-inl.h \ futures/Future-inl.h \
futures/FutureException.h \ futures/FutureException.h \
futures/FutureSplitter.h \
futures/InlineExecutor.h \ futures/InlineExecutor.h \
futures/ManualExecutor.h \ futures/ManualExecutor.h \
futures/OpaqueCallbackShunt.h \ futures/OpaqueCallbackShunt.h \
......
...@@ -69,4 +69,7 @@ class PredicateDoesNotObtain : public FutureException { ...@@ -69,4 +69,7 @@ class PredicateDoesNotObtain : public FutureException {
PredicateDoesNotObtain() : FutureException("Predicate does not obtain") {} PredicateDoesNotObtain() : FutureException("Predicate does not obtain") {}
}; };
struct NoFutureInSplitter : FutureException {
NoFutureInSplitter() : FutureException("No Future in this FutureSplitter") {}
};
} }
/*
* 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.
*/
#pragma once
#include <folly/futures/Future.h>
#include <folly/futures/SharedPromise.h>
namespace folly {
/*
* FutureSplitter provides a `getFuture()' method which can be called multiple
* times, returning a new Future each time. These futures are called-back when
* the original Future passed to the FutureSplitter constructor completes. Calls
* to `getFuture()' after that time return a completed Future.
*
* Note that while the Futures from `getFuture()' depend on the completion of
* the original Future they do not inherit any other properties such as
* Executors passed to `via' etc.
*/
template <class T>
class FutureSplitter {
public:
/**
* Default constructor for convenience only. It is an error to call
* `getFuture()` on a default-constructed FutureSplitter which has not had
* a correctly-constructed FutureSplitter copy- or move-assigned into it.
*/
FutureSplitter() = default;
/**
* Provide a way to split a Future<T>.
*/
explicit FutureSplitter(Future<T>&& future)
: promise_(std::make_shared<SharedPromise<T>>()) {
future.then([promise = promise_](Try<T> && theTry) {
promise->setTry(std::move(theTry));
});
}
/**
* This can be called an unlimited number of times per FutureSplitter.
*/
Future<T> getFuture() {
if (UNLIKELY(promise_ == nullptr)) {
throw NoFutureInSplitter();
}
return promise_->getFuture();
}
private:
std::shared_ptr<SharedPromise<T>> promise_;
};
/**
* Convenience function, allowing us to exploit template argument deduction to
* improve readability.
*/
template <class T>
FutureSplitter<T> splitFuture(Future<T>&& future) {
return FutureSplitter<T>{std::move(future)};
}
}
...@@ -47,11 +47,6 @@ SharedPromise<T>& SharedPromise<T>::operator=( ...@@ -47,11 +47,6 @@ SharedPromise<T>& SharedPromise<T>::operator=(
return *this; return *this;
} }
template <class T>
SharedPromise<T>::SharedPromise(Future<T> future) {
future.then(&SharedPromise<T>::setTry, this);
}
template <class T> template <class T>
size_t SharedPromise<T>::size() { size_t SharedPromise<T>::size() {
std::lock_guard<std::mutex> g(mutex_); std::lock_guard<std::mutex> g(mutex_);
......
...@@ -46,13 +46,6 @@ public: ...@@ -46,13 +46,6 @@ public:
SharedPromise(SharedPromise<T>&&) noexcept; SharedPromise(SharedPromise<T>&&) noexcept;
SharedPromise& operator=(SharedPromise<T>&&) noexcept; SharedPromise& operator=(SharedPromise<T>&&) noexcept;
/**
* Provide a way to split a Future<T>. Note that while the Futures from
* `getFuture()' depend on the completion of the parameter Future they do not
* inherit any other properties such as Executor's passed to `via' etc.
*/
explicit SharedPromise(Future<T>);
/** /**
* Return a Future tied to the shared core state. Unlike Promise::getFuture, * Return a Future tied to the shared core state. Unlike Promise::getFuture,
* this can be called an unlimited number of times per SharedPromise. * this can be called an unlimited number of times per SharedPromise.
......
/*
* 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/futures/FutureSplitter.h>
#include <folly/portability/GTest.h>
using namespace folly;
TEST(FutureSplitter, splitFutureSuccess) {
Promise<int> p;
FutureSplitter<int> sp(p.getFuture());
auto f1 = sp.getFuture();
EXPECT_FALSE(f1.isReady());
p.setValue(1);
EXPECT_TRUE(f1.isReady());
EXPECT_TRUE(f1.hasValue());
auto f2 = sp.getFuture();
EXPECT_TRUE(f2.isReady());
EXPECT_TRUE(f2.hasValue());
}
TEST(FutureSplitter, splitFutureCopyable) {
Promise<int> p;
FutureSplitter<int> sp1(p.getFuture());
FutureSplitter<int> sp2(sp1);
auto f1 = sp1.getFuture();
EXPECT_FALSE(f1.isReady());
p.setValue(1);
EXPECT_TRUE(f1.isReady());
EXPECT_TRUE(f1.hasValue());
auto f2 = sp2.getFuture();
EXPECT_TRUE(f2.isReady());
EXPECT_TRUE(f2.hasValue());
FutureSplitter<int> sp3(sp1);
auto f3 = sp3.getFuture();
EXPECT_TRUE(f3.isReady());
EXPECT_TRUE(f3.hasValue());
}
TEST(FutureSplitter, splitFutureMovable) {
Promise<int> p;
FutureSplitter<int> sp1(p.getFuture());
auto f1 = sp1.getFuture();
FutureSplitter<int> sp2(std::move(sp1));
EXPECT_FALSE(f1.isReady());
p.setValue(1);
EXPECT_TRUE(f1.isReady());
EXPECT_TRUE(f1.hasValue());
auto f2 = sp2.getFuture();
EXPECT_TRUE(f2.isReady());
EXPECT_TRUE(f2.hasValue());
FutureSplitter<int> sp3(std::move(sp2));
auto f3 = sp3.getFuture();
EXPECT_TRUE(f3.isReady());
EXPECT_TRUE(f3.hasValue());
}
TEST(FutureSplitter, splitFutureCopyAssignable) {
Promise<int> p;
FutureSplitter<int> sp1(p.getFuture());
FutureSplitter<int> sp2{};
sp2 = sp1;
auto f1 = sp1.getFuture();
EXPECT_FALSE(f1.isReady());
p.setValue(1);
EXPECT_TRUE(f1.isReady());
EXPECT_TRUE(f1.hasValue());
auto f2 = sp2.getFuture();
EXPECT_TRUE(f2.isReady());
EXPECT_TRUE(f2.hasValue());
FutureSplitter<int> sp3(sp1);
auto f3 = sp3.getFuture();
EXPECT_TRUE(f3.isReady());
EXPECT_TRUE(f3.hasValue());
}
TEST(FutureSplitter, splitFutureMoveAssignable) {
Promise<int> p;
FutureSplitter<int> sp1(p.getFuture());
auto f1 = sp1.getFuture();
FutureSplitter<int> sp2{};
sp2 = std::move(sp1);
EXPECT_FALSE(f1.isReady());
p.setValue(1);
EXPECT_TRUE(f1.isReady());
EXPECT_TRUE(f1.hasValue());
auto f2 = sp2.getFuture();
EXPECT_TRUE(f2.isReady());
EXPECT_TRUE(f2.hasValue());
FutureSplitter<int> sp3(std::move(sp2));
auto f3 = sp3.getFuture();
EXPECT_TRUE(f3.isReady());
EXPECT_TRUE(f3.hasValue());
}
TEST(FutureSplitter, splitFutureScope) {
Promise<int> p;
auto pSP = make_unique<FutureSplitter<int>>(p.getFuture());
auto f1 = pSP->getFuture();
EXPECT_FALSE(f1.isReady());
pSP.reset();
EXPECT_NO_THROW(EXPECT_FALSE(f1.isReady()));
p.setValue(1);
EXPECT_TRUE(f1.isReady());
EXPECT_TRUE(f1.hasValue());
EXPECT_EQ(1, f1.get());
}
TEST(FutureSplitter, splitFutureFailure) {
Promise<int> p;
FutureSplitter<int> sp(p.getFuture());
auto f1 = sp.getFuture();
EXPECT_FALSE(f1.isReady());
try {
throw std::runtime_error("Oops");
} catch (...) {
p.setException(exception_wrapper(std::current_exception()));
}
EXPECT_TRUE(f1.isReady());
EXPECT_TRUE(f1.hasException());
auto f2 = sp.getFuture();
EXPECT_TRUE(f2.isReady());
EXPECT_TRUE(f2.hasException());
}
...@@ -125,33 +125,3 @@ TEST(SharedPromise, interruptHandler) { ...@@ -125,33 +125,3 @@ TEST(SharedPromise, interruptHandler) {
f.cancel(); f.cancel();
EXPECT_TRUE(flag); EXPECT_TRUE(flag);
} }
TEST(SharedPromise, splitFutureSuccess) {
Promise<int> p;
SharedPromise<int> sp(p.getFuture());
auto f1 = sp.getFuture();
EXPECT_FALSE(f1.isReady());
p.setValue(1);
EXPECT_TRUE(f1.isReady());
EXPECT_TRUE(f1.hasValue());
auto f2 = sp.getFuture();
EXPECT_TRUE(f2.isReady());
EXPECT_TRUE(f2.hasValue());
}
TEST(SharedPromise, splitFutureFailure) {
Promise<int> p;
SharedPromise<int> sp(p.getFuture());
auto f1 = sp.getFuture();
EXPECT_FALSE(f1.isReady());
try {
throw std::runtime_error("Oops");
} catch (const std::exception& e) {
p.setException(exception_wrapper(std::current_exception(), e));
}
EXPECT_TRUE(f1.isReady());
EXPECT_TRUE(f1.hasException());
auto f2 = sp.getFuture();
EXPECT_TRUE(f2.isReady());
EXPECT_TRUE(f2.hasException());
}
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