Commit 87ab4855 authored by Marshall Cline's avatar Marshall Cline Committed by Facebook Github Bot

add contracts to Promise

Summary: Add contracts to Promise

Reviewed By: LeeHowes

Differential Revision: D7923804

fbshipit-source-id: f7f29646d523ee69f00ac8576e2edf7e003f25e3
parent b02ec278
...@@ -67,84 +67,334 @@ class CoreCallbackState; ...@@ -67,84 +67,334 @@ class CoreCallbackState;
} // namespace detail } // namespace detail
} // namespace futures } // namespace futures
/// Promises and futures provide a potentially nonblocking mechanism
/// to execute a producer/consumer operation concurrently, with
/// threading/pools controlled via an executor. There are multiple potential
/// patterns for using promises and futures including some that block the
/// caller, though that is discouraged; it should be used only when necessary.
///
/// One typical pattern uses a series of calls to set up a small, limited
/// program that...
///
/// - ...performs the desired operations (based on a lambda)...
/// - ...on an asynchronously provided input (an exception or a value)...
/// - ...lazily, when that input is ready (without blocking the caller)...
/// - ...using appropriate execution resources (determined by the executor)...
/// - ...then after constructing the 'program,' launches the asynchronous
/// producer.
///
/// That usage pattern looks roughly like this:
///
/// ```
/// auto [p, f] = makePromiseContract(executor);
/// g = std::move(f).then([](MyValue&& x) {
/// ...executor runs this code if/when a MyValue is ready...
/// });
/// ...launch the async producer that eventually calls p.setResult()...
/// ```
///
/// This is just one of many potential usage patterns. It has the desired
/// property of being nonblocking to the caller. Of course the `.then()`
/// code is deferred until the produced value (or exception) is ready,
/// but no code actually blocks pending completion of other operations.
///
/// The promise/future mechanism is limited to a single object of some arbitrary
/// type. It also supports a (logically) void result, i.e., in cases where the
/// continuation/consumer (the `.then()` code if using the above pattern) is not
/// expecting a value because the 'producer' is running for its side-effects.
///
/// The primary data movement is from producer to consumer, however Promise and
/// Future also provide a mechanism where the consumer can send an interruption
/// message to the producer. The meaning and response to that interruption
/// message is controlled by the promise; see `Promise::setInterruptHandler()`.
///
/// Neither Promise nor Future is thread-safe. All internal interactions
/// between a promise and its associated future are thread-safe, provided that
/// callers otherwise honor the promise's contract and the future's contract.
///
/// Logically there are up to three threads (though in practice there are often
/// fewer - one thread might take on more than one role):
///
/// - Set-up thread: thread used to construct the Promise, and often also to
/// set up the SemiFuture/Future.
/// - Producer thread: thread that produces the result.
/// - Consumer thread: thread in which the continuation is invoked (a
/// continuation is a callback provided to `.then` or to a variant).
///
/// For description purposes, the term 'shared state' is used to describe the
/// logical state shared by the promise and the future. This 'third object'
/// represents things like whether the result has been fulfilled, the value or
/// exception in that result, and the data needed to handle interruption
/// requests.
///
/// A promise can be in various logical states:
///
/// - valid vs. invalid (has vs. does not have a shared state, respectfully).
/// - fulfilled vs. unfulfilled (an invalid promise is always fulfilled; a valid
/// promise is fulfilled if the shared-state has a result).
///
/// A promise `p` may optionally have an associated future. This future, if it
/// exists, may be either a SemiFuture or a Future, and is defined as the
/// future (if any) that holds the same shared state as promise `p`.
/// The associated future is initially the future returned from
/// `p.getFuture()` or `p.getSemiFuture()`, but various operations
/// may transfer the shared state from one future to another.
template <class T> template <class T>
class Promise { class Promise {
public: public:
static Promise<T> makeEmpty() noexcept; // equivalent to moved-from /// Returns an invalid promise.
///
/// Postconditions:
///
/// RESULT.valid() will be false
/// RESULT.isFulfilled() will be true
static Promise<T> makeEmpty() noexcept;
/// Constructs a valid but unfulfilled promise.
///
/// Postconditions:
///
/// - valid() will be true (it will have a shared state)
/// - isFulfilled() will be false (its shared state won't have a result)
Promise(); Promise();
/// Postconditions:
///
/// - If valid() and !isFulfilled(), the associated future (if any) will be
/// completed with a `BrokenPromise` exception *as if* by
/// `setException(...)`.
/// - If valid(), releases, possibly destroying, the shared state.
~Promise(); ~Promise();
// not copyable // not copyable
Promise(Promise const&) = delete; Promise(Promise const&) = delete;
Promise& operator=(Promise const&) = delete; Promise& operator=(Promise const&) = delete;
// movable /// Move ctor
Promise(Promise<T>&&) noexcept; ///
Promise& operator=(Promise<T>&&) noexcept; /// Postconditions:
///
/// - `this` will have whatever shared-state was previously held by `other`
/// (if any)
/// - `other.valid()` will be false (`other` will not have any shared state)
Promise(Promise<T>&& other) noexcept;
/** Return a SemiFuture tied to the shared core state. This can be called only /// Move assignment
once, thereafter FutureAlreadyRetrieved exception will be raised. */ ///
/// Postconditions:
///
/// - If valid() and !isFulfilled(), the associated future (if any) will be
/// completed with a `BrokenPromise` exception *as if* by
/// `setException(...)`.
/// - If valid(), releases, possibly destroying, the original shared state.
/// - `this` will have whatever shared-state was previously held by `other`
/// (if any)
/// - `other.valid()` will be false (`other` will not have any shared state)
Promise& operator=(Promise<T>&& other) noexcept;
/// Return a SemiFuture associated with this Promise, sharing the same shared
/// state as `this`.
///
/// Preconditions:
///
/// - valid() (else throws PromiseInvalid)
/// - neither getSemiFuture() nor getFuture() may have been called previously
/// on `this` Promise (else throws FutureAlreadyRetrieved)
///
/// Postconditions:
///
/// - RESULT.valid() will be true
/// - RESULT will share the same shared-state as `this`
///
/// DEPRECATED: use `folly::makePromiseContract()` instead.
SemiFuture<T> getSemiFuture(); SemiFuture<T> getSemiFuture();
/** Return a Future tied to the shared core state. This can be called only /// Return a Future associated with this Promise, sharing the same shared
once, thereafter FutureAlreadyRetrieved exception will be raised. /// state as `this`.
NOTE: This function is deprecated. Please use getSemiFuture and pass the ///
appropriate executor to .via on the returned SemiFuture to get a /// Preconditions:
valid Future where necessary. */ ///
/// - valid() (else throws PromiseInvalid)
/// - neither getSemiFuture() nor getFuture() may have been called previously
/// on `this` Promise (else throws FutureAlreadyRetrieved)
///
/// Postconditions:
///
/// - RESULT.valid() will be true
/// - RESULT will share the same shared-state as `this`
///
/// DEPRECATED: use `folly::makePromiseContract()` instead. If you can't use
/// that, use `this->getSemiFuture()` then get a Future by calling `.via()`
/// with an appropriate executor.
Future<T> getFuture(); Future<T> getFuture();
/** Fulfill the Promise with an exception_wrapper */ /// Fulfill the Promise with an exception_wrapper.
///
/// Sample usage:
///
/// ```
/// Promise<MyValue> p = ...
/// ...
/// auto const ep = std::exception_ptr();
/// auto const ew = exception_wrapper::from_exception_ptr(ep);
/// p.setException(ew);
/// ```
///
/// Functionally equivalent to `setTry(Try<T>(std::move(ew)))`
///
/// Preconditions:
///
/// - valid() (else throws PromiseInvalid)
/// - !isFulfilled() (else throws PromiseAlreadySatisfied)
///
/// Postconditions:
///
/// - isFulfilled() will be true
/// - valid() will be unchanged (true)
/// - The associated future (if any) will complete with the exception.
void setException(exception_wrapper ew); void setException(exception_wrapper ew);
/** Fulfill the Promise with an exception_ptr, e.g. /// Fulfill the Promise with the exception referenced by `ep` *as if* by
try { /// `setException(exception_wrapper::from_exception_ptr(ep))`.
... ///
} catch (...) { /// Please see `setException(exception_wrapper)` for semantics/contract.
p.setException(std::current_exception()); /// Please also use `setException(exception_wrapper)` instead of this.
}
*/
[[deprecated("use setException(exception_wrapper)")]] [[deprecated("use setException(exception_wrapper)")]]
void setException(std::exception_ptr const&); void setException(std::exception_ptr const& ep);
/** Fulfill the Promise with an exception type E, which can be passed to /// Fulfill the Promise with exception `e` *as if* by
std::make_exception_ptr(). Useful for originating exceptions. If you /// `setException(make_exception_wrapper<E>(e))`.
caught an exception the exception_wrapper form is more appropriate. ///
*/ /// Please see `setException(exception_wrapper)` for semantics/contract.
template <class E> template <class E>
typename std::enable_if<std::is_base_of<std::exception, E>::value>::type typename std::enable_if<std::is_base_of<std::exception, E>::value>::type
setException(E const&); setException(E const& e);
/// Set an interrupt handler to handle interrupts. See the documentation for /// Sets a handler for the producer to receive a (logical) interruption
/// Future::raise(). Your handler can do whatever it wants, but if you /// request (exception) sent from the consumer via `future.raise()`.
/// bother to set one then you probably will want to fulfill the promise with ///
/// an exception (or special value) indicating how the interrupt was /// Details: The consumer calls `future.raise()` when it wishes to send a
/// handled. /// logical interruption message (an exception), and that exception/message
/// is passed to `fn()`. The thread used to call `fn()` depends on timing
/// (see Postconditions for threading details).
///
/// Handler `fn()` can do anything you want, but if you bother to set one
/// then you probably will want to (more or less immediately) fulfill the
/// promise with an exception (or other special value) indicating how the
/// interrupt was handled.
///
/// This call silently does nothing if `isFulfilled()`.
/// ///
/// `fn` must be copyable and must be invocable with /// Preconditions:
///
/// - valid() (else throws PromiseInvalid)
/// - `fn` must be copyable and must be invocable with
/// `exception_wrapper const&` /// `exception_wrapper const&`
/// - the code within `fn()` must be safe to run either synchronously within
/// the `setInterruptHandler()` call or asynchronously within the consumer
/// thread's call to `future.raise()`.
/// - the code within `fn()` must also be safe to run after this promise is
/// fulfilled; this may have lifetime/race-case ramifications, e.g., if the
/// code of `fn()` might access producer-resources that will be destroyed,
/// then the destruction of those producer-resources must be deferred beyond
/// the moment when this promise is fulfilled.
///
/// Postconditions:
///
/// - if the consumer calls `future.raise()` early enough (up to a particular
/// moment within the `setInterruptHandler()` call), `fn()` will be called
/// synchronously (in the current thread, during this call).
/// - if the consumer calls `future.raise()` after that moment within
/// `setInterruptHandler()` but before this promise is fulfilled, `fn()`
/// will be called asynchronously (in the consumer's thread, within the call
/// to `future.raise()`).
/// - if the consumer calls `future.raise()` after this promise is fulfilled,
/// `fn()` may or may not be called at all, and if it is called, it will be
/// called asynchronously (within the consumer's call to `future.raise()`).
///
/// IMPORTANT: `fn()` should return quickly since it could block this call
/// to `promise.setInterruptHandler()` and/or a concurrent call to
/// `future.raise()`. Those two functions contend on the same lock; those
/// calls could block if `fn()` is invoked within one of those while the
/// lock is held.
template <typename F> template <typename F>
void setInterruptHandler(F&& fn); void setInterruptHandler(F&& fn);
/// Sugar to fulfill this Promise<Unit> /// Fulfills a (logically) void Promise, that is, Promise<Unit>.
/// (If you want a void-promise, use Promise<Unit>, not Promise<void>.)
///
/// Preconditions:
///
/// - valid() (else throws PromiseInvalid)
/// - !isFulfilled() (else throws PromiseAlreadySatisfied)
///
/// Postconditions:
///
/// - isFulfilled() will be true
/// - valid() will be unchanged (true)
template <class B = T> template <class B = T>
typename std::enable_if<std::is_same<Unit, B>::value, void>::type typename std::enable_if<std::is_same<Unit, B>::value, void>::type
setValue() { setValue() {
setTry(Try<T>(T())); setTry(Try<T>(T()));
} }
/** Set the value (use perfect forwarding for both move and copy) */ /// Fulfill the Promise with the specified value using perfect forwarding.
///
/// Functionally equivalent to `setTry(Try<T>(std::forward<M>(value)))`
///
/// Preconditions:
///
/// - valid() (else throws PromiseInvalid)
/// - !isFulfilled() (else throws PromiseAlreadySatisfied)
///
/// Postconditions:
///
/// - isFulfilled() will be true
/// - valid() will be unchanged (true)
/// - The associated future will see the value, e.g., in its continuation.
template <class M> template <class M>
void setValue(M&& value); void setValue(M&& value);
/// Fulfill the Promise with the specified Try (value or exception).
///
/// Preconditions:
///
/// - valid() (else throws PromiseInvalid)
/// - !isFulfilled() (else throws PromiseAlreadySatisfied)
///
/// Postconditions:
///
/// - isFulfilled() will be true
/// - valid() will be unchanged (true)
/// - The associated future will see the result, e.g., in its continuation.
void setTry(Try<T>&& t); void setTry(Try<T>&& t);
/** Fulfill this Promise with the result of a function that takes no /// Fulfill this Promise with the result of a function that takes no
arguments and returns something implicitly convertible to T. /// arguments and returns something implicitly convertible to T.
Captures exceptions. e.g. ///
/// Example:
p.setWith([] { do something that may throw; return a T; }); ///
*/ /// ```
/// p.setWith([] { do something that may throw; return a T; });
/// ```
///
/// Functionally equivalent to `setTry(makeTryWith(static_cast<F&&>(func)));`
///
/// Preconditions:
///
/// - valid() (else throws PromiseInvalid)
/// - !isFulfilled() (else throws PromiseAlreadySatisfied)
///
/// Postconditions:
///
/// - `func()` will be run synchronously (in this thread, during this call)
/// - If `func()` returns, the return value will be captured as if via
/// `setValue()`
/// - If `func()` throws, the exception will be captured as if via
/// `setException()`
/// - isFulfilled() will be true
/// - valid() will be unchanged (true)
/// - The associated future will see the result, e.g., in its continuation.
template <class F> template <class F>
void setWith(F&& func); void setWith(F&& func);
...@@ -154,6 +404,11 @@ class Promise { ...@@ -154,6 +404,11 @@ class Promise {
return core_ != nullptr; return core_ != nullptr;
} }
/// True if either this promise was fulfilled or is invalid.
///
/// - True if !valid()
/// - True if valid() and this was fulfilled (a prior call to `setValue()`,
/// `setTry()`, `setException()`, or `setWith()`)
bool isFulfilled() const noexcept; bool isFulfilled() const noexcept;
private: private:
......
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