template <typename Executor>
inline Future<T> Future<T>::via(Executor* executor) {
throwIfInvalid();
- MoveWrapper<Promise<T>> promise;
-
- auto f = promise->getFuture();
- // We are obligated to return a cold future.
- f.deactivate();
- // But we also need to make this one cold for via to at least work some of
- // the time. (see below)
- deactivate();
-
- then([=](Try<T>&& t) mutable {
- MoveWrapper<Try<T>> tw(std::move(t));
- // There is a race here.
- // When the promise is fulfilled, and the future is still inactive, when
- // the future is activated (e.g. by destruction) the callback will happen
- // in that context, not in the intended context (which has already left
- // the building).
- //
- // Currently, this will work fine because all the temporaries are
- // destructed in an order that is compatible with this implementation:
- //
- // makeFuture().via(x).then(a).then(b);
- //
- // However, this will not work reliably:
- //
- // auto f2 = makeFuture().via(x);
- // f2.then(a).then(b);
- //
- // Because the first temporary is destructed on the first line, and the
- // executor is fed. But by the time f2 is destructed, the executor
- // may have already fulfilled the promise on the other thread.
- //
- // TODO(#4920689) fix it
- executor->add([=]() mutable { promise->fulfilTry(std::move(*tw)); });
- });
- return f;
+ this->deactivate();
+ state_->setExecutor(executor);
+
+ return std::move(*this);
}
template <class T>
#include <folly/wangle/Try.h>
#include <folly/wangle/Promise.h>
#include <folly/wangle/Future.h>
+#include <folly/wangle/Executor.h>
namespace folly { namespace wangle { namespace detail {
bool isActive() { return active_; }
+ void setExecutor(Executor* x) {
+ std::lock_guard<decltype(mutex_)> lock(mutex_);
+ executor_ = x;
+ }
+
private:
void maybeCallback() {
std::lock_guard<decltype(mutex_)> lock(mutex_);
if (!calledBack_ &&
value_ && callback_ && isActive()) {
- // TODO we should probably try/catch here
- callback_(std::move(*value_));
- calledBack_ = true;
+ // TODO(5306911) we should probably try/catch here
+ if (executor_) {
+ MoveWrapper<folly::Optional<Try<T>>> val(std::move(value_));
+ MoveWrapper<std::function<void(Try<T>&&)>> cb(std::move(callback_));
+ executor_->add([cb, val]() mutable { (*cb)(std::move(**val)); });
+ calledBack_ = true;
+ } else {
+ callback_(std::move(*value_));
+ calledBack_ = true;
+ }
}
}
bool calledBack_ = false;
unsigned char detached_ = 0;
bool active_ = true;
+ Executor* executor_ = nullptr;
// this lock isn't meant to protect all accesses to members, only the ones
// that need to be threadsafe: the act of setting value_ and callback_, and