Summary:
Better support and test chaining of `via` and `activate`/`deactivate`.
The real problem is that without the ref qualifier a multiple chain of lvalue references can end up with a destructed object being referenced after.
https://akrzemi1.wordpress.com/2014/06/02/ref-qualifiers/
Test Plan:
This is mostly new tests that would fail and now pass.
I think maybe the tests are a bit weak, it would be good to find a way to ensure we aren't going to see the access-after-free bugs in these tests.
Reviewed By: jsedgwick@fb.com
Subscribers: trunkagent, fugalh, exa, njormrod, folly-diffs@
FB internal diff:
D1714873
Tasks:
5489801
Signature: t1:
1714873:
1417628538:
9e610c5ba5e0a22c19a11d53aa956be45d585058
template <class T>
template <typename Executor>
template <class T>
template <typename Executor>
-inline Future<T> Future<T>::via(Executor* executor) {
+inline Future<T> Future<T>::via(Executor* executor) && {
throwIfInvalid();
this->deactivate();
throwIfInvalid();
this->deactivate();
return std::move(*this);
}
return std::move(*this);
}
+template <class T>
+template <typename Executor>
+inline Future<T> Future<T>::via(Executor* executor) & {
+ throwIfInvalid();
+
+ MoveWrapper<Promise<T>> p;
+ auto f = p->getFuture();
+ then([p](Try<T>&& t) mutable { p->fulfilTry(std::move(t)); });
+ return std::move(f).via(executor);
+}
+
template <class T>
bool Future<T>::isReady() const {
throwIfInvalid();
template <class T>
bool Future<T>::isReady() const {
throwIfInvalid();
///
/// f = f.via(e).then(a);
/// f.then(b);
///
/// f = f.via(e).then(a);
/// f.then(b);
+ // The ref-qualifier allows for `this` to be moved out so we
+ // don't get access-after-free situations in chaining.
+ // https://akrzemi1.wordpress.com/2014/06/02/ref-qualifiers/
template <typename Executor>
template <typename Executor>
- Future<T> via(Executor* executor);
+ Future<T> via(Executor* executor) &&;
+
+ /// This variant creates a new future, where the ref-qualifier && version
+ /// moves `this` out. This one is less efficient but avoids confusing users
+ /// when "return f.via(x);" fails.
+ template <typename Executor>
+ Future<T> via(Executor* executor) &;
/** True when the result (or exception) is ready. */
bool isReady() const;
/** True when the result (or exception) is ready. */
bool isReady() const;
core_->deactivate();
return std::move(*this);
}
core_->deactivate();
return std::move(*this);
}
bool isActive() {
return core_->isActive();
}
bool isActive() {
return core_->isActive();
}
}
TEST(Future, activateOnDestruct) {
}
TEST(Future, activateOnDestruct) {
- Promise<void> p;
- auto f = p.getFuture();
- f.deactivate();
+ auto f = std::make_shared<Future<void>>(makeFuture());
+ f->deactivate();
- f.then([&](Try<void>&&) { count++; });
-
- p.setValue();
+ f->then([&](Try<void>&&) { count++; });
- f = makeFuture(); // force destruction of old f
struct ViaFixture : public testing::Test {
ViaFixture() :
struct ViaFixture : public testing::Test {
ViaFixture() :
- future_(makeFuture().deactivate()),
westExecutor(new ManualExecutor),
eastExecutor(new ManualExecutor),
waiter(new ManualWaiter(westExecutor)),
westExecutor(new ManualExecutor),
eastExecutor(new ManualExecutor),
waiter(new ManualWaiter(westExecutor)),
std::shared_ptr<ManualExecutor> westExecutor;
std::shared_ptr<ManualExecutor> eastExecutor;
std::shared_ptr<ManualWaiter> waiter;
std::shared_ptr<ManualExecutor> westExecutor;
std::shared_ptr<ManualExecutor> eastExecutor;
std::shared_ptr<ManualWaiter> waiter;
EXPECT_EQ(f.value(), "start;static;class-static;class");
}
EXPECT_EQ(f.value(), "start;static;class-static;class");
}
+TEST_F(ViaFixture, deactivateChain) {
+ bool flag = false;
+ auto f = makeFuture().deactivate();
+ EXPECT_FALSE(f.isActive());
+ auto f2 = f.then([&](Try<void>){ flag = true; });
+ EXPECT_FALSE(flag);
+}
+
+TEST_F(ViaFixture, deactivateActivateChain) {
+ bool flag = false;
+ // you can do this all day long with temporaries.
+ auto f1 = makeFuture().deactivate().activate().deactivate();
+ // Chaining on activate/deactivate requires an rvalue, so you have to move
+ // one of these two ways (if you're not using a temporary).
+ auto f2 = std::move(f1).activate();
+ f2.deactivate();
+ auto f3 = std::move(f2.activate());
+ f3.then([&](Try<void>){ flag = true; });
+ EXPECT_TRUE(flag);
+}
+
TEST_F(ViaFixture, thread_hops) {
auto westThreadId = std::this_thread::get_id();
TEST_F(ViaFixture, thread_hops) {
auto westThreadId = std::this_thread::get_id();
- auto f = future_.via(eastExecutor.get()).then([=](Try<void>&& t) {
+ auto f = via(eastExecutor.get()).then([=](Try<void>&& t) {
EXPECT_NE(std::this_thread::get_id(), westThreadId);
return makeFuture<int>(1);
}).via(westExecutor.get()
EXPECT_NE(std::this_thread::get_id(), westThreadId);
return makeFuture<int>(1);
}).via(westExecutor.get()
TEST_F(ViaFixture, chain_vias) {
auto westThreadId = std::this_thread::get_id();
TEST_F(ViaFixture, chain_vias) {
auto westThreadId = std::this_thread::get_id();
- auto f = future_.via(eastExecutor.get()).then([=](Try<void>&& t) {
+ auto f = via(eastExecutor.get()).then([=](Try<void>&& t) {
EXPECT_NE(std::this_thread::get_id(), westThreadId);
return makeFuture<int>(1);
}).then([=](Try<int>&& t) {
EXPECT_NE(std::this_thread::get_id(), westThreadId);
return makeFuture<int>(1);
}).then([=](Try<int>&& t) {
EXPECT_EQ(f.value(), 1);
}
EXPECT_EQ(f.value(), 1);
}
+TEST_F(ViaFixture, bareViaAssignment) {
+ auto f = via(eastExecutor.get());
+}
+TEST_F(ViaFixture, viaAssignment) {
+ // via()&&
+ auto f = makeFuture().via(eastExecutor.get());
+ // via()&
+ auto f2 = f.via(eastExecutor.get());
+}