template<typename T>
class folly::futures::detail::Core< T >
The shared state object for Future and Promise.
Nomenclature:
- "result": a
Try
object which, when set, contains a T
or exception.
- "move-out the result": used to mean the
Try
object and/or its contents are moved-out by a move-constructor or move-assignment. After the result is set, Core itself never modifies (including moving out) the result; however the docs refer to both since caller-code can move-out the result implicitly (see below for examples) whereas other types of modifications are more explicit in the caller code.
- "callback": a function provided by the future which Core may invoke. The thread in which the callback is invoked depends on the executor; if there is no executor or an inline executor the thread-choice depends on timing.
- "executor": an object which may in the future invoke a provided function (some executors may, as a matter of policy, simply destroy provided functions without executing them).
- "consumer thread": the thread which currently owns the Future and which may provide the callback and/or the interrupt.
- "producer thread": the thread which owns the Future and which may provide the result and which may provide the interrupt handler.
- "interrupt": if provided, an object managed by (if non-empty)
exception_wrapper
.
- "interrupt handler": if provided, a function-object passed to
promise.setInterruptHandler()
. Core invokes the interrupt handler with the interrupt when both are provided (and, best effort, if there is not yet any result).
Core holds three sets of data, each of which is concurrency-controlled:
- The primary producer-to-consumer info-flow: this info includes the result, callback, executor, and a priority for running the callback. Management of and concurrency control for this info is by an FSM based on
enum class State
. All state transitions are atomic; other producer-to-consumer data is sometimes modified within those transitions; see below for details.
- The consumer-to-producer interrupt-request flow: this info includes an interrupt-handler and an interrupt. Concurrency of this info is controlled by a Spin Lock (
interruptLock_
).
- Lifetime control info: this includes two reference counts, both which are internally synchronized (atomic).
The FSM to manage the primary producer-to-consumer info-flow has these allowed (atomic) transitions:
+-------------------------------------------------------------—+ | —> OnlyResult --— | | / \ | | (setResult()) (setCallback()) | | / \ | | Start ------—> ---—> Done | | \ \ / | | \ (setCallback()) (setResult()) | | \ \ / | | \ —> OnlyCallback — | | \ \ | | (setProxy()) (setProxy()) | | \ \ | | \ ---—> Empty | | \ / | | \ (setCallback()) | | \ / | | ------—> Proxy -------— | +-------------------------------------------------------------—+
States and the corresponding producer-to-consumer data status & ownership:
- Start: has neither result nor callback. While in this state, the producer thread may set the result (
setResult()
) or the consumer thread may set the callback (setCallback()
).
- OnlyResult: producer thread has set the result and must never access it. The result is logically owned by, and possibly modified or moved-out by, the consumer thread. Callers of the future object can do arbitrary modifications, including moving-out, via continuations or via non-const and/or rvalue-qualified
future.result()
, future.value()
, etc. Future/SemiFuture proper also move-out the result in some cases, e.g., in wait()
, get()
, when passing through values or results from core to core, as then-value
and then-error
, etc.
- OnlyCallback: consumer thread has set a callback/continuation. From this point forward only the producer thread can safely access that callback (see
setResult()
and doCallback()
where the producer thread can both read and modify the callback).
- Proxy: producer thread has set a proxy core which the callback should be proxied to.
- Done: callback can be safely accessed only within
doCallback()
, which gets called on exactly one thread exactly once just after the transition to Done. The future object will have determined whether that callback has/will move-out the result, but either way the result remains logically owned exclusively by the consumer thread (the code of Future/SemiFuture, of the continuation, and/or of callers of future.result()
, etc.).
- Empty: the core successfully proxied the callback and is now empty.
Start state:
- Start: e.g.,
Core<X>::make()
.
- (See also
Core<X>::make(x)
which logically transitions Start => OnlyResult within the underlying constructor.)
Terminal states:
- OnlyResult: a terminal state when a callback is never attached, and also sometimes when a callback is provided, e.g., sometimes when
future.wait()
and/or future.get()
are used.
- Done: a terminal state when
future.then()
is used, and sometimes also when future.wait()
and/or future.get()
are used.
- Proxy: a terminal state if proxy core was set, but callback was never set.
- Empty: a terminal state when proxying a callback was successful.
Notes and caveats:
- Unfortunately, there are things that users can do to break concurrency and we can't detect that. However users should be ok if they follow move semantics religiously wrt threading.
- Futures and/or Promises can and usually will migrate between threads, though this usually happens within the API code. For example, an async operation will probably make a promise-future pair (see overloads of
makePromiseContract()
), then move the Promise into another thread that will eventually fulfill it.
- Things get slightly more complicated with executors and via, but the principle is the same.
- In general, as long as the user doesn't access a future or promise object from more than one thread at a time there won't be any problems.
Definition at line 195 of file Core.h.
Call only from consumer thread (since the consumer thread can modify the referenced Try object; see non-const overloads of future.result()
, etc., and certain Future-provided callbacks which move-out the result).
Unconditionally returns a reference to the result.
State dependent preconditions:
- Start or OnlyCallback: Never safe - do not call. (Access in those states would be undefined behavior since the producer thread can, in those states, asynchronously set the referenced Try object.)
- OnlyResult: Always safe. (Though the consumer thread should not use the returned reference after it attaches a callback unless it knows that the callback does not move-out the referenced result.)
- Done: Safe but sometimes unusable. (Always returns a valid reference, but the referenced result may or may not have been modified, including possibly moved-out, depending on what the callback did; some but not all callbacks modify (possibly move-out) the result.)
Definition at line 280 of file Core.h.
References folly::futures::detail::Proxy.
283 while (core->state_.load(std::memory_order_relaxed) ==
State::Proxy) {
286 return core->result_;
bool hasResult() const noexcept
template<typename T>
template<typename F >
Call only from consumer 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 or if the executor is inline).
Definition at line 306 of file Core.h.
References folly::assume(), callback_, folly::futures::detail::Done, folly::gen::move, folly::futures::detail::OnlyCallback, folly::futures::detail::OnlyResult, folly::futures::detail::Proxy, and folly::futures::detail::Start.
Referenced by folly::futures::detail::FutureBase< T >::setCallback_().
316 if (
state_.compare_exchange_strong(
333 terminate_with<std::logic_error>(
"setCallback unexpected state");
std::atomic< State > state_
folly::Function< void(Result &&)> Callback
constexpr detail::Map< Move > move
State
See Core for details.
bool hasCallback() const noexcept
May call from any thread.
if(FOLLY_USE_SYMBOLIZER) add_library(folly_exception_tracer_base ExceptionTracer.cpp StackTrace.cpp) apply_folly_compile_options_to_target(folly_exception_tracer_base) target_link_libraries(folly_exception_tracer_base PUBLIC folly) add_library(folly_exception_tracer ExceptionStackTraceLib.cpp ExceptionTracerLib.cpp) apply_folly_compile_options_to_target(folly_exception_tracer) target_link_libraries(folly_exception_tracer PUBLIC folly_exception_tracer_base) add_library(folly_exception_counter ExceptionCounterLib.cpp) apply_folly_compile_options_to_target(folly_exception_counter) target_link_libraries(folly_exception_counter PUBLIC folly_exception_tracer) install(FILES ExceptionAbi.h ExceptionCounterLib.h ExceptionTracer.h ExceptionTracerLib.h StackTrace.h DESTINATION $
std::shared_ptr< RequestContext > Context
FOLLY_ALWAYS_INLINE void assume(bool cond)
template<typename T>
template<typename F >
Call only from producer thread
May invoke fn()
(passing the interrupt) synchronously within this call (if raise()
preceded or perhaps if raise()
is called concurrently).
Has no effect if State is OnlyResult or Done.
Note: fn()
must not touch resources that are destroyed immediately after setResult()
is called. Reason: it is possible for fn()
to get called asynchronously (in the consumer thread) after the producer thread calls setResult()
.
Definition at line 483 of file Core.h.
References folly::as_const(), and folly::lock().
Referenced by folly::Promise< T >::setInterruptHandler().
constexpr T const & as_const(T &t) noexcept
auto lock(Synchronized< D, M > &synchronized, Args &&...args)
std::unique_ptr< exception_wrapper > interrupt_
void setInterruptHandlerNoLock(std::function< void(exception_wrapper const &)> fn)
bool hasResult() const noexcept
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 or if the executor is inline).
Definition at line 376 of file Core.h.
References folly::assume(), folly::futures::detail::Done, FOLLY_FALLTHROUGH, folly::gen::move, folly::futures::detail::OnlyCallback, folly::futures::detail::OnlyResult, folly::futures::detail::Start, and folly::pushmi::detail::t.
Referenced by folly::futures::detail::coreDetachPromiseMaybeWithResult(), and folly::Promise< T >::setTry().
385 if (
state_.compare_exchange_strong(
393 if (
state_.compare_exchange_strong(
401 terminate_with<std::logic_error>(
"setResult unexpected state");
std::atomic< State > state_
constexpr detail::Map< Move > move
FOLLY_ALWAYS_INLINE void assume(bool cond)
bool hasResult() const noexcept
#define FOLLY_FALLTHROUGH