Commit 9d6a5522 authored by Marshall Cline's avatar Marshall Cline Committed by Facebook Github Bot

add method-level contracts to SemiFuture & Future

Summary:
method-level contracts

(Note: this ignores all push blocking failures!)

Reviewed By: yfeldblum

Differential Revision: D8132350

fbshipit-source-id: d79b1d88a38ea97de8d0e841e822805aff3959ae
parent 71bfdd3e
......@@ -104,7 +104,13 @@ class FutureBase {
public:
typedef T value_type;
/// Construct a Future from a value (perfect forwarding)
/// Construct from a value (perfect forwarding)
///
/// Postconditions:
///
/// - `valid() == true`
/// - `isReady() == true`
/// - `hasValue() == true`
template <
class T2 = T,
typename = typename std::enable_if<
......@@ -112,6 +118,13 @@ class FutureBase {
!isSemiFuture<typename std::decay<T2>::type>::value>::type>
/* implicit */ FutureBase(T2&& val);
/// Construct a (logical) FutureBase-of-void.
///
/// Postconditions:
///
/// - `valid() == true`
/// - `isReady() == true`
/// - `hasValue() == true`
template <class T2 = T>
/* implicit */ FutureBase(
typename std::enable_if<std::is_same<Unit, T2>::value>::type*);
......@@ -133,44 +146,97 @@ class FutureBase {
~FutureBase();
/// true if this has a shared state;
/// false if this has been consumed/moved-out.
/// false if this has been either moved-out or created without a shared state.
bool valid() const noexcept {
return core_ != nullptr;
}
/// Returns a reference to the result, with a reference category and const-
/// qualification equivalent to the reference category and const-qualification
/// of the receiver.
/// Returns a reference to the result value if it is ready, with a reference
/// category and const-qualification like those of the future.
///
/// If moved-from, throws FutureInvalid.
/// Does not `wait()`; see `get()` for that.
///
/// If !isReady(), throws FutureNotReady.
/// Preconditions:
///
/// If an exception has been captured, throws that exception.
/// - `valid() == true` (else throws FutureInvalid)
/// - `isReady() == true` (else throws FutureNotReady)
///
/// Postconditions:
///
/// - If an exception has been captured (i.e., if `hasException() == true`),
/// throws that exception.
/// - This call does not mutate the future's value.
/// - However calling code may mutate that value (including moving it out by
/// move-constructing or move-assigning another value from it), for
/// example, via the `&` or the `&&` overloads or via casts.
T& value() &;
T const& value() const&;
T&& value() &&;
T const&& value() const&&;
/// Returns a reference to the try of the result. Throws as for value if
/// future is not valid.
/// Returns a reference to the result's Try if it is ready, with a reference
/// category and const-qualification like those of the future.
///
/// Does not `wait()`; see `get()` for that.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
/// - `isReady() == true` (else throws FutureNotReady)
///
/// Postconditions:
///
/// - This call does not mutate the future's result.
/// - However calling code may mutate that result (including moving it out by
/// move-constructing or move-assigning another result from it), for
/// example, via the `&` or the `&&` overloads or via casts.
Try<T>& result() &;
Try<T> const& result() const&;
Try<T>&& result() &&;
Try<T> const&& result() const&&;
/** True when the result (or exception) is ready. */
/// True when the result (or exception) is ready; see value(), result(), etc.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
bool isReady() const;
/// sugar for getTry().hasValue()
/// True if the result is an exception (not a value) on a future for which
/// isReady returns true.
///
/// Equivalent to result().hasValue()
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
/// - `isReady() == true` (else throws FutureNotReady)
bool hasValue() const;
/// sugar for getTry().hasException()
/// True if the result is ready (`isReady() == true`) and the result is an
/// exception (not a value).
///
/// Equivalent to result().hasException()
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
/// - `isReady() == true` (else throws FutureNotReady)
bool hasException() const;
/// If the promise has been fulfilled, return an Optional with the Try<T>.
/// Otherwise return an empty Optional.
/// Note that this moves the Try<T> out.
/// Returns either an Optional holding the result or an empty Optional
/// depending on whether or not (respectively) the promise has been
/// fulfilled (i.e., `isReady() == true`).
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true` (note however that this moves-out the result when
/// it returns a populated `Try<T>`, which effects any subsequent use of
/// that result, e.g., `poll()`, `result()`, `value()`, `get()`, etc.)
Optional<Try<T>> poll();
/// This is not the method you're looking for.
......@@ -181,22 +247,84 @@ class FutureBase {
template <class F>
void setCallback_(F&& func);
/// Provides a threadsafe back-channel so the consumer's thread can send an
/// interrupt-object to the producer's thread.
///
/// If the promise-holder registers an interrupt-handler and consumer thread
/// raises an interrupt early enough (details below), the promise-holder
/// will typically halt its work, fulfilling the future with an exception
/// or some special non-exception value.
///
/// However this interrupt request is voluntary, asynchronous, & advisory:
///
/// - Voluntary: the producer will see the interrupt only if the producer uses
/// a `Promise` object and registers an interrupt-handler;
/// see `Promise::setInterruptHandler()`.
/// - Asynchronous: the producer will see the interrupt only if `raise()` is
/// called before (or possibly shortly after) the producer is done producing
/// its result, which is asynchronous with respect to the call to `raise()`.
/// - Advisory: the producer's interrupt-handler can do whatever it wants,
/// including ignore the interrupt or perform some action other than halting
/// its producer-work.
///
/// Guidelines:
///
/// - It is ideal if the promise-holder can both halt its work and fulfill the
/// promise early, typically with the same exception that was delivered to
/// the promise-holder in the form of an interrupt.
/// - If the promise-holder does not do this, and if it holds the promise
/// alive for a long time, then the whole continuation chain will not be
/// invoked and the whole future chain will be kept alive for that long time
/// as well.
/// - It is also ideal if the promise-holder can invalidate the promise.
/// - The promise-holder must also track whether it has set a result in the
/// interrupt handler so that it does not attempt to do so outside the
/// interrupt handler, and must track whether it has set a result in its
/// normal flow so that it does not attempt to do so in the interrupt
/// handler, since setting a result twice is an error. Because the interrupt
/// handler can be invoked in some other thread, this tracking may have to
/// be done with some form of concurrency control.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - has no visible effect if `raise()` was previously called on `this` or
/// any other Future/SemiFuture that uses the same shared state as `this`.
/// - has no visible effect if the producer never (either in the past or in
/// the future) registers an interrupt-handler.
/// - has no visible effect if the producer fulfills its promise (sets the
/// result) before (or possibly also shortly after) receiving the interrupt.
/// - otherwise the promise-holder's interrupt-handler is called, passing the
/// exception (within an `exception_wrapper`).
///
/// The specific thread used to invoke the producer's interrupt-handler (if
/// it is called at all) depends on timing:
///
/// - if the interrupt-handler is registered prior to `raise()` (or possibly
/// concurrently within the call to `raise()`), the interrupt-handler will
/// be executed using this current thread within the call to `raise()`.
/// - if the interrupt-handler is registered after `raise()` (and possibly
/// concurrently within the call to `raise()`), the interrupt-handler will
/// be executed using the producer's thread within the call to
/// `Promise::setInterruptHandler()`.
///
/// Synchronizes between `raise()` (in the consumer's thread)
/// and `Promise::setInterruptHandler()` (in the producer's thread).
void raise(exception_wrapper interrupt);
/// Raises the specified exception-interrupt.
/// See `raise(exception_wrapper)` for details.
template <class E>
void raise(E&& exception) {
raise(make_exception_wrapper<typename std::remove_reference<E>::type>(
std::forward<E>(exception)));
}
/// Raise an interrupt. If the promise holder has an interrupt
/// handler it will be called and potentially stop asynchronous work from
/// being done. This is advisory only - a promise holder may not set an
/// interrupt handler, or may do anything including ignore. But, if you know
/// your future supports this the most likely result is stopping or
/// preventing the asynchronous operation (if in time), and the promise
/// holder setting an exception on the future. (That may happen
/// asynchronously, of course.)
void raise(exception_wrapper interrupt);
/// Raises a FutureCancellation interrupt.
/// See `raise(exception_wrapper)` for details.
void cancel() {
raise(FutureCancellation());
}
......@@ -265,6 +393,10 @@ class FutureBase {
return getCore().getExecutor();
}
// Sets the Executor within the Core state object of `this`.
// Must be called either before attaching a callback or after the callback
// has already been invoked, but not concurrently with anything which might
// trigger invocation of the callback.
void setExecutor(Executor* x, int8_t priority = Executor::MID_PRI) {
getCore().setExecutor(x, priority);
}
......@@ -295,7 +427,7 @@ void convertFuture(SemiFuture<T>&& sf, Future<T>& f);
} // namespace detail
} // namespace futures
/// The interface (along with SemiFuture) for the consumer-side of a
/// The interface (along with Future) for the consumer-side of a
/// producer/consumer pair.
///
/// Future vs. SemiFuture:
......@@ -317,7 +449,7 @@ void convertFuture(SemiFuture<T>&& sf, Future<T>& f);
/// - the two styles cannot be mixed within the same future; use one or the
/// other.
///
/// SemiFuture/Future also provide a back-channel so an interruption request can
/// SemiFuture/Future also provide a back-channel so an interrupt can
/// be sent from consumer to producer; see SemiFuture/Future's `raise()`
/// and Promise's `setInterruptHandler()`.
///
......@@ -333,14 +465,25 @@ class SemiFuture : private futures::detail::FutureBase<T> {
public:
~SemiFuture();
static SemiFuture<T> makeEmpty(); // equivalent to moved-from
/// Creates/returns an invalid SemiFuture, that is, one with no shared state.
///
/// Postcondition:
///
/// - `RESULT.valid() == false`
static SemiFuture<T> makeEmpty();
// Export public interface of FutureBase
// FutureBase is inherited privately to avoid subclasses being cast to
// a FutureBase pointer
/// Type of the value that the producer, when successful, produces.
using typename Base::value_type;
/// Construct a SemiFuture from a value (perfect forwarding)
///
/// Postconditions:
///
/// - `valid() == true`
/// - `isReady() == true`
/// - `hasValue() == true`
/// - `hasException() == false`
/// - `value()`, `get()`, `result()` will return the forwarded `T`
template <
class T2 = T,
typename = typename std::enable_if<
......@@ -348,11 +491,27 @@ class SemiFuture : private futures::detail::FutureBase<T> {
!isSemiFuture<typename std::decay<T2>::type>::value>::type>
/* implicit */ SemiFuture(T2&& val) : Base(std::forward<T2>(val)) {}
/// Construct a (logical) SemiFuture-of-void.
///
/// Postconditions:
///
/// - `valid() == true`
/// - `isReady() == true`
/// - `hasValue() == true`
template <class T2 = T>
/* implicit */ SemiFuture(
typename std::enable_if<std::is_same<Unit, T2>::value>::type* p = nullptr)
: Base(p) {}
/// Construct a SemiFuture from a `T` constructed from `args`
///
/// Postconditions:
///
/// - `valid() == true`
/// - `isReady() == true`
/// - `hasValue() == true`
/// - `hasException() == false`
/// - `value()`, `get()`, `result()` will return the newly constructed `T`
template <
class... Args,
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
......@@ -381,57 +540,141 @@ class SemiFuture : private futures::detail::FutureBase<T> {
SemiFuture& operator=(SemiFuture&&) noexcept;
SemiFuture& operator=(Future<T>&&) noexcept;
/// Block until the future is fulfilled. Returns the value (moved out), or
/// throws the exception. The future must not already have a callback.
/// Blocks until the promise is fulfilled, either by value (which is returned)
/// or exception (which is thrown).
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
/// - must not have a continuation, e.g., via `.then()` or similar
///
/// Postconditions:
///
/// - `valid() == false`
T get() &&;
/// Block until the future is fulfilled, or until timed out. Returns the
/// value (moved out), or throws the exception (which might be a FutureTimeout
/// Blocks until the semifuture is fulfilled, or until `dur` elapses. Returns
/// the value (moved-out), or throws the exception (which might be a
/// FutureTimeout).
/// exception).
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
T get(Duration dur) &&;
/// Block until the future is fulfilled. Returns the Try of the value (moved
/// out).
/// Blocks until the future is fulfilled. Returns the Try of the result
/// (moved-out).
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
Try<T> getTry() &&;
/// Block until the future is fulfilled, or until timed out. Returns the
/// Try of the value (moved out) or may throw a FutureTimeout exception.
/// Blocks until the future is fulfilled, or until `dur` elapses.
/// Returns the Try of the result (moved-out), or throws FutureTimeout
/// exception.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - if returns (no exception), moves-out the Try; treat `*this` as if
/// `!valid()`.
/// - on FutureTimeout exception, `valid()` remains true.
Try<T> getTry(Duration dur) &&;
/// Block until this Future is complete. Returns a reference to this Future.
/// Blocks the caller's thread until this Future `isReady()`, i.e., until the
/// asynchronous producer has stored a result or exception.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true`
/// - `isReady() == true`
/// - `&RESULT == this`
SemiFuture<T>& wait() &;
/// Overload of wait() for rvalue Futures
/// Blocks the caller's thread until this Future `isReady()`, i.e., until the
/// asynchronous producer has stored a result or exception.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true` (but the calling code can trivially move-out `*this`
/// by assigning or constructing the result into a distinct object).
/// - `&RESULT == this`
/// - `isReady() == true`
SemiFuture<T>&& wait() &&;
/// Block until this Future is complete or until the given Duration passes.
/// Returns a reference to this Future
SemiFuture<T>& wait(Duration) &;
/// Blocks until the future is fulfilled, or `dur` elapses.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true`
/// - `&RESULT == this`
/// - `isReady()` will be indeterminate - may or may not be true
SemiFuture<T>& wait(Duration dur) &;
/// Overload of wait(Duration) for rvalue Futures
SemiFuture<T>&& wait(Duration) &&;
/// Blocks until the future is fulfilled, or `dur` elapses.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true` (but the calling code can trivially move-out `*this`
/// by assigning or constructing the result into a distinct object).
/// - `&RESULT == this`
/// - `isReady()` will be indeterminate - may or may not be true
SemiFuture<T>&& wait(Duration dur) &&;
/// Returns a Future which will call back on the other side of executor.
///
/// The ref-qualifier allows for `this` to be moved out so we
/// don't get access-after-free situations in chaining.
/// https://akrzemi1.wordpress.com/2014/06/02/ref-qualifiers/
Future<T> via(Executor* executor, int8_t priority = Executor::MID_PRI) &&;
Future<T> via(
Executor::KeepAlive<> executor,
int8_t priority = Executor::MID_PRI) &&;
/**
* Defer work to run on the consumer of the future.
* Function must take a Try as a parameter.
* This work will be run either on an executor that the caller sets on the
* SemiFuture, or inline with the call to .get().
* NB: This is a custom method because boost-blocking executors is a
* special-case for work deferral in folly. With more general boost-blocking
* support all executors would boost block and we would simply use some form
* of driveable executor here.
*/
/// Defer work to run on the consumer of the future.
/// Function must take a Try as a parameter.
/// This work will be run either on an executor that the caller sets on the
/// SemiFuture, or inline with the call to .get().
///
/// NB: This is a custom method because boost-blocking executors is a
/// special-case for work deferral in folly. With more general boost-blocking
/// support all executors would boost block and we would simply use some form
/// of driveable executor here.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
/// - `RESULT.valid() == true`
template <typename F>
SemiFuture<typename futures::detail::tryCallableResult<T, F>::value_type>
defer(F&& func) &&;
......@@ -441,9 +684,16 @@ class SemiFuture : private futures::detail::FutureBase<T> {
return std::move(*this).defer(&func);
}
/**
* Defer for functions taking a T rather than a Try<T>.
*/
/// Defer for functions taking a T rather than a Try<T>.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
/// - `RESULT.valid() == true`
template <typename F>
SemiFuture<typename futures::detail::valueCallableResult<T, F>::value_type>
deferValue(F&& func) &&;
......@@ -453,11 +703,13 @@ class SemiFuture : private futures::detail::FutureBase<T> {
return std::move(*this).deferValue(&func);
}
/// Set an error continuation for this SemiFuture. The continuation should
/// take a single argument of the type that you want to catch, and should
/// return a `T`, `Future<T>` or `SemiFuture<`T`> (see overload below).
/// For instance,
/// Set an error continuation for this SemiFuture where the continuation can
/// be called with a known exception type and returns a `T`, `Future<T>`, or
/// `SemiFuture<T>`.
///
/// Example:
///
/// ```
/// makeSemiFuture()
/// .defer([] {
/// throw std::runtime_error("oh no!");
......@@ -465,10 +717,18 @@ class SemiFuture : private futures::detail::FutureBase<T> {
/// })
/// .deferError<std::runtime_error>([] (auto const& e) {
/// LOG(INFO) << "std::runtime_error: " << e.what();
/// return -1; // or makeSemiFuture<int>(-1)
/// return -1; // or makeFuture<int>(-1) or makeSemiFuture<int>(-1)
/// });
/// Overload of deferError where continuation can be called with a known
/// exception type and returns T, Future<T> or SemiFuture<T>
/// ```
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
/// - `RESULT.valid() == true`
template <class ExceptionType, class F>
SemiFuture<T> deferError(F&& func) &&;
......@@ -477,8 +737,32 @@ class SemiFuture : private futures::detail::FutureBase<T> {
return std::move(*this).template deferError<ExceptionType>(&func);
}
/// Overload of deferError where continuation can be called with
/// exception_wrapper&& and returns T, Future<T> or SemiFuture<T>
/// Set an error continuation for this SemiFuture where the continuation can
/// be called with `exception_wrapper&&` and returns a `T`, `Future<T>`, or
/// `SemiFuture<T>`.
///
/// Example:
///
/// ```
/// makeSemiFuture()
/// .defer([] {
/// throw std::runtime_error("oh no!");
/// return 42;
/// })
/// .deferError([] (exception_wrapper&& e) {
/// LOG(INFO) << e.what();
/// return -1; // or makeFuture<int>(-1) or makeSemiFuture<int>(-1)
/// });
/// ```
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
/// - `RESULT.valid() == true`
template <class F>
SemiFuture<T> deferError(F&& func) &&;
......@@ -500,10 +784,22 @@ class SemiFuture : private futures::detail::FutureBase<T> {
/// Return a future that completes inline, as if the future had no executor.
/// Intended for porting legacy code without behavioural change, and for rare
/// cases where this is really the intended behaviour.
/// Returns a future that completes inline, as if the future had no executor.
/// Intended for porting legacy code without behavioral change, and for rare
/// cases where this is really the intended behavior.
/// Future is unsafe in the sense that the executor it completes on is
/// non-deterministic in the standard case.
/// For new code, or to update code that temporarily uses this, please
/// use via and pass a meaningful executor.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
/// - `RESULT.valid() == true`
Future<T> toUnsafeFuture() &&;
#if FOLLY_HAS_COROUTINES
......@@ -562,6 +858,7 @@ class SemiFuture : private futures::detail::FutureBase<T> {
explicit SemiFuture(futures::detail::EmptyConstruct) noexcept
: Base(futures::detail::EmptyConstruct{}) {}
// Throws FutureInvalid if !this->core_
DeferredExecutor* getDeferredExecutor() const;
static void releaseDeferredExecutor(corePtr core);
......@@ -596,7 +893,7 @@ std::pair<Promise<T>, SemiFuture<T>> makePromiseContract() {
/// - the two styles cannot be mixed within the same future; use one or the
/// other.
///
/// SemiFuture/Future also provide a back-channel so an interruption request can
/// SemiFuture/Future also provide a back-channel so an interrupt can
/// be sent from consumer to producer; see SemiFuture/Future's `raise()`
/// and Promise's `setInterruptHandler()`.
///
......@@ -608,12 +905,17 @@ class Future : private futures::detail::FutureBase<T> {
using Base = futures::detail::FutureBase<T>;
public:
// Export public interface of FutureBase
// FutureBase is inherited privately to avoid subclasses being cast to
// a FutureBase pointer
/// Type of the value that the producer, when successful, produces.
using typename Base::value_type;
/// Construct a Future from a value (perfect forwarding)
///
/// Postconditions:
///
/// - `valid() == true`
/// - `isReady() == true`
/// - `hasValue() == true`
/// - `value()`, `get()`, `result()` will return the forwarded `T`
template <
class T2 = T,
typename = typename std::enable_if<
......@@ -621,11 +923,27 @@ class Future : private futures::detail::FutureBase<T> {
!isSemiFuture<typename std::decay<T2>::type>::value>::type>
/* implicit */ Future(T2&& val) : Base(std::forward<T2>(val)) {}
/// Construct a (logical) Future-of-void.
///
/// Postconditions:
///
/// - `valid() == true`
/// - `isReady() == true`
/// - `hasValue() == true`
template <class T2 = T>
/* implicit */ Future(
typename std::enable_if<std::is_same<Unit, T2>::value>::type* p = nullptr)
: Base(p) {}
/// Construct a Future from a `T` constructed from `args`
///
/// Postconditions:
///
/// - `valid() == true`
/// - `isReady() == true`
/// - `hasValue() == true`
/// - `hasException() == false`
/// - `value()`, `get()`, `result()` will return the newly constructed `T`
template <
class... Args,
typename std::enable_if<std::is_constructible<T, Args&&...>::value, int>::
......@@ -673,7 +991,12 @@ class Future : private futures::detail::FutureBase<T> {
using Base::value;
using Base::result;
static Future<T> makeEmpty(); // equivalent to moved-from
/// Creates/returns an invalid Future, that is, one with no shared state.
///
/// Postcondition:
///
/// - `RESULT.valid() == false`
static Future<T> makeEmpty();
// not copyable
Future& operator=(Future const&) = delete;
......@@ -681,13 +1004,18 @@ class Future : private futures::detail::FutureBase<T> {
// movable
Future& operator=(Future&&) noexcept;
/// Call e->drive() repeatedly until the future is fulfilled. Examples
/// of DrivableExecutor include EventBase and ManualExecutor. Returns the
/// value (moved out), or throws the exception.
/// Call e->drive() repeatedly until the future is fulfilled.
///
/// Examples of DrivableExecutor include EventBase and ManualExecutor.
///
/// Returns the fulfilled value (moved-out) or throws the fulfilled exception.
T getVia(DrivableExecutor* e);
/// getVia but will wait only until timed out. Returns the
/// Try of the value (moved out) or may throw a FutureTimeout exception.
/// Call e->drive() repeatedly until the future is fulfilled, or `dur`
/// elapses.
///
/// Returns the fulfilled value (moved-out), throws the fulfilled exception,
/// or on timeout throws FutureTimeout.
T getVia(TimedDrivableExecutor* e, Duration dur);
/// Call e->drive() repeatedly until the future is fulfilled. Examples
......@@ -695,8 +1023,8 @@ class Future : private futures::detail::FutureBase<T> {
/// reference to the Try of the value.
Try<T>& getTryVia(DrivableExecutor* e);
/// getTryVia but will wait only until timed out. Returns the
/// Try of the value (moved out) or may throw a FutureTimeout exception.
/// getTryVia but will wait only until `dur` elapses. Returns the
/// Try of the value (moved-out) or may throw a FutureTimeout exception.
Try<T>& getTryVia(TimedDrivableExecutor* e, Duration dur);
/// Unwraps the case of a Future<Future<T>> instance, and returns a simple
......@@ -708,44 +1036,59 @@ class Future : private futures::detail::FutureBase<T> {
/// Returns a Future which will call back on the other side of executor.
///
/// The ref-qualifier allows for `this` to be moved out so we
/// don't get access-after-free situations in chaining.
/// https://akrzemi1.wordpress.com/2014/06/02/ref-qualifiers/
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
/// - `RESULT.valid() == true`
Future<T> via(Executor* executor, int8_t priority = Executor::MID_PRI) &&;
Future<T> via(
Executor::KeepAlive<> executor,
int8_t priority = Executor::MID_PRI) &&;
/// This variant creates a new future, where the ref-qualifier && version
/// moves `this` out. This one is less efficient but avoids confusing users
/// when "return f.via(x);" fails.
/// Returns a Future which will call back on the other side of executor.
///
/// When practical, use the rvalue-qualified overload instead - it's faster.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true`
/// - `RESULT.valid() == true`
/// - when `this` gets fulfilled, it automatically fulfills RESULT
Future<T> via(Executor* executor, int8_t priority = Executor::MID_PRI) &;
Future<T> via(
Executor::KeepAlive<> executor,
int8_t priority = Executor::MID_PRI) &;
/** When this Future has completed, execute func which is a function that
takes one of:
(const) Try<T>&&
(const) Try<T>&
(const) Try<T>
(const) T&&
(const) T&
(const) T
(void)
Func shall return either another Future or a value.
A Future for the return type of func is returned.
Future<string> f2 = f1.then([](Try<T>&&) { return string("foo"); });
The Future given to the functor is ready, and the functor may call
value(), which may rethrow if this has captured an exception. If func
throws, the exception will be captured in the Future that is returned.
*/
/// When this Future has completed, execute func which is a function that
/// can be called with either `T&&` or `Try<T>&&`.
///
/// Func shall return either another Future or a value.
///
/// A Future for the return type of func is returned.
///
/// ```
/// Future<string> f2 = f1.then([](Try<T>&&) { return string("foo"); });
/// ```
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <typename F, typename R = futures::detail::callableResult<T, F>>
typename R::Return then(F&& func) {
return this->template thenImplementation<F, R>(
......@@ -762,6 +1105,16 @@ class Future : private futures::detail::FutureBase<T> {
/// This is just sugar for
///
/// f1.then(std::bind(&Worker::doWork, w));
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <typename R, typename Caller, typename... Args>
Future<typename isFuture<R>::Inner> then(
R (Caller::*func)(Args...),
......@@ -779,6 +1132,16 @@ class Future : private futures::detail::FutureBase<T> {
///
/// In the former both b and c execute via x. In the latter, only b executes
/// via x, and c executes via the same executor (if any) that f had.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class Executor, class Arg, class... Args>
auto then(Executor* x, Arg&& arg, Args&&... args) {
auto oldX = this->getExecutor();
......@@ -787,33 +1150,56 @@ class Future : private futures::detail::FutureBase<T> {
.via(oldX);
}
/** When this Future has completed, execute func which is a function that
takes one of:
(const) Try<T>&&
(const) Try<T>&
(const) Try<T>
Func shall return either another Future or a value.
A Future for the return type of func is returned.
Future<string> f2 = f1.then([](Try<T>&&) { return string("foo"); });
The Future given to the functor is ready, and the functor may call
value(), which may rethrow if this has captured an exception. If func
throws, the exception will be captured in the Future that is returned.
*/
/// When this Future has completed, execute func which is a function that
/// can be called with `Try<T>&&` (often a lambda with parameter type
/// `auto&&` or `auto`).
///
/// Func shall return either another Future or a value.
///
/// A Future for the return type of func is returned.
///
/// ```
/// Future<string> f2 = std::move(f1).thenTry([](auto&& t) {
/// ...
/// return string("foo");
/// });
/// ```
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
/// - `RESULT.valid() == true`
template <typename F>
Future<typename futures::detail::tryCallableResult<T, F>::value_type> thenTry(
F&& func) &&;
/** When this Future has completed, execute func which is a function that
takes one of:
(const) T&&
(const) T&
(const) T
(void)
*/
/// When this Future has completed, execute func which is a function that
/// can be called with `T&&` (often a lambda with parameter type
/// `auto&&` or `auto`).
///
/// Func shall return either another Future or a value.
///
/// A Future for the return type of func is returned.
///
/// ```
/// Future<string> f2 = f1.thenValue([](auto&& v) {
/// ...
/// return string("foo");
/// });
/// ```
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
/// - `RESULT.valid() == true`
template <typename F>
Future<typename futures::detail::valueCallableResult<T, F>::value_type>
thenValue(F&& func) &&;
......@@ -823,11 +1209,13 @@ class Future : private futures::detail::FutureBase<T> {
return std::move(*this).thenValue(&func);
}
/// Set an error callback for this Future. The callback should take a
/// single argument of the type that you want to catch, and should return
/// T, SemiFuture<T> or Future<T>
/// (see overload below). For instance,
/// Set an error continuation for this Future where the continuation can
/// be called with a known exception type and returns a `T`, `Future<T>`, or
/// `SemiFuture<T>`.
///
/// Example:
///
/// ```
/// makeFuture()
/// .thenTry([] {
/// throw std::runtime_error("oh no!");
......@@ -835,10 +1223,18 @@ class Future : private futures::detail::FutureBase<T> {
/// })
/// .thenError<std::runtime_error>([] (auto const& e) {
/// LOG(INFO) << "std::runtime_error: " << e.what();
/// return -1; // or makeSemiFuture<int>(-1)
/// return -1; // or makeFuture<int>(-1) or makeSemiFuture<int>(-1)
/// });
/// Overload of thenError where continuation can be called with a known
/// exception type and returns T or Future<T>
/// ```
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
/// - `RESULT.valid() == true`
template <class ExceptionType, class F>
Future<T> thenError(F&& func) &&;
......@@ -847,8 +1243,32 @@ class Future : private futures::detail::FutureBase<T> {
return std::move(*this).template thenError<ExceptionType>(&func);
}
/// Overload of thenError where continuation can be called with
/// exception_wrapper&& and returns T or Future<T>
/// Set an error continuation for this Future where the continuation can
/// be called with `exception_wrapper&&` and returns a `T`, `Future<T>`, or
/// `SemiFuture<T>`.
///
/// Example:
///
/// ```
/// makeFuture()
/// .thenTry([] {
/// throw std::runtime_error("oh no!");
/// return 42;
/// })
/// .thenError([] (exception_wrapper&& e) {
/// LOG(INFO) << e.what();
/// return -1; // or makeFuture<int>(-1) or makeSemiFuture<int>(-1)
/// });
/// ```
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
/// - `RESULT.valid() == true`
template <class F>
Future<T> thenError(F&& func) &&;
......@@ -860,16 +1280,36 @@ class Future : private futures::detail::FutureBase<T> {
/// Convenience method for ignoring the value and creating a Future<Unit>.
/// Exceptions still propagate.
/// This function is identical to .unit().
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
Future<Unit> then();
/// Convenience method for ignoring the value and creating a Future<Unit>.
/// Exceptions still propagate.
/// This function is identical to parameterless .then().
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
Future<Unit> unit() {
return then();
}
/// Set an error callback for this Future. The callback should take a single
/// Set an error continuation for this Future. The continuation should take an
/// argument of the type that you want to catch, and should return a value of
/// the same type as this Future, or a Future of that type (see overload
/// below). For instance,
......@@ -883,6 +1323,16 @@ class Future : private futures::detail::FutureBase<T> {
/// LOG(INFO) << "std::runtime_error: " << e.what();
/// return -1; // or makeFuture<int>(-1)
/// });
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class F>
typename std::enable_if<
!is_invocable<F, exception_wrapper>::value &&
......@@ -890,7 +1340,17 @@ class Future : private futures::detail::FutureBase<T> {
Future<T>>::type
onError(F&& func);
/// Overload of onError where the error callback returns a Future<T>
/// Overload of onError where the error continuation returns a Future<T>
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class F>
typename std::enable_if<
!is_invocable<F, exception_wrapper>::value &&
......@@ -899,6 +1359,16 @@ class Future : private futures::detail::FutureBase<T> {
onError(F&& func);
/// Overload of onError that takes exception_wrapper and returns Future<T>
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class F>
typename std::enable_if<
is_invocable<F, exception_wrapper>::value &&
......@@ -907,6 +1377,16 @@ class Future : private futures::detail::FutureBase<T> {
onError(F&& func);
/// Overload of onError that takes exception_wrapper and returns T
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class F>
typename std::enable_if<
is_invocable<F, exception_wrapper>::value &&
......@@ -918,37 +1398,90 @@ class Future : private futures::detail::FutureBase<T> {
/// the value/exception is passed through to the resulting Future.
/// func shouldn't throw, but if it does it will be captured and propagated,
/// and discard any value/exception that this Future has obtained.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class F>
Future<T> ensure(F&& func);
/// Like onError, but for timeouts. example:
///
/// ```
/// Future<int> f = makeFuture<int>(42)
/// .delayed(long_time)
/// .onTimeout(short_time,
/// []() -> int{ return -1; });
/// [] { return -1; });
/// ```
///
/// or perhaps
///
/// ```
/// Future<int> f = makeFuture<int>(42)
/// .delayed(long_time)
/// .onTimeout(short_time,
/// []() { return makeFuture<int>(some_exception); });
/// [] { return makeFuture<int>(some_exception); });
/// ```
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class F>
Future<T> onTimeout(Duration, F&& func, Timekeeper* = nullptr);
/// Throw FutureTimeout if this Future does not complete within the given
/// duration from now. The optional Timekeeper is as with futures::sleep().
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
Future<T> within(Duration, Timekeeper* = nullptr);
/// Throw the given exception if this Future does not complete within the
/// given duration from now. The optional Timekeeper is as with
/// futures::sleep().
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class E>
Future<T> within(Duration, E exception, Timekeeper* = nullptr);
/// Delay the completion of this Future for at least this duration from
/// now. The optional Timekeeper is as with futures::sleep().
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
/// - `RESULT.valid() == true`
Future<T> delayed(Duration, Timekeeper* = nullptr) &&;
/// Delay the completion of this Future for at least this duration from
......@@ -957,45 +1490,145 @@ class Future : private futures::detail::FutureBase<T> {
/// WARNING: Returned future may complete on Timekeeper thread.
Future<T> delayedUnsafe(Duration, Timekeeper* = nullptr);
/// Block until the future is fulfilled. Returns the value (moved out), or
/// throws the exception. The future must not already have a callback.
/// Blocks until the future is fulfilled. Returns the value (moved-out), or
/// throws the exception. The future must not already have a continuation.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
T get();
/// Block until the future is fulfilled, or until timed out. Returns the
/// value (moved out), or throws the exception (which might be a FutureTimeout
/// Blocks until the future is fulfilled, or until `dur` elapses. Returns the
/// value (moved-out), or throws the exception (which might be a FutureTimeout
/// exception).
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == false`
T get(Duration dur);
/** A reference to the Try of the value */
/// A reference to the Try of the value
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
/// - `isReady() == true` (else throws FutureNotReady)
Try<T>& getTry();
/// Block until this Future is complete. Returns a reference to this Future.
/// Blocks until this Future is complete.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true`
/// - `&RESULT == this`
/// - `isReady() == true`
Future<T>& wait() &;
/// Overload of wait() for rvalue Futures
/// Blocks until this Future is complete.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true` (but the calling code can trivially move-out `*this`
/// by assigning or constructing the result into a distinct object).
/// - `&RESULT == this`
/// - `isReady() == true`
Future<T>&& wait() &&;
/// Block until this Future is complete or until the given Duration passes.
/// Returns a reference to this Future
Future<T>& wait(Duration) &;
/// Blocks until this Future is complete, or `dur` elapses.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true` (so you may call `wait(...)` repeatedly)
/// - `&RESULT == this`
/// - `isReady()` will be indeterminate - may or may not be true
Future<T>& wait(Duration dur) &;
/// Overload of wait(Duration) for rvalue Futures
Future<T>&& wait(Duration) &&;
/// Blocks until this Future is complete or until `dur` passes.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true` (but the calling code can trivially move-out `*this`
/// by assigning or constructing the result into a distinct object).
/// - `&RESULT == this`
/// - `isReady()` will be indeterminate - may or may not be true
Future<T>&& wait(Duration dur) &&;
/// Call e->drive() repeatedly until the future is fulfilled. Examples
/// of DrivableExecutor include EventBase and ManualExecutor. Returns a
/// reference to this Future so that you can chain calls if desired.
/// value (moved out), or throws the exception.
/// value (moved-out), or throws the exception.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true` (does not move-out `*this`)
/// - `&RESULT == this`
Future<T>& waitVia(DrivableExecutor* e) &;
/// Overload of waitVia() for rvalue Futures
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true` (but the calling code can trivially move-out `*this`
/// by assigning or constructing the result into a distinct object).
/// - `&RESULT == this`
Future<T>&& waitVia(DrivableExecutor* e) &&;
/// As waitVia but may return early after dur passes.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true` (does not move-out `*this`)
/// - `&RESULT == this`
Future<T>& waitVia(TimedDrivableExecutor* e, Duration dur) &;
/// Overload of waitVia() for rvalue Futures
/// As waitVia but may return early after dur passes.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - `valid() == true` (but the calling code can trivially move-out `*this`
/// by assigning or constructing the result into a distinct object).
/// - `&RESULT == this`
Future<T>&& waitVia(TimedDrivableExecutor* e, Duration dur) &&;
/// If the value in this Future is equal to the given Future, when they have
......@@ -1007,22 +1640,56 @@ class Future : private futures::detail::FutureBase<T> {
/// predicate behaves like std::function<bool(T const&)>
/// If the predicate does not obtain with the value, the result
/// is a folly::FuturePredicateDoesNotObtain exception
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class F>
Future<T> filter(F&& predicate);
/// Like reduce, but works on a Future<std::vector<T / Try<T>>>, for example
/// the result of collect or collectAll
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class I, class F>
Future<I> reduce(I&& initial, F&& func);
/// Create a Future chain from a sequence of callbacks. i.e.
/// Create a Future chain from a sequence of continuations. i.e.
///
/// ```
/// f.then(a).then(b).then(c)
/// ```
///
/// where f is a Future<A> and the result of the chain is a Future<D>
/// becomes
///
/// ```
/// f.thenMulti(a, b, c);
/// ```
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class Callback, class... Callbacks>
auto thenMulti(Callback&& fn, Callbacks&&... fns) {
// thenMulti with two callbacks is just then(a).thenMulti(b, ...)
......@@ -1030,6 +1697,17 @@ class Future : private futures::detail::FutureBase<T> {
.thenMulti(std::forward<Callbacks>(fns)...);
}
/// Create a Future chain from a sequence of callbacks.
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class Callback>
auto thenMulti(Callback&& fn) {
// thenMulti with one callback is just a then
......@@ -1038,12 +1716,26 @@ class Future : private futures::detail::FutureBase<T> {
/// Create a Future chain from a sequence of callbacks. i.e.
///
/// ```
/// f.via(executor).then(a).then(b).then(c).via(oldExecutor)
/// ```
///
/// where f is a Future<A> and the result of the chain is a Future<D>
/// becomes
///
/// ```
/// f.thenMultiWithExecutor(executor, a, b, c);
/// ```
///
/// Preconditions:
///
/// - `valid() == true` (else throws FutureInvalid)
///
/// Postconditions:
///
/// - Calling code should act as if `valid() == false`,
/// i.e., as if `*this` was moved into RESULT.
/// - `RESULT.valid() == true`
template <class Callback, class... Callbacks>
auto thenMultiWithExecutor(Executor* x, Callback&& fn, Callbacks&&... fns) {
// thenMultiExecutor with two callbacks is
......@@ -1061,8 +1753,13 @@ class Future : private futures::detail::FutureBase<T> {
return then(x, std::forward<Callback>(fn));
}
// Convert this Future to a SemiFuture to safely export from a library
// without exposing a continuation interface
/// Moves-out `*this`, creating/returning a corresponding SemiFuture.
/// Result will behave like `*this` except result won't have an Executor.
///
/// Postconditions:
///
/// - `RESULT.valid() ==` the original value of `this->valid()`
/// - RESULT will not have an Executor regardless of whether `*this` had one
SemiFuture<T> semi() {
return SemiFuture<T>{std::move(*this)};
}
......@@ -1090,8 +1787,7 @@ class Future : private futures::detail::FutureBase<T> {
template <class T2>
friend Future<T2> makeFuture(Try<T2>&&);
/// Repeat the given future (i.e., the computation it contains)
/// n times.
/// Repeat the given future (i.e., the computation it contains) n times.
///
/// thunk behaves like std::function<Future<T2>(void)>
template <class F>
......@@ -1127,7 +1823,7 @@ class Future : private futures::detail::FutureBase<T> {
/// Delays that are used to trigger timeouts of async operations), then you
/// can and should cancel them to reclaim resources.
///
/// Users will typically get one of these via Future::sleep(Duration) or
/// Users will typically get one of these via Future::sleep(Duration dur) or
/// use them implicitly behind the scenes by passing a timeout to some Future
/// operation.
///
......@@ -1153,12 +1849,12 @@ class Timekeeper {
/// elapsed time. Exceptional errors can happen but they must be
/// exceptional. Use the steady (monotonic) clock.
///
/// You may cancel this Future to reclaim resources.
/// The consumer thread may cancel this Future to reclaim resources.
///
/// This future probably completes on the timer thread. You should almost
/// certainly follow it with a via() call or the accuracy of other timers
/// will suffer.
virtual Future<Unit> after(Duration) = 0;
virtual Future<Unit> after(Duration dur) = 0;
/// Returns a future that will complete at the requested time.
///
......
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