template <class T>
Future<T>::~Future() {
if (obj_) {
- setContinuation([](Try<T>&&) {}); // detach
+ setCallback_([](Try<T>&&) {}); // detach
}
}
template <class T>
template <class F>
-void Future<T>::setContinuation(F&& func) {
+void Future<T>::setCallback_(F&& func) {
throwIfInvalid();
- obj_->setContinuation(std::move(func));
+ obj_->setCallback_(std::move(func));
obj_ = nullptr;
}
in some circumstances, but I think it should be explicit not implicit
in the destruction of the Future used to create it.
*/
- setContinuation(
+ setCallback_(
[p, funcm](Try<T>&& t) mutable {
p->fulfil([&]() {
return (*funcm)(std::move(t));
// grab the Future now before we lose our handle on the Promise
auto f = p->getFuture();
- setContinuation(
+ setCallback_(
[p, funcm](Try<T>&& t) mutable {
try {
auto f2 = (*funcm)(std::move(t));
// that didn't throw, now we can steal p
- f2.setContinuation([p](Try<B>&& b) mutable {
+ f2.setCallback_([p](Try<B>&& b) mutable {
p->fulfilTry(std::move(b));
});
} catch (...) {
folly::MoveWrapper<Promise<T>> p;
auto f = p->getFuture();
- setContinuation([executor, p](Try<T>&& t) mutable {
+ setCallback_([executor, p](Try<T>&& t) mutable {
folly::MoveWrapper<Try<T>> tt(std::move(t));
executor->add([p, tt]() mutable {
p->fulfilTry(std::move(*tt));
for (size_t i = 0; first != last; ++first, ++i) {
auto& f = *first;
- f.setContinuation([ctx, i](Try<T>&& t) {
+ f.setCallback_([ctx, i](Try<T>&& t) {
ctx->results[i] = std::move(t);
if (++ctx->count == ctx->total) {
ctx->p.setValue(std::move(ctx->results));
for (size_t i = 0; first != last; first++, i++) {
auto& f = *first;
- f.setContinuation([i, ctx](Try<T>&& t) {
+ f.setCallback_([i, ctx](Try<T>&& t) {
if (!ctx->done.exchange(true)) {
ctx->p.setValue(std::make_pair(i, std::move(t)));
}
/// not worth listing all those and their fancy template signatures as
/// friends. But it's not for public consumption.
template <class F>
- void setContinuation(F&& func);
+ void setCallback_(F&& func);
private:
typedef detail::FutureObject<T>* objPtr;
Later<T> later(std::move(starter_));
later.future_ = promise->getFuture();
- future_->setContinuation([executor, promise](Try<T>&& t) mutable {
+ 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));
/*
* Since wangle primitives (promise/future) are not thread safe, it is difficult
* to build complex asynchronous workflows. A Later allows you to build such a
- * workflow before actually launching it so that continuations can be set in a
+ * workflow before actually launching it so that callbacks can be set in a
* threadsafe manner.
*
* The interface to add additional work is the same as future: a then() method
Wangle is a futures-based async framework inspired by [Twitter's Finagle](http://twitter.github.io/finagle/) (which is in scala), and (loosely) building upon the existing (but anemic) Futures code found in the C++11 standard ([`std::future`](http://en.cppreference.com/w/cpp/thread/future)) and [`boost::future`](http://www.boost.org/doc/libs/1_53_0/boost/thread/future.hpp) (especially >= 1.53.0). Although inspired by the std::future interface, it is not syntactically drop-in compatible because some ideas didn't translate well enough and we decided to break from the API. But semantically, it should be straightforward to translate from existing std::future code to Wangle.
-The primary semantic differences are that Wangle Futures and Promises are not threadsafe; and as does `boost::future`, Wangle supports continuations (`then()`) and there are helper methods `whenAll()` and `whenAny()` which are important compositional building blocks.
+The primary semantic differences are that Wangle Futures and Promises are not threadsafe; and as does `boost::future`, Wangle supports continuing callbacks (`then()`) and there are helper methods `whenAll()` and `whenAny()` which are important compositional building blocks.
## Brief Synopsis
`all` and `any` are Futures (for the exact type and usage see the header files). They will be complete when all/one of `futs` are complete, respectively. (There is also `whenN()` for when you need *some*.)
-Second, we can attach continuations (aka callbacks) to a Future, and chain them together monadically. An example will clarify:
+Second, we can attach callbacks to a Future, and chain them together monadically. An example will clarify:
```C++
Future<GetReply> fut1 = mc.get("foo");
That example is a little contrived but the idea is that you can transform a result from one type to another, potentially in a chain, and unhandled errors propagate. Of course, the intermediate variables are optional. `Try<T>` is the object wrapper that supports both value and exception.
-Using `then` to add continuations is idiomatic. It brings all the code into one place, which avoids callback hell.
+Using `then` to add callbacks is idiomatic. It brings all the code into one place, which avoids callback hell.
Up to this point we have skirted around the matter of waiting for Futures. You may never need to wait for a Future, because your code is event-driven and all follow-up action happens in a then-block. But if want to have a batch workflow, where you initiate a batch of asynchronous operations and then wait for them all to finish at a synchronization point, then you will want to wait for a Future.
## FAQ
### Why not use std::future?
-No callback or continuation support.
+No callback support.
See also http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3428.pdf
### Why not use boost::future?
C++. It boils down to wanting to return a Future by value for performance (move semantics and compiler optimizations), and programmer sanity, and needing a reference to the shared state by both the user (which holds the Future) and the asynchronous operation (which holds the Promise), and allowing either to go out of scope.
### What about proper continuations? Futures suck.
-People mean two things here, they either mean using continuations or they mean using generators which require continuations. It's important to know those are two distinct questions, but in our context the answer is the same because continuations are a prerequisite for generators.
+People mean two things here, they either mean using continuations (as in CSP) or they mean using generators which require continuations. It's important to know those are two distinct questions, but in our context the answer is the same because continuations are a prerequisite for generators.
C++ doesn't directly support continuations very well. But there are some ways to do them in C/C++ that rely on some rather low-level facilities like `setjmp` and `longjmp` (among others). So yes, they are possible (cf. [Mordor](https://github.com/ccutrer/mordor)).
}
template <typename F>
- void setContinuation(F func) {
+ void setCallback_(F func) {
if (continuation_) {
- throw std::logic_error("setContinuation called twice");
+ throw std::logic_error("setCallback_ called twice");
}
continuation_ = std::move(func);
template <typename... Ts, typename THead, typename... Fs>
typename std::enable_if<sizeof...(Fs) == 0, void>::type
whenAllVariadicHelper(VariadicContext<Ts...> *ctx, THead&& head, Fs&&... tail) {
- head.setContinuation([ctx](Try<typename THead::value_type>&& t) {
+ head.setCallback_([ctx](Try<typename THead::value_type>&& t) {
std::get<sizeof...(Ts) - sizeof...(Fs) - 1>(ctx->results) = std::move(t);
if (++ctx->count == ctx->total) {
ctx->p.setValue(std::move(ctx->results));
template <typename... Ts, typename THead, typename... Fs>
typename std::enable_if<sizeof...(Fs) != 0, void>::type
whenAllVariadicHelper(VariadicContext<Ts...> *ctx, THead&& head, Fs&&... tail) {
- head.setContinuation([ctx](Try<typename THead::value_type>&& t) {
+ head.setCallback_([ctx](Try<typename THead::value_type>&& t) {
std::get<sizeof...(Ts) - sizeof...(Fs) - 1>(ctx->results) = std::move(t);
if (++ctx->count == ctx->total) {
ctx->p.setValue(std::move(ctx->results));
Promise<int> p;
auto f = p.getFuture().then([x](Try<int>&& t) { *x = t.value(); });
- // The continuation hasn't executed
+ // The callback hasn't executed
EXPECT_EQ(0, *x);
- // The continuation has a reference to x
+ // The callback has a reference to x
EXPECT_EQ(2, x.use_count());
p.setValue(42);
- // the continuation has executed
+ // the callback has executed
EXPECT_EQ(42, *x);
- // the continuation has been destructed
+ // the callback has been destructed
// and has released its reference to x
EXPECT_EQ(1, x.use_count());
}
EXPECT_EQ(result.load(), 1);
p.setValue(42);
t.join();
- // validate that the continuation ended up executing in this thread, which
+ // validate that the callback ended up executing in this thread, which
// is more to ensure that this test actually tests what it should
EXPECT_EQ(id, std::this_thread::get_id());
EXPECT_EQ(result.load(), 42);