Commit a6eb914b authored by Hans Fugal's avatar Hans Fugal Committed by Dave Watson

Remove Later

Summary: dalek-exterminate

Test Plan:
Moved `Later` tests to `via` tests.
fbgs
contbuild

Reviewed By: jsedgwick@fb.com

Subscribers: fbcode-common-diffs@, adityab, lins, trunkagent, fugalh, exa, njormrod, folly-diffs@, hannesr

FB internal diff: D1714862

Tasks: 5409538

Signature: t1:1714862:1417621949:f63f49e1093a021170d2346e8e673db042d2bc56
parent 11329181
......@@ -237,8 +237,6 @@ nobase_follyinclude_HEADERS = \
wangle/Future-inl.h \
wangle/Future.h \
wangle/InlineExecutor.h \
wangle/Later-inl.h \
wangle/Later.h \
wangle/ManualExecutor.h \
wangle/OpaqueCallbackShunt.h \
wangle/Promise-inl.h \
......
......@@ -91,8 +91,6 @@ class Future {
///
/// f = f.via(e).then(a);
/// f.then(b);
///
/// If you need something like that, use a Later.
template <typename Executor>
Future<T> via(Executor* executor);
......
/*
* Copyright 2014 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/wangle/Executor.h>
#include <folly/wangle/Future.h>
#include <folly/Optional.h>
namespace folly { namespace wangle {
template <typename T>
struct isLater {
static const bool value = false;
};
template <typename T>
struct isLater<Later<T> > {
static const bool value = true;
};
template <typename T>
struct isLaterOrFuture {
static const bool value = false;
};
template <typename T>
struct isLaterOrFuture<Later<T>> {
static const bool value = true;
};
template <typename T>
struct isLaterOrFuture<Future<T>> {
static const bool value = true;
};
template <typename T>
template <class U, class Unused, class Unused2>
Later<T>::Later() {
future_ = starter_.getFuture();
}
template <class T>
Later<T>::Later(Future<T>&& f) {
MoveWrapper<Future<T>> fw(std::move(f));
*this = Later<void>()
.then([fw](Try<void>&&) mutable {
return std::move(*fw);
});
}
template <typename T>
Later<T>::Later(Promise<void>&& starter)
: starter_(std::forward<Promise<void>>(starter)) { }
template <class T>
template <class U, class Unused, class Unused2>
Later<T>::Later(U&& input) {
folly::MoveWrapper<Promise<U>> promise;
folly::MoveWrapper<U> inputm(std::forward<U>(input));
future_ = promise->getFuture();
starter_.getFuture().then([=](Try<void>&& t) mutable {
promise->setValue(std::move(*inputm));
});
}
template <typename T>
Later<T>::Later(std::exception_ptr const& eptr) {
folly::MoveWrapper<Promise<T>> promise;
future_ = promise->getFuture();
starter_.getFuture().then([=](Try<void>&& t) mutable {
promise->setException(eptr);
});
}
template <typename T>
template <typename E, class Unused>
Later<T>::Later(E const& e) :
Later<T>::Later(std::make_exception_ptr<E>(e)) {
}
template <class T>
template <class U, class Unused, class Unused2>
Later<T>::Later(std::function<void(std::function<void(U&&)>&&)>&& fn) {
folly::MoveWrapper<Promise<U>> promise;
future_ = promise->getFuture();
starter_.getFuture().then([=](Try<void>&& t) mutable {
fn([=](U&& output) mutable {
promise->setValue(std::move(output));
});
});
}
template <class T>
template <class F>
typename std::enable_if<
!isLaterOrFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
Later<typename std::result_of<F(Try<T>&&)>::type> >::type
Later<T>::then(F&& fn) {
typedef typename std::result_of<F(Try<T>&&)>::type B;
Later<B> later(std::move(starter_));
later.future_ = future_->then(std::forward<F>(fn));
return later;
}
template <class T>
template <class F>
typename std::enable_if<
isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
Later<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
Later<T>::then(F&& fn) {
typedef typename std::result_of<F(Try<T>&&)>::type::value_type B;
Later<B> later(std::move(starter_));
later.future_ = future_->then(std::move(fn));
return later;
}
template <class T>
template <class F>
typename std::enable_if<
isLater<typename std::result_of<F(Try<T>&&)>::type>::value,
Later<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
Later<T>::then(F&& fn) {
typedef typename std::result_of<F(Try<T>&&)>::type::value_type B;
folly::MoveWrapper<Promise<B>> promise;
folly::MoveWrapper<F> fnm(std::move(fn));
Later<B> later(std::move(starter_));
later.future_ = promise->getFuture();
future_->then([=](Try<T>&& t) mutable {
(*fnm)(std::move(t))
.then([=](Try<B>&& t2) mutable {
promise->fulfilTry(std::move(t2));
})
.launch();
});
return later;
}
template <class T>
Later<T> Later<T>::via(Executor* executor) {
folly::MoveWrapper<Promise<T>> promise;
Later<T> later(std::move(starter_));
later.future_ = promise->getFuture();
future_->setCallback_([executor, promise](Try<T>&& t) mutable {
folly::MoveWrapper<Try<T>> tt(std::move(t));
executor->add([promise, tt]() mutable {
promise->fulfilTry(std::move(*tt));
});
});
return later;
}
template <class T>
Future<T> Later<T>::launch() {
starter_.setValue();
return std::move(*future_);
}
template <class T>
Later<std::vector<Try<T>>> whenAllLater(std::vector<Later<T>>&& laters) {
if (laters.size() == 0) {
return Later<std::vector<Try<T>>>(std::vector<Try<T>>());
}
auto ctx = new detail::WhenAllLaterContext<T>();
ctx->total = laters.size();
ctx->results.resize(ctx->total);
MoveWrapper<std::vector<Later<T>>> mlaters{std::move(laters)};
std::function<void(std::function<void(std::vector<Try<T>>&&)>&&)> wrapper =
[ctx, mlaters](std::function<void(std::vector<Try<T>>&&)>&& fn) mutable {
ctx->fn = std::move(fn);
size_t i = 0;
for (auto& l : *mlaters) {
l.then([ctx, i](Try<T>&& t) {
ctx->results[i] = std::move(t);
if (++ctx->count == ctx->total) {
ctx->fn(std::move(ctx->results));
delete ctx;
}
}).launch();
++i;
}
};
return Later<std::vector<Try<T>>>(std::move(wrapper));
}
}}
/*
* Copyright 2014 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/wangle/Deprecated.h>
#include <folly/wangle/Executor.h>
#include <folly/wangle/Future.h>
#include <folly/Optional.h>
namespace folly { namespace wangle {
template <typename T> struct isLaterOrFuture;
template <typename T> struct isLater;
/*
* Later is like a cold Future, but makes it easier to avoid triggering until
* later, because it must be triggered explicitly. An equivalence example will
* help differentiate:
*
* Later<Foo> later =
* Later<Foo>(std::move(foo))
* .then(cb1)
* .via(ex1)
* .then(cb2)
* .then(cb3)
* .via(ex2)
* .then(cb4)
* .then(cb5);
* ...
* later.launch();
*
* Future<Foo> coldFuture = makeFuture(std::move(foo));
* coldFuture.deactivate();
* coldFuture
* .then(cb1)
* .via(ex1)
* .then(cb2)
* .then(cb3)
* .via(ex2)
* .then(cb4)
* .then(cb5);
* ...
* coldFuture.activate();
*
* Using a Later means you don't have to grab a handle to the first Future and
* deactivate it.
*
* Later used to be a workaround to the thread-unsafe nature of Future
* chaining, but that has changed and there is no need to use Later if your
* only goal is to traverse thread boundaries with executors. In that case,
* just use Future::via().
*
* Here is an example of a workflow:
*
* Later<ClientRequest> later(std::move(request));
*
* auto future = later.
* .via(cpuExecutor)
* .then([=](Try<ClientRequest>&& t) { return doCpuWork(t.value()); })
* .via(diskExecutor)
* .then([=](Try<CpuResponse>&& t) { return doDiskWork(t.value()); })
* .via(serverExecutor)
* .then([=]Try<DiskResponse>&& t) { return sendClientResponse(t.value()); })
* .launch();
*/
// DEPRECATED. Just use Future::via() to accomplish the same thing. If it's
// not obvious how, feel free to reach out.
template <class T>
class DEPRECATED Later {
public:
typedef T value_type;
/*
* This default constructor is used to build an asynchronous workflow that
* takes no input.
*/
template <class U = void,
class = typename std::enable_if<std::is_void<U>::value>::type,
class = typename std::enable_if<std::is_same<T, U>::value>::type>
Later();
/*
* Lift a Future into a Later
*/
/* implicit */ Later(Future<T>&& f);
/*
* This constructor is used to build an asynchronous workflow that takes a
* value as input, and that value is passed in.
*/
template <class U,
class = typename std::enable_if<!std::is_void<U>::value>::type,
class = typename std::enable_if<std::is_same<T, U>::value>::type>
explicit Later(U&& input);
/*
* This constructor is used to build an asynchronous workflow that takes an
* exception_ptr as input, and throws it on completion.
*/
explicit Later(std::exception_ptr const&);
/*
* This constructor is used to build an asynchronous workflow that takes an
* exception as input, and throws it on completion.
*/
template <class E,
class = typename std::enable_if<
std::is_base_of<std::exception, E>::value>::type>
explicit Later(E const& e);
/*
* This constructor is used to wrap a pre-existing cob-style asynchronous api
* so that it can be used in wangle. wangle provides the callback to this
* pre-existing api, and this callback will fulfill a promise so as to
* incorporate this api into the workflow.
*
* Example usage:
*
* // This adds two ints asynchronously. cob is called in another thread.
* void addAsync(int a, int b, std::function<void(int&&)>&& cob);
*
* Later<int> asyncWrapper([=](std::function<void(int&&)>&& fn) {
* addAsync(1, 2, std::move(fn));
* });
*/
// TODO we should implement a makeFuture-ish with this pattern too, now.
template <class U,
class = typename std::enable_if<!std::is_void<U>::value>::type,
class = typename std::enable_if<std::is_same<T, U>::value>::type>
explicit Later(std::function<void(std::function<void(U&&)>&&)>&& fn);
/*
* then() adds additional work to the end of the workflow. If the lambda
* provided to then() returns a future, that future must be fulfilled in the
* same thread of the last set executor (either at constructor or from a call
* to via()).
*/
template <class F>
typename std::enable_if<
!isLaterOrFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
Later<typename std::result_of<F(Try<T>&&)>::type> >::type
then(F&& fn);
template <class F>
typename std::enable_if<
isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
Later<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
then(F&& fn);
/*
* If the function passed to then() returns a Later<T>, calls to then() will
* be chained to the new Later before launching the new Later.
*
* This can be used to build asynchronous modules that can be called from a
* user thread and completed in a callback thread.
*
* Using the Later(std::function<void(std::function<void(T&&)>)>&& fn)
* constructor, you can wrap existing asynchronous modules with a Later and
* can chain it to wangle asynchronous workflows via this call.
*/
template <class F>
typename std::enable_if<
isLater<typename std::result_of<F(Try<T>&&)>::type>::value,
Later<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
then(F&& fn);
/// Variant where func is an ordinary function (static method, method)
/// Must return a Later
template <class R>
typename std::enable_if<isLater<R>::value, R>::type
inline then(R(*func)(Try<T>&&)) {
return then([func](Try<T>&& t) {
return (*func)(std::move(t));
});
}
/// Variant where func is an member function
/// Must return a Later
template <class R, class Caller>
typename std::enable_if<isLater<R>::value, R>::type
inline then(Caller *instance, R(Caller::*func)(Try<T>&&)) {
return then([instance, func](Try<T>&& t) {
return (instance->*func)(std::move(t));
});
}
/*
* Resets the executor - all then() calls made after the call to via() will be
* made in the new executor. The Executor must outlive.
*/
Later<T> via(Executor* executor);
/*
* Starts the workflow. The function provided in the constructor will be
* called in the executor provided in the constructor. Subsequent then()
* calls will be made, potentially changing threads if a via() call is made.
* The future returned will be fulfilled in the last executor.
*/
Future<T> launch();
private:
Promise<void> starter_;
folly::Optional<Future<T>> future_;
struct hide { };
explicit Later(Promise<void>&& starter);
template <class U>
friend class Later;
};
// See Future.whenAll
template <class T>
Later<std::vector<Try<T>>> whenAllLater(std::vector<Later<T>>&& laters);
}}
#include <folly/wangle/Later-inl.h>
......@@ -21,7 +21,7 @@
namespace folly { namespace wangle {
/// These classes help you wrap an existing C style callback function
/// into a Future/Later.
/// into a Future.
///
/// void legacy_send_async(..., void (*cb)(void*), void*);
///
......@@ -54,37 +54,4 @@ private:
T obj_;
};
/// Variant that returns a Later instead of a Future
///
/// Later<int> wrappedSendAsyncLater(int i) {
/// folly::MoveWrapper<int> wrapped(std::move(i));
/// return Later<int>(
/// [..., wrapped](std::function<void(int&&)>&& fn) mutable {
/// auto handle = new OpaqueCallbackLaterShunt<int>(
/// std::move(*wrapped), std::move(fn));
/// legacy_send_async(...,
/// OpaqueCallbackLaterShunt<int>::callback, handle);
/// });
/// }
///
/// Depending on your compiler's kung-fu knowledge, you might need to assign
/// the lambda to a std::function<void(std::function<void(int&&)>&&)> temporary
/// variable before std::moving into it into the later.
template <typename T>
class OpaqueCallbackLaterShunt {
public:
explicit
OpaqueCallbackLaterShunt(T&& obj, std::function<void(T&&)>&& fn)
: fn_(std::move(fn)), obj_(std::move(obj)) { }
static void callback(void* arg) {
std::unique_ptr<OpaqueCallbackLaterShunt<T>> handle(
static_cast<OpaqueCallbackLaterShunt<T>*>(arg));
handle->fn_(std::move(handle->obj_));
}
private:
std::function<void(T&&)> fn_;
T obj_;
};
}} // folly::wangle
......@@ -207,13 +207,6 @@ You can still have a race after `via` if you break it into multiple statements,
f = f.via(e1).then(y1).then(y2); // nothing racy here
f2.then(y3); // racy
```
If you want more control over the delayed execution, check out `Later`.
```C++
Later<void> later;
later = later.via(e1).then(y1).then(y2); // nothing racy here
later = later.then(y3); // nor here
later.launch(); // explicit launch
```
## You make me Promises, Promises
......
......@@ -289,13 +289,4 @@ struct WhenAnyContext {
}
};
template <typename T>
struct WhenAllLaterContext {
explicit WhenAllLaterContext() : count(0), total(0) {}
std::function<void(std::vector<Try<T>>&&)> fn;
std::vector<Try<T> > results;
std::atomic<size_t> count;
size_t total;
};
}}} // namespace
......@@ -17,9 +17,9 @@
#include <gtest/gtest.h>
#include <thread>
#include <folly/wangle/ManualExecutor.h>
#include <folly/wangle/Future.h>
#include <folly/wangle/InlineExecutor.h>
#include <folly/wangle/Later.h>
#include <folly/wangle/ManualExecutor.h>
using namespace folly::wangle;
......@@ -34,8 +34,9 @@ struct ManualWaiter {
std::shared_ptr<ManualExecutor> ex;
};
struct LaterFixture : public testing::Test {
LaterFixture() :
struct ViaFixture : public testing::Test {
ViaFixture() :
future_(makeFuture().deactivate()),
westExecutor(new ManualExecutor),
eastExecutor(new ManualExecutor),
waiter(new ManualWaiter(westExecutor)),
......@@ -48,7 +49,7 @@ struct LaterFixture : public testing::Test {
});
}
~LaterFixture() {
~ViaFixture() {
done = true;
eastExecutor->add([=]() { });
t.join();
......@@ -60,7 +61,7 @@ struct LaterFixture : public testing::Test {
});
}
Later<void> later;
Future<void> future_;
std::shared_ptr<ManualExecutor> westExecutor;
std::shared_ptr<ManualExecutor> eastExecutor;
std::shared_ptr<ManualWaiter> waiter;
......@@ -69,110 +70,77 @@ struct LaterFixture : public testing::Test {
std::thread t;
};
TEST(Later, construct_and_launch) {
bool fulfilled = false;
auto later = Later<void>().then([&](Try<void>&& t) {
fulfilled = true;
return makeFuture<int>(1);
});
// has not started yet.
EXPECT_FALSE(fulfilled);
EXPECT_EQ(later.launch().value(), 1);
EXPECT_TRUE(fulfilled);
}
TEST(Later, exception_on_launch) {
auto later = Later<void>(std::runtime_error("E"));
EXPECT_THROW(later.launch().value(), std::runtime_error);
TEST(Via, exception_on_launch) {
auto future = makeFuture<int>(std::runtime_error("E"));
EXPECT_THROW(future.value(), std::runtime_error);
}
TEST(Later, then_value) {
auto future = Later<int>(std::move(1))
TEST(Via, then_value) {
auto future = makeFuture(std::move(1))
.then([](Try<int>&& t) {
return t.value() == 1;
})
.launch();
;
EXPECT_TRUE(future.value());
}
TEST(Later, then_future) {
auto future = Later<int>(1)
TEST(Via, then_future) {
auto future = makeFuture(1)
.then([](Try<int>&& t) {
return makeFuture(t.value() == 1);
})
.launch();
;
EXPECT_TRUE(future.value());
}
static Later<std::string> doWorkStatic(Try<std::string>&& t) {
return Later<std::string>(t.value() + ";static");
static Future<std::string> doWorkStatic(Try<std::string>&& t) {
return makeFuture(t.value() + ";static");
}
TEST(Later, then_function) {
TEST(Via, then_function) {
struct Worker {
Later<std::string> doWork(Try<std::string>&& t) {
return Later<std::string>(t.value() + ";class");
Future<std::string> doWork(Try<std::string>&& t) {
return makeFuture(t.value() + ";class");
}
static Later<std::string> doWorkStatic(Try<std::string>&& t) {
return Later<std::string>(t.value() + ";class-static");
static Future<std::string> doWorkStatic(Try<std::string>&& t) {
return makeFuture(t.value() + ";class-static");
}
} w;
auto f = Later<std::string>(std::string("start"))
auto f = makeFuture(std::string("start"))
.then(doWorkStatic)
.then(Worker::doWorkStatic)
.then(&w, &Worker::doWork)
.launch();
;
EXPECT_EQ(f.value(), "start;static;class-static;class");
}
TEST_F(LaterFixture, thread_hops) {
TEST_F(ViaFixture, thread_hops) {
auto westThreadId = std::this_thread::get_id();
auto future = later.via(eastExecutor.get()).then([=](Try<void>&& t) {
auto f = future_.via(eastExecutor.get()).then([=](Try<void>&& t) {
EXPECT_NE(std::this_thread::get_id(), westThreadId);
return makeFuture<int>(1);
}).via(westExecutor.get()
).then([=](Try<int>&& t) {
EXPECT_EQ(std::this_thread::get_id(), westThreadId);
return t.value();
}).launch();
while (!future.isReady()) {
waiter->makeProgress();
}
EXPECT_EQ(future.value(), 1);
}
TEST_F(LaterFixture, wrapping_preexisting_async_modules) {
auto westThreadId = std::this_thread::get_id();
std::function<void(std::function<void(int&&)>&&)> wrapper =
[=](std::function<void(int&&)>&& fn) {
addAsync(2, 2, std::move(fn));
};
auto future = Later<int>(std::move(wrapper))
.via(westExecutor.get())
.then([=](Try<int>&& t) {
EXPECT_EQ(std::this_thread::get_id(), westThreadId);
return t.value();
})
.launch();
while (!future.isReady()) {
});
while (!f.isReady()) {
waiter->makeProgress();
}
EXPECT_EQ(future.value(), 4);
EXPECT_EQ(f.value(), 1);
}
TEST_F(LaterFixture, chain_laters) {
TEST_F(ViaFixture, chain_vias) {
auto westThreadId = std::this_thread::get_id();
auto future = later.via(eastExecutor.get()).then([=](Try<void>&& t) {
auto f = future_.via(eastExecutor.get()).then([=](Try<void>&& t) {
EXPECT_NE(std::this_thread::get_id(), westThreadId);
return makeFuture<int>(1);
}).then([=](Try<int>&& t) {
int val = t.value();
return Later<int>(std::move(val)).via(westExecutor.get())
return makeFuture(std::move(val)).via(westExecutor.get())
.then([=](Try<int>&& t) mutable {
EXPECT_EQ(std::this_thread::get_id(), westThreadId);
return t.value();
......@@ -180,31 +148,11 @@ TEST_F(LaterFixture, chain_laters) {
}).then([=](Try<int>&& t) {
EXPECT_EQ(std::this_thread::get_id(), westThreadId);
return t.value();
}).launch();
});
while (!future.isReady()) {
while (!f.isReady()) {
waiter->makeProgress();
}
EXPECT_EQ(future.value(), 1);
EXPECT_EQ(f.value(), 1);
}
TEST(Later, when_all_later) {
size_t done = 0;
std::vector<Later<int>> laters;
laters.emplace_back(Later<int>(1).then([&](Try<int>&& i) mutable {
done += i.value(); return 8;
}));
laters.emplace_back(Later<int>(2).then([&](Try<int>&& i) mutable {
done += i.value(); return 16;
}));
laters.emplace_back(Later<int>(4).then([&](Try<int>&& i) mutable {
done += i.value(); return 32;
}));
whenAllLater(std::move(laters))
.then([&](Try<std::vector<Try<int>>>&& v) mutable {
for (const auto& i : v.value()) {
done += i.value();
}
}).launch();
EXPECT_EQ(done, 63);
}
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