Commit 3f2140ea authored by Sven Over's avatar Sven Over Committed by Facebook Github Bot 5

folly/futures: use folly::Function to store callback

Summary:This diff makes it possible to pass callables (such as lambdas) to
folly::Future::then that are not copy-constructible. As a consequence, move-only
types (such as folly::Promise or std::unique_ptr) can now be captured in lambdas
passed to folly::Future::then.

Using C++14 notation, the following is now possible:
  Future<Unit>().then([promise = std::move(promise)]() mutable {
    promise.setValue(123);
  });
See folly/futures/test/NonCopyableLambdaTest.cpp for more examples.

folly::Future uses std::function to store callback functions. (More precisely,
the callback function is stored in a std::function in folly::detail::Core.)
std::function is a copy-constructible type and it requires that the callable
that it stores is copy constructible as well.

This diff changes the implementation of folly::detail::Core to use
folly::Function instead of std::function. It also simplifies the code in
folly::detail::Core a little bit: Core had a reserved space of size
8*sizeof(void*) to store callbacks in-place. Only larger callbacks (capturing
more data) would be stored in the std::function, which puts those on the heap.
folly::Function has a template parameter to set the size of the in-place
callable storage. In Core, it is set to 8*sizeof(void*), so all callbacks that
used to be stored in-place inside Core, are now stored in-place inside
folly::Function. This even reduces the size of a Core object: the
folly::Function object occupies 80 bytes, which is 16 bytes less than what was
used before for a std::function (32 bytes) and the callback storage (64
bytes). The resulting size of a Core<Unit> goes down from 192 to 176 bytes (on
x86_64).

Reviewed By: fugalh

Differential Revision: D2884868

fb-gh-sync-id: 35548890c392f80e6c680676b0f98e711bc03ca3
fbshipit-source-id: 35548890c392f80e6c680676b0f98e711bc03ca3
parent 6f068498
...@@ -21,13 +21,13 @@ ...@@ -21,13 +21,13 @@
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
#include <folly/Optional.h> #include <folly/Executor.h>
#include <folly/Function.h>
#include <folly/MicroSpinLock.h> #include <folly/MicroSpinLock.h>
#include <folly/Optional.h>
#include <folly/futures/Try.h>
#include <folly/futures/Promise.h>
#include <folly/futures/Future.h> #include <folly/futures/Future.h>
#include <folly/Executor.h> #include <folly/futures/Promise.h>
#include <folly/futures/Try.h>
#include <folly/futures/detail/FSM.h> #include <folly/futures/detail/FSM.h>
#include <folly/io/async/Request.h> #include <folly/io/async/Request.h>
...@@ -147,34 +147,13 @@ class Core { ...@@ -147,34 +147,13 @@ class Core {
} }
} }
template <typename F>
class LambdaBufHelper {
public:
template <typename FF>
explicit LambdaBufHelper(FF&& func) : func_(std::forward<FF>(func)) {}
void operator()(Try<T>&& t) {
SCOPE_EXIT { this->~LambdaBufHelper(); };
func_(std::move(t));
}
private:
F func_;
};
/// Call only from Future thread. /// Call only from Future thread.
template <typename F> template <typename F>
void setCallback(F func) { void setCallback(F func) {
bool transitionToArmed = false; bool transitionToArmed = false;
auto setCallback_ = [&]{ auto setCallback_ = [&]{
context_ = RequestContext::saveContext(); context_ = RequestContext::saveContext();
callback_ = std::move(func);
// Move the lambda into the Core if it fits
if (sizeof(LambdaBufHelper<F>) <= lambdaBufSize) {
auto funcLoc = reinterpret_cast<LambdaBufHelper<F>*>(&lambdaBuf_);
new (funcLoc) LambdaBufHelper<F>(std::forward<F>(func));
callback_ = std::ref(*funcLoc);
} else {
callback_ = std::move(func);
}
}; };
FSM_START(fsm_) FSM_START(fsm_)
...@@ -395,13 +374,14 @@ class Core { ...@@ -395,13 +374,14 @@ class Core {
// sizeof(Core<T>) == size(Core<U>). // sizeof(Core<T>) == size(Core<U>).
// See Core::convert for details. // See Core::convert for details.
// lambdaBuf occupies exactly one cache line folly::Function<
static constexpr size_t lambdaBufSize = 8 * sizeof(void*); void(Try<T>&&),
typename std::aligned_storage<lambdaBufSize>::type lambdaBuf_; folly::FunctionMoveCtor::MAY_THROW,
8 * sizeof(void*)>
callback_;
// place result_ next to increase the likelihood that the value will be // place result_ next to increase the likelihood that the value will be
// contained entirely in one cache line // contained entirely in one cache line
folly::Optional<Try<T>> result_; folly::Optional<Try<T>> result_;
std::function<void(Try<T>&&)> callback_ {nullptr};
FSM<State> fsm_; FSM<State> fsm_;
std::atomic<unsigned char> attached_; std::atomic<unsigned char> attached_;
std::atomic<bool> active_ {true}; std::atomic<bool> active_ {true};
......
/*
* Copyright 2016 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 <gtest/gtest.h>
#include <folly/futures/Future.h>
using namespace folly;
TEST(NonCopyableLambda, basic) {
Promise<int> promise;
Future<int> future = promise.getFuture();
Future<Unit>().then(std::bind(
[](Promise<int>& promise) mutable { promise.setValue(123); },
std::move(promise)));
// The previous statement can be simplified in C++14:
// Future<Unit>().then([promise = std::move(promise)]() mutable {
// promise.setValue(123);
// });
EXPECT_TRUE(future.isReady());
EXPECT_EQ(future.get(), 123);
}
TEST(NonCopyableLambda, unique_ptr) {
Promise<Unit> promise;
auto int_ptr = folly::make_unique<int>(1);
EXPECT_EQ(*int_ptr, 1);
auto future = promise.getFuture().then(std::bind(
[](std::unique_ptr<int>& int_ptr) mutable {
++*int_ptr;
return std::move(int_ptr);
},
std::move(int_ptr)));
// The previous statement can be simplified in C++14:
// auto future =
// promise.getFuture().then([int_ptr = std::move(int_ptr)]() mutable {
// ++*int_ptr;
// return std::move(int_ptr);
// });
EXPECT_FALSE(future.isReady());
promise.setValue();
EXPECT_TRUE(future.isReady());
EXPECT_EQ(*future.get(), 2);
}
TEST(NonCopyableLambda, Function) {
Promise<int> promise;
Function<int(int)> callback = [](int x) { return x + 1; };
auto future = promise.getFuture().then(std::move(callback));
EXPECT_THROW(callback(0), std::bad_function_call);
EXPECT_FALSE(future.isReady());
promise.setValue(100);
EXPECT_TRUE(future.isReady());
EXPECT_EQ(future.get(), 101);
}
TEST(NonCopyableLambda, FunctionConst) {
Promise<int> promise;
Function<int(int) const> callback = [](int x) { return x + 1; };
auto future = promise.getFuture().then(std::move(callback));
EXPECT_THROW(callback(0), std::bad_function_call);
EXPECT_FALSE(future.isReady());
promise.setValue(100);
EXPECT_TRUE(future.isReady());
EXPECT_EQ(future.get(), 101);
}
...@@ -226,6 +226,7 @@ futures_test_SOURCES = \ ...@@ -226,6 +226,7 @@ futures_test_SOURCES = \
../futures/test/HeaderCompileTest.cpp \ ../futures/test/HeaderCompileTest.cpp \
../futures/test/InterruptTest.cpp \ ../futures/test/InterruptTest.cpp \
../futures/test/MapTest.cpp \ ../futures/test/MapTest.cpp \
../futures/test/NonCopyableLambdaTest.cpp \
../futures/test/PollTest.cpp \ ../futures/test/PollTest.cpp \
../futures/test/PromiseTest.cpp \ ../futures/test/PromiseTest.cpp \
../futures/test/ReduceTest.cpp \ ../futures/test/ReduceTest.cpp \
......
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