Commit 8898db44 authored by Sven Over's avatar Sven Over Committed by Facebook Github Bot

execute callbacks as rvalue references

Summary:
Callable objects may implement a separate `operator()` for when
they are called as rvalue references. For example, `folly::partial`
will move captured objects to the function call when invoked as
rvalue reference. That allows e.g. to capture pass a `unique_ptr`
to `folly::partial` for a function that takes a `unique_ptr` by
value or rvalue-reference.

Callbacks for `folly::Future`s are only ever executed once. They
may consume captured data. If the callback is an object that
implements a `operator()() &&`, then that one should be invoked.

This diff makes sure, callbacks passed to `folly::Future` are
invoked as rvalue references.

Reviewed By: ericniebler, fugalh

Differential Revision: D4404186

fbshipit-source-id: 9f33e442a634acb8797183d3d869840d85bd5d15
parent ef2e3d4b
......@@ -159,12 +159,14 @@ Future<T>::thenImplementation(F&& func, detail::argResult<isTry, F, Args...>) {
in some circumstances, but I think it should be explicit not implicit
in the destruction of the Future used to create it.
*/
setCallback_([ funcm = std::forward<F>(func), pm = std::move(p) ](
Try<T> && t) mutable {
setCallback_(
[ func = std::forward<F>(func), pm = std::move(p) ](Try<T> && t) mutable {
if (!isTry && t.hasException()) {
pm.setException(std::move(t.exception()));
} else {
pm.setWith([&]() { return funcm(t.template get<isTry, Args>()...); });
pm.setWith([&]() {
return std::move(func)(t.template get<isTry, Args>()...);
});
}
});
......@@ -189,14 +191,14 @@ Future<T>::thenImplementation(F&& func, detail::argResult<isTry, F, Args...>) {
auto f = p.getFuture();
f.core_->setExecutorNoLock(getExecutor());
setCallback_([ funcm = std::forward<F>(func), pm = std::move(p) ](
setCallback_([ func = std::forward<F>(func), pm = std::move(p) ](
Try<T> && t) mutable {
auto ew = [&] {
if (!isTry && t.hasException()) {
return std::move(t.exception());
} else {
try {
auto f2 = funcm(t.template get<isTry, Args>()...);
auto f2 = std::move(func)(t.template get<isTry, Args>()...);
// that didn't throw, now we can steal p
f2.setCallback_([p = std::move(pm)](Try<B> && b) mutable {
p.setTry(std::move(b));
......@@ -263,10 +265,11 @@ Future<T>::onError(F&& func) {
p.core_->setInterruptHandlerNoLock(core_->getInterruptHandler());
auto f = p.getFuture();
setCallback_([ funcm = std::forward<F>(func), pm = std::move(p) ](
Try<T> && t) mutable {
if (!t.template withException<Exn>(
[&](Exn& e) { pm.setWith([&] { return funcm(e); }); })) {
setCallback_(
[ func = std::forward<F>(func), pm = std::move(p) ](Try<T> && t) mutable {
if (!t.template withException<Exn>([&](Exn& e) {
pm.setWith([&] { return std::move(func)(e); });
})) {
pm.setTry(std::move(t));
}
});
......@@ -290,12 +293,12 @@ Future<T>::onError(F&& func) {
Promise<T> p;
auto f = p.getFuture();
setCallback_([ pm = std::move(p), funcm = std::forward<F>(func) ](
setCallback_([ pm = std::move(p), func = std::forward<F>(func) ](
Try<T> && t) mutable {
if (!t.template withException<Exn>([&](Exn& e) {
auto ew = [&] {
try {
auto f2 = funcm(e);
auto f2 = std::move(func)(e);
f2.setCallback_([pm = std::move(pm)](Try<T> && t2) mutable {
pm.setTry(std::move(t2));
});
......@@ -346,11 +349,11 @@ Future<T>::onError(F&& func) {
Promise<T> p;
auto f = p.getFuture();
setCallback_(
[ pm = std::move(p), funcm = std::forward<F>(func) ](Try<T> t) mutable {
[ pm = std::move(p), func = std::forward<F>(func) ](Try<T> t) mutable {
if (t.hasException()) {
auto ew = [&] {
try {
auto f2 = funcm(std::move(t.exception()));
auto f2 = std::move(func)(std::move(t.exception()));
f2.setCallback_([pm = std::move(pm)](Try<T> t2) mutable {
pm.setTry(std::move(t2));
});
......@@ -387,9 +390,9 @@ Future<T>::onError(F&& func) {
Promise<T> p;
auto f = p.getFuture();
setCallback_(
[ pm = std::move(p), funcm = std::forward<F>(func) ](Try<T> t) mutable {
[ pm = std::move(p), func = std::forward<F>(func) ](Try<T> t) mutable {
if (t.hasException()) {
pm.setWith([&] { return funcm(std::move(t.exception())); });
pm.setWith([&] { return std::move(func)(std::move(t.exception())); });
} else {
pm.setTry(std::move(t));
}
......
......@@ -846,3 +846,49 @@ TEST(Future, RequestContext) {
TEST(Future, makeFutureNoThrow) {
makeFuture().value();
}
TEST(Future, invokeCallbackReturningValueAsRvalue) {
struct Foo {
int operator()(int x) & {
return x + 1;
}
int operator()(int x) const& {
return x + 2;
}
int operator()(int x) && {
return x + 3;
}
};
Foo foo;
Foo const cfoo;
// The callback will be copied when given as lvalue or const ref, and moved
// if provided as rvalue. Either way, it should be executed as rvalue.
EXPECT_EQ(103, makeFuture<int>(100).then(foo).value());
EXPECT_EQ(203, makeFuture<int>(200).then(cfoo).value());
EXPECT_EQ(303, makeFuture<int>(300).then(Foo()).value());
}
TEST(Future, invokeCallbackReturningFutureAsRvalue) {
struct Foo {
Future<int> operator()(int x) & {
return x + 1;
}
Future<int> operator()(int x) const& {
return x + 2;
}
Future<int> operator()(int x) && {
return x + 3;
}
};
Foo foo;
Foo const cfoo;
// The callback will be copied when given as lvalue or const ref, and moved
// if provided as rvalue. Either way, it should be executed as rvalue.
EXPECT_EQ(103, makeFuture<int>(100).then(foo).value());
EXPECT_EQ(203, makeFuture<int>(200).then(cfoo).value());
EXPECT_EQ(303, makeFuture<int>(300).then(Foo()).value());
}
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