We’ve seen how to use folly::Promise to convert old style callbacks to chained callbacks. The key is to wrap promise in callback and call setValue to pass the data back:
SemiFuture<rpcReply> future_async_call(rpcArgs* args) {
Promise promise;
auto f = promise.getSemiFuture();
async_call(args, [p=std::move(promise)](rpcReply&& r) mutable {
p.setValue(std::move(r));
});
return f;
}
Here is a question I always have about promise: how is it implemented such that it is safe to destroy a folly::Promise after calling setValue? In another word, how does setValue pass the data to the future?
Promise::setValue
What does Promise::setValue do? Let’s take a look at relevant code:
template <class T>
SemiFuture<T> Promise<T>::getSemiFuture() {
if (retrieved_) {
throw_exception<FutureAlreadyRetrieved>();
}
retrieved_ = true;
return SemiFuture<T>(&getCore());
}
template <class T>
template <class M>
void Promise<T>::setValue(M&& v) {
static_assert(!std::is_same<T, void>::value, "Use setValue() instead");
setTry(Try<T>(std::forward<M>(v)));
}
template <class T>
void Promise<T>::setTry(Try<T>&& t) {
throwIfFulfilled();
core_->setResult(std::move(t));
}
...
Core& getCore() {
return getCoreImpl(core_);
}
Core const& getCore() const {
return getCoreImpl(core_);
}
...
// shared core state object
// usually you should use `getCore()` instead of directly accessing `core_`.
using Core = futures::detail::Core<T>;
Core* core_;
Note that
- Promise wraps futures::detail::Core, and simply calls core_->setResult in setValue.
- getSemiFuture simply constructs a SemiFuture from core_.
So we’ll have to understand how Core is implemented to find the answser to our question.
Core::setResult
Core has good documentation on how it works. It does nothing special but storing the callback and the result: both of them must be present so the callback can be triggered with the result. From the documentation, it uses a small state machine to guard its state transition from state Start to state Done.
/// +----------------------------------------------------------------+
/// | ---> OnlyResult ----- |
/// | / \ |
/// | (setResult()) (setCallback()) |
/// | / \ |
/// | Start ---------> ------> Done |
/// | \ \ / |
/// | \ (setCallback()) (setResult()) |
/// | \ \ / |
/// | \ ---> OnlyCallback --- |
First let’s take a look at members to get a sense of what a Core stores:
template <typename T>
class Core final {
...
using Context = std::shared_ptr<RequestContext>;
union {
Callback callback_;
};
// place result_ next to increase the likelihood that the value will be
// contained entirely in one cache line
union {
Result result_;
Core* proxy_;
};
std::atomic<State> state_;
std::atomic<unsigned char> attached_;
...
};
It stores a callback, a result, its state, and attached number for reference count (see below). It stores many other members, but we just need to see those above to continue our exploration.
What Core supports are
bool hasCallback()
bool hasResult()
bool ready()
void setCallback(...)
void setProxy(...)
void setResult(...)
void setExecutor(...)
void setInterruptHandler(...)
void detachFuture()
void detachPromise()
void raise(...)
Let’s get back to setResult. It is implemented as
/// Call only from producer thread.
/// Call only once - else undefined behavior.
///
/// See FSM graph for allowed transitions.
///
/// If it transitions to Done, synchronously initiates a call to the callback,
/// and might also synchronously execute that callback (e.g., if there is no
/// executor, if the executor is inline or if completingKA represents the
/// same executor as does executor_).
void setResult(Executor::KeepAlive<>&& completingKA, Try<T>&& t) {
DCHECK(!hasResult());
::new (&result_) Result(std::move(t));
auto state = state_.load(std::memory_order_acquire);
switch (state) {
case State::Start:
if (folly::atomic_compare_exchange_strong_explicit(
&state_,
&state,
State::OnlyResult,
std::memory_order_release,
std::memory_order_acquire)) {
return;
}
assume(
state == State::OnlyCallback ||
state == State::OnlyCallbackAllowInline);
FOLLY_FALLTHROUGH;
case State::OnlyCallback:
case State::OnlyCallbackAllowInline:
state_.store(State::Done, std::memory_order_relaxed);
doCallback(std::move(completingKA), state);
return;
case State::OnlyResult:
case State::Proxy:
case State::Done:
case State::Empty:
default:
terminate_with<std::logic_error>("setResult unexpected state");
}
}
Here we see it does state transition, but more importantly,
- It stores the result locally to result_.
- If there is a callback stored, setResult triggers the callback.
- If there is a result stored already, setResult termiantes the program because there must be only one result.
Core is really nothing special here. It stores the result when setResult is called, and stores the callback when setCallback is called. If both are present, trigger the callback via doCallback.
Attach and Detach: Reference Counting in Core
Now that we’ve learned Core supports data passing via a shared state, let’s try to answer the question: after we call setValue, what happens if we delete the promise object immediately?
To answer that question, let’s take a look at the ownership of core in Promise and SemiFuture.
When Promise is created, it creates a core via make.
template <class T>
Promise<T>::Promise() : retrieved_(false), core_(Core::make()) {}
When a Core is created this way, internally Core::attched_ is initialized to 2 because a core has two attachments: one is the promise, or “the producer,” and the other one the future associated with the promise, or “the comsumer.” The attached_ is for reference counting.
Core() : state_(State::Start), attached_(2) {}
Calling getSemiFuture will check and set retrieved_ to true; there must be only one future associated with a promise. and core_ will be passed into SemiFuture:
template <class T>
SemiFuture<T> Promise<T>::getSemiFuture() {
if (retrieved_) {
throw_exception<FutureAlreadyRetrieved>();
}
retrieved_ = true;
return SemiFuture<T>(&getCore());
}
Does SemiFuture‘s constructor do anything sepecial with the core?
explicit SemiFuture(Core* obj) : Base(obj) {}
Core simply got passed to base class. What about the base class FutureBase, does it do anything special?
explicit FutureBase(Core* obj) : core_(obj) {}
No, the core’s raw poiner is just passed into SemiFuture and stored there.
We know that when we call Promise::setValue, Core::setResult is called, which simply stores the value internally if the callback is not stored yet. If we delete promise immediately, the following code in promise will execute:
template <class T>
Promise<T>::~Promise() {
detach();
}
template <class T>
void Promise<T>::detach() {
if (core_) {
if (!retrieved_) {
core_->detachPromise();
}
futures::detail::coreDetachPromiseMaybeWithResult(*core_);
core_ = nullptr;
}
}
If a future is retrieved, the destructor of Promise simply does nothing; it does not destroy core_.
If a future is not retrieved, it calls Core::detachPromise, which decreases attached_ and core_ is freed:
/// Called by a destructing Promise (in the producer thread, by definition).
/// Calls `delete this` if there are no more references to `this`
/// (including if `detachFuture()` is called previously or concurrently).
void detachPromise() noexcept {
DCHECK(hasResult());
detachOne();
}
void detachOne() noexcept {
auto a = attached_.fetch_sub(1, std::memory_order_acq_rel);
assert(a >= 1);
if (a == 1) {
delete this;
}
}
Now we can answer the question: how does Promise passes value to Future.
There is no data passing between Promise and Future; they share the same internal state via Core. Promise is the data supplier; Future is the callback supplier. Promise, the data supplier, can be destroyed before a callback is supplied because Core is reference counted.