diff options
author | Mathias Stearn <mathias@10gen.com> | 2019-03-21 19:01:26 -0400 |
---|---|---|
committer | Mathias Stearn <mathias@10gen.com> | 2019-04-11 18:38:43 -0400 |
commit | f0f894395d2676c5b93fc99a978970f0ffce8127 (patch) | |
tree | 14621a1b7d22728a4e644288019de848b9d87e28 /src/mongo | |
parent | 342e6f115ee06cfa226a3c3006f87d9bd50bda8e (diff) | |
download | mongo-f0f894395d2676c5b93fc99a978970f0ffce8127.tar.gz |
SERVER-36359 Introduce SemiFuture<T> and ExecutorFuture<T>
Diffstat (limited to 'src/mongo')
25 files changed, 1435 insertions, 776 deletions
diff --git a/src/mongo/client/remote_command_targeter.h b/src/mongo/client/remote_command_targeter.h index df4caa2c4d3..9adc553c2f0 100644 --- a/src/mongo/client/remote_command_targeter.h +++ b/src/mongo/client/remote_command_targeter.h @@ -78,8 +78,8 @@ public: * DEPRECATED. Prefer findHost(OperationContext*, const ReadPreferenceSetting&), whenever * an OperationContext is available. */ - virtual SharedSemiFuture<HostAndPort> findHostWithMaxWait(const ReadPreferenceSetting& readPref, - Milliseconds maxWait) = 0; + virtual SemiFuture<HostAndPort> findHostWithMaxWait(const ReadPreferenceSetting& readPref, + Milliseconds maxWait) = 0; /** * Reports to the targeter that a 'status' indicating a not master error was received when diff --git a/src/mongo/client/remote_command_targeter_factory_mock.cpp b/src/mongo/client/remote_command_targeter_factory_mock.cpp index 3b06f70a83e..b2aef422956 100644 --- a/src/mongo/client/remote_command_targeter_factory_mock.cpp +++ b/src/mongo/client/remote_command_targeter_factory_mock.cpp @@ -53,8 +53,8 @@ public: return _mock->findHost(opCtx, readPref); } - SharedSemiFuture<HostAndPort> findHostWithMaxWait(const ReadPreferenceSetting& readPref, - Milliseconds maxWait) override { + SemiFuture<HostAndPort> findHostWithMaxWait(const ReadPreferenceSetting& readPref, + Milliseconds maxWait) override { return _mock->findHostWithMaxWait(readPref, maxWait); } diff --git a/src/mongo/client/remote_command_targeter_mock.cpp b/src/mongo/client/remote_command_targeter_mock.cpp index 705f46317b1..4cec97a246d 100644 --- a/src/mongo/client/remote_command_targeter_mock.cpp +++ b/src/mongo/client/remote_command_targeter_mock.cpp @@ -58,7 +58,7 @@ StatusWith<HostAndPort> RemoteCommandTargeterMock::findHost(OperationContext* op return _findHostReturnValue; } -SharedSemiFuture<HostAndPort> RemoteCommandTargeterMock::findHostWithMaxWait( +SemiFuture<HostAndPort> RemoteCommandTargeterMock::findHostWithMaxWait( const ReadPreferenceSetting& readPref, Milliseconds maxTime) { return _findHostReturnValue; diff --git a/src/mongo/client/remote_command_targeter_mock.h b/src/mongo/client/remote_command_targeter_mock.h index 28878f266ac..89c7245d62b 100644 --- a/src/mongo/client/remote_command_targeter_mock.h +++ b/src/mongo/client/remote_command_targeter_mock.h @@ -55,8 +55,8 @@ public: * Returns the return value last set by setFindHostReturnValue. * Returns ErrorCodes::InternalError if setFindHostReturnValue was never called. */ - SharedSemiFuture<HostAndPort> findHostWithMaxWait(const ReadPreferenceSetting& readPref, - Milliseconds maxWait) override; + SemiFuture<HostAndPort> findHostWithMaxWait(const ReadPreferenceSetting& readPref, + Milliseconds maxWait) override; StatusWith<HostAndPort> findHost(OperationContext* opCtx, const ReadPreferenceSetting& readPref) override; diff --git a/src/mongo/client/remote_command_targeter_rs.cpp b/src/mongo/client/remote_command_targeter_rs.cpp index 25a242c9042..f4f3b86e8b6 100644 --- a/src/mongo/client/remote_command_targeter_rs.cpp +++ b/src/mongo/client/remote_command_targeter_rs.cpp @@ -61,7 +61,7 @@ ConnectionString RemoteCommandTargeterRS::connectionString() { return uassertStatusOK(ConnectionString::parse(_rsMonitor->getServerAddress())); } -SharedSemiFuture<HostAndPort> RemoteCommandTargeterRS::findHostWithMaxWait( +SemiFuture<HostAndPort> RemoteCommandTargeterRS::findHostWithMaxWait( const ReadPreferenceSetting& readPref, Milliseconds maxWait) { return _rsMonitor->getHostOrRefresh(readPref, maxWait); } diff --git a/src/mongo/client/remote_command_targeter_rs.h b/src/mongo/client/remote_command_targeter_rs.h index 254e6dbb402..fae6225318b 100644 --- a/src/mongo/client/remote_command_targeter_rs.h +++ b/src/mongo/client/remote_command_targeter_rs.h @@ -56,8 +56,8 @@ public: StatusWith<HostAndPort> findHost(OperationContext* opCtx, const ReadPreferenceSetting& readPref) override; - SharedSemiFuture<HostAndPort> findHostWithMaxWait(const ReadPreferenceSetting& readPref, - Milliseconds maxWait) override; + SemiFuture<HostAndPort> findHostWithMaxWait(const ReadPreferenceSetting& readPref, + Milliseconds maxWait) override; void markHostNotMaster(const HostAndPort& host, const Status& status) override; diff --git a/src/mongo/client/remote_command_targeter_standalone.cpp b/src/mongo/client/remote_command_targeter_standalone.cpp index d7f32eb318c..aede6abb142 100644 --- a/src/mongo/client/remote_command_targeter_standalone.cpp +++ b/src/mongo/client/remote_command_targeter_standalone.cpp @@ -43,7 +43,7 @@ ConnectionString RemoteCommandTargeterStandalone::connectionString() { return ConnectionString(_hostAndPort); } -SharedSemiFuture<HostAndPort> RemoteCommandTargeterStandalone::findHostWithMaxWait( +SemiFuture<HostAndPort> RemoteCommandTargeterStandalone::findHostWithMaxWait( const ReadPreferenceSetting& readPref, Milliseconds maxWait) { return _hostAndPort; } diff --git a/src/mongo/client/remote_command_targeter_standalone.h b/src/mongo/client/remote_command_targeter_standalone.h index 176da90caae..22a07642274 100644 --- a/src/mongo/client/remote_command_targeter_standalone.h +++ b/src/mongo/client/remote_command_targeter_standalone.h @@ -47,8 +47,8 @@ public: StatusWith<HostAndPort> findHost(OperationContext* opCtx, const ReadPreferenceSetting& readPref) override; - SharedSemiFuture<HostAndPort> findHostWithMaxWait(const ReadPreferenceSetting& readPref, - Milliseconds maxWait) override; + SemiFuture<HostAndPort> findHostWithMaxWait(const ReadPreferenceSetting& readPref, + Milliseconds maxWait) override; void markHostNotMaster(const HostAndPort& host, const Status& status) override; diff --git a/src/mongo/client/replica_set_monitor.cpp b/src/mongo/client/replica_set_monitor.cpp index e09711d18c2..6a34b09ec7d 100644 --- a/src/mongo/client/replica_set_monitor.cpp +++ b/src/mongo/client/replica_set_monitor.cpp @@ -273,8 +273,8 @@ void ReplicaSetMonitor::_doScheduledRefresh(const CallbackHandle& currentHandle) _scheduleRefresh(_state->now() + period, lk); } -SharedSemiFuture<HostAndPort> ReplicaSetMonitor::getHostOrRefresh( - const ReadPreferenceSetting& criteria, Milliseconds maxWait) { +SemiFuture<HostAndPort> ReplicaSetMonitor::getHostOrRefresh(const ReadPreferenceSetting& criteria, + Milliseconds maxWait) { if (_isRemovedFromManager.load()) { return Status(ErrorCodes::ReplicaSetMonitorRemoved, str::stream() << "ReplicaSetMonitor for set " << getName() << " is removed"); @@ -309,7 +309,7 @@ SharedSemiFuture<HostAndPort> ReplicaSetMonitor::getHostOrRefresh( _scheduleRefresh(_state->now() + kExpeditedRefreshPeriod, lk); } - return std::move(pf.future).share(); + return std::move(pf.future).semi(); } HostAndPort ReplicaSetMonitor::getMasterOrUassert() { diff --git a/src/mongo/client/replica_set_monitor.h b/src/mongo/client/replica_set_monitor.h index e0fa492ac9d..5256d5cc004 100644 --- a/src/mongo/client/replica_set_monitor.h +++ b/src/mongo/client/replica_set_monitor.h @@ -93,8 +93,8 @@ public: * Known errors are: * FailedToSatisfyReadPreference, if node cannot be found, which matches the read preference. */ - SharedSemiFuture<HostAndPort> getHostOrRefresh(const ReadPreferenceSetting& readPref, - Milliseconds maxWait = kDefaultFindHostTimeout); + SemiFuture<HostAndPort> getHostOrRefresh(const ReadPreferenceSetting& readPref, + Milliseconds maxWait = kDefaultFindHostTimeout); /** * Returns the host we think is the current master or uasserts. diff --git a/src/mongo/executor/thread_pool_task_executor.cpp b/src/mongo/executor/thread_pool_task_executor.cpp index 9496b88d21f..e067fc43203 100644 --- a/src/mongo/executor/thread_pool_task_executor.cpp +++ b/src/mongo/executor/thread_pool_task_executor.cpp @@ -598,7 +598,7 @@ void ThreadPoolTaskExecutor::scheduleIntoPool_inlock(WorkQueue* fromQueue, if (MONGO_FAIL_POINT(scheduleIntoPoolSpinsUntilThreadPoolShutsDown)) { scheduleIntoPoolSpinsUntilThreadPoolShutsDown.setMode(FailPoint::off); - auto checkStatus = [&] { return _pool->execute([] {}).getNoThrow(); }; + auto checkStatus = [&] { return ExecutorFuture(_pool).then([] {}).getNoThrow(); }; while (!ErrorCodes::isCancelationError(checkStatus().code())) { sleepmillis(100); } diff --git a/src/mongo/executor/thread_pool_task_executor.h b/src/mongo/executor/thread_pool_task_executor.h index 74a0d8bf46d..099746c8e13 100644 --- a/src/mongo/executor/thread_pool_task_executor.h +++ b/src/mongo/executor/thread_pool_task_executor.h @@ -186,7 +186,7 @@ private: std::shared_ptr<NetworkInterface> _net; // The thread pool that executes scheduled work items. - std::unique_ptr<ThreadPoolInterface> _pool; + std::shared_ptr<ThreadPoolInterface> _pool; // Mutex guarding all remaining fields. mutable stdx::mutex _mutex; diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript index dc81899bb93..00f125195bd 100644 --- a/src/mongo/util/SConscript +++ b/src/mongo/util/SConscript @@ -728,9 +728,10 @@ env.CppUnitTest( target='future_test', source=[ # This list of targets is reverse speed order, to facilitate better ninja performance. - 'future_test_future_move_only.cpp', - 'future_test_future_int.cpp', 'future_test_future_void.cpp', + 'future_test_future_int.cpp', + 'future_test_future_move_only.cpp', + 'future_test_executor_future.cpp', 'future_test_edge_cases.cpp', 'future_test_promise_int.cpp', 'future_test_promise_void.cpp', diff --git a/src/mongo/util/future.h b/src/mongo/util/future.h index 0cb0f7947f8..9cbbf114553 100644 --- a/src/mongo/util/future.h +++ b/src/mongo/util/future.h @@ -40,14 +40,23 @@ #include "mongo/stdx/type_traits.h" #include "mongo/util/assert_util.h" #include "mongo/util/debug_util.h" +#include "mongo/util/if_constexpr.h" #include "mongo/util/interruptible.h" #include "mongo/util/intrusive_counter.h" +#include "mongo/util/out_of_line_executor.h" namespace mongo { + /** - * Future<T> is logically a possibly-deferred StatusWith<T> (or Status when T is void). + * SemiFuture<T> is logically a possibly-deferred StatusWith<T> (or Status when T is void). + * + * Unlike Future<T> it only supports blocking operations, not directly chained continuations. You + * are only allowed to chain continuations by passing an executor to thenRunOn(). This is intended + * to protect the promise-completer's execution context from needing to perform arbitrary operations + * requested by other subsystem's continuations. * - * As is usual for rvalue-qualified methods, you may call at most one of them on a given Future. + * SemiFutures can't convert to or be assigned to Futures since that would allow adding + * continuations which would defeat the purpose of SemiFuture. * * A future may be passed between threads, but only one thread may use it at a time. * @@ -56,13 +65,13 @@ namespace mongo { * behavior may change. End all Future chains with either a blocking call to get()/getNoThrow() or a * non-blocking call to getAsync(). * - * Future<void> should be the same as the generic Future<T> with the following exceptions: + * SemiFuture<void> is the same as the generic SemiFuture<T> with the following exceptions: * - Anything mentioning StatusWith<T> will use Status instead. * - Anything returning references to T will just return void since there are no void references. * - Anything taking a T argument will receive no arguments. */ template <typename T> -class MONGO_WARN_UNUSED_RESULT_CLASS Future { +class MONGO_WARN_UNUSED_RESULT_CLASS SemiFuture { using Impl = future_details::FutureImpl<T>; using T_unless_void = std::conditional_t<std::is_void_v<T>, future_details::FakeVoid, T>; @@ -79,64 +88,77 @@ public: using value_type = T; /** - * For non-void T: Constructs a Future in a moved-from state that can only be assigned to + * For non-void T: Constructs a SemiFuture in a moved-from state that can only be assigned to * or destroyed. * - * For void T: Constructs a ready future for parity with Future<T>(T) + * For void T: Constructs a ready Semifuture for parity with SemiFuture<T>(T) */ - Future() = default; + SemiFuture() = default; - Future& operator=(Future&&) = default; - Future(Future&&) = default; + SemiFuture& operator=(SemiFuture&&) = default; + SemiFuture(SemiFuture&&) = default; - Future(const Future&) = delete; - Future& operator=(const Future&) = delete; + SemiFuture(const SemiFuture&) = delete; + SemiFuture& operator=(const SemiFuture&) = delete; /** * For non-void T: This must be passed a not-OK Status. * * For void T: This behaves like the StatusWith constructor and accepts any Status. */ - /* implicit */ Future(Status status) : Future(Impl::makeReady(std::move(status))) {} + /* implicit */ SemiFuture(Status status) : SemiFuture(Impl::makeReady(std::move(status))) {} // These should not be used with T=void. - /* implicit */ Future(T_unless_void val) : Future(Impl::makeReady(std::move(val))) { + /* implicit */ SemiFuture(T_unless_void val) : SemiFuture(Impl::makeReady(std::move(val))) { static_assert(!std::is_void_v<T>); } - /* implicit */ Future(StatusWith<T_unless_void> sw) : Future(Impl::makeReady(std::move(sw))) { + /* implicit */ SemiFuture(StatusWith<T_unless_void> sw) + : SemiFuture(Impl::makeReady(std::move(sw))) { static_assert(!std::is_void_v<T>); } /** - * Make a ready Future<T> from a value for cases where you don't need to wait asynchronously. + * Make a ready SemiFuture<T> from a value for cases where you don't need to wait + * asynchronously. * - * Calling this is faster than getting a Future out of a Promise, and is effectively free. It is - * fast enough that you never need to avoid returning a Future from an API, even if the result + * Calling this is faster than getting a SemiFuture out of a Promise, and is effectively free. + * It is + * fast enough that you never need to avoid returning a SemiFuture from an API, even if the + * result * is ready 99.99% of the time. * * As an example, if you are handing out results from a batch, you can use this when for each - * result while you have a batch, then use a Promise to return a not-ready Future when you need + * result while you have a batch, then use a Promise to return a not-ready SemiFuture when you + * need * to get another batch. */ - static Future<T> makeReady(T_unless_void val) { // TODO emplace? - return mongo::Future(Impl::makeReady(std::move(val))); + static SemiFuture<T> makeReady(T_unless_void val) { + return SemiFuture(Impl::makeReady(std::move(val))); } - static Future<T> makeReady(Status status) { - return mongo::Future(Impl::makeReady(std::move(status))); + static SemiFuture<T> makeReady(Status status) { + return SemiFuture(Impl::makeReady(std::move(status))); } - static Future<T> makeReady(StatusWith<T_unless_void> val) { - return mongo::Future(Impl::makeReady(std::move(val))); + static SemiFuture<T> makeReady(StatusWith<T_unless_void> val) { + return SemiFuture(Impl::makeReady(std::move(val))); } template <typename U = T, typename = std::enable_if_t<std::is_void_v<U>>> - static Future<void> makeReady() { // TODO emplace? - return mongo::Future(Impl::makeReady()); + static SemiFuture<void> makeReady() { + return SemiFuture(Impl::makeReady()); } /** - * Convert this Future to a SharedSemiFuture. + * A no-op so that you can always do `return makesFutureOrSemiFuture().semi()` when you want to + * protect your execution context. + */ + SemiFuture<T> semi() && noexcept { + return std::move(*this); + } + + /** + * Convert this SemiFuture to a SharedSemiFuture. */ SharedSemiFuture<T> share() && noexcept { return std::move(_impl).share(); @@ -145,22 +167,22 @@ public: /** * If this returns true, get() is guaranteed not to block and callbacks will be immediately * invoked. You can't assume anything if this returns false since it may be completed - * immediately after checking (unless you have independent knowledge that this Future can't + * immediately after checking (unless you have independent knowledge that this SemiFuture can't * complete in the background). * - * Callers must still call get() or similar, even on Future<void>, to ensure that they are + * Callers must still call get() or similar, even on SemiFuture<void>, to ensure that they are * correctly sequenced with the completing task, and to be informed about whether the Promise * completed successfully. * * This is generally only useful as an optimization to avoid prep work, such as setting up - * timeouts, that is unnecessary if the Future is ready already. + * timeouts, that is unnecessary if the SemiFuture is ready already. */ bool isReady() const { return _impl.isReady(); } /** - * Returns when the future isReady(). + * Returns when the Semifuture isReady(). * * Throws if the interruptible passed is interrupted (explicitly or via deadline). */ @@ -169,7 +191,7 @@ public: } /** - * Returns Status::OK() when the future isReady(). + * Returns Status::OK() when the Semifuture isReady(). * * Returns a non-okay status if the interruptible is interrupted. */ @@ -179,14 +201,14 @@ public: } /** - * Gets the value out of this Future, blocking until it is ready. + * Gets the value out of this SemiFuture, blocking until it is ready. * * get() methods throw on error, while getNoThrow() returns a !OK status. * * These methods can be called multiple times, except for the rvalue overloads. * * Note: It is impossible to differentiate interruptible interruption from an error propagating - * down the future chain with these methods. If you need to distinguish the two cases, call + * down the Semifuture chain with these methods. If you need to distinguish the two cases, call * wait() first. */ T get(Interruptible* interruptible = Interruptible::notInterruptible()) && { @@ -212,6 +234,106 @@ public: } /** + * Ignores the return value of a future, transforming it down into a SemiFuture<void>. + * + * This only ignores values, not errors. Those remain propagated to an onError handler. + */ + SemiFuture<void> ignoreValue() && noexcept { + return SemiFuture<void>(std::move(this->_impl).ignoreValue()); + } + + /** + * Returns a future that allows you to add continuations that are guaranteed to run on the + * provided executor. + * + * Be sure to read the ExecutorFuture class comment. + */ + ExecutorFuture<T> thenRunOn(ExecutorPtr exec) && noexcept; + +private: + friend class Promise<T>; + template <typename> + friend class Future; + template <typename> + friend class ExecutorFuture; + template <typename> + friend class future_details::FutureImpl; + + Future<T> unsafeToInlineFuture() && noexcept; + + explicit SemiFuture(future_details::SharedStateHolder<T_unless_void>&& impl) + : _impl(std::move(impl)) {} + + explicit SemiFuture(Impl&& impl) : _impl(std::move(impl)) {} + operator Impl &&() && { + return std::move(_impl); + } + + template <typename U> + void propagateResultTo(U&& arg) && { + std::move(_impl).propagateResultTo(std::forward<U>(arg)); + } + + Impl _impl; +}; + +// Deduction Guides +template <typename T, + typename = std::enable_if_t<!isStatusOrStatusWith<T> && !future_details::isFutureLike<T>>> +SemiFuture(T)->SemiFuture<T>; +template <typename T> +SemiFuture(StatusWith<T>)->SemiFuture<T>; + +/** + * Future<T> is a SemiFuture<T> (which is logically a possibly deferred StatusOrStatusWith<T>), + * extended with the ability to chain additional continuations that will be invoked when the result + * is ready. + * + * All comments on SemiFuture<T> apply to Future<T> as well. + */ +template <typename T> +class MONGO_WARN_UNUSED_RESULT_CLASS Future : private SemiFuture<T> { + using Impl = typename SemiFuture<T>::Impl; + using T_unless_void = typename SemiFuture<T>::T_unless_void; + +public: + /** + * Re-export the API of SemiFuture. The API of Future is a superset, except you can't convert + * from a SemiFuture to a Future. + */ + using value_type = T; + using SemiFuture<T>::SemiFuture; // Constructors. + using SemiFuture<T>::share; + using SemiFuture<T>::isReady; + using SemiFuture<T>::wait; + using SemiFuture<T>::waitNoThrow; + using SemiFuture<T>::get; + using SemiFuture<T>::getNoThrow; + using SemiFuture<T>::semi; + using SemiFuture<T>::thenRunOn; + + /** + * Re-export makeReady, but return a Future<T> + */ + static Future<T> makeReady(T_unless_void val) { + return Future(Impl::makeReady(std::move(val))); + } + static Future<T> makeReady(Status status) { + return Future(Impl::makeReady(std::move(status))); + } + static Future<T> makeReady(StatusWith<T_unless_void> val) { + return Future(Impl::makeReady(std::move(val))); + } + template <typename U = T, typename = std::enable_if_t<std::is_void_v<U>>> + static Future<void> makeReady() { + return Future(Impl::makeReady()); + } + + Future<void> ignoreValue() && noexcept { + return Future<void>(std::move(this->_impl).ignoreValue()); + } + + /** * This ends the Future continuation chain by calling a callback on completion. Use this to * escape back into a callback-based API. * @@ -220,11 +342,19 @@ public: */ template <typename Func> void getAsync(Func&& func) && noexcept { - std::move(_impl).getAsync(std::forward<Func>(func)); + std::move(this->_impl).getAsync(std::forward<Func>(func)); } // - // The remaining methods are all continuation based and take a callback and return a Future. + // The remaining methods are all continuation-based and take a callback and return a Future-like + // type based on the return type of the callback, except for the "tap" methods that always + // return Future<T>. When passed a callback that returns a FutureLike<U> type, the return type + // of the method will be either Future<U> if FutureLike is Future, otherwise SemiFuture<U>. The + // result of the callback will be automatically unwrapped and connected to the returned + // FutureLike<U> rather than producing a Future<FutureLike<U>>. When the callback returns a + // non-FutureLike type U, the return type of the method will be Future<U>, with the adjustment + // for Status/StatusWith described below. + // // Each method has a comment indicating the supported signatures for that callback, and a // description of when the callback is invoked and how the impacts the returned Future. It may // be helpful to think of Future continuation chains as a pipeline of stages that take input @@ -242,29 +372,29 @@ public: // in something that called uassertStatusOK() on the return value. There is no way to // distinguish between a function throwing or returning a !OK status. // - // Callbacks that return Future<T> are automatically unwrapped and connected to the returned - // Future<T>, rather than producing a Future<Future<T>>. - // /** * Callbacks passed to then() are only called if the input Future completes successfully. * Otherwise the error propagates automatically, bypassing the callback. + * + * The callback takes a T and can return anything (see above for how Statusy and Futurey returns + * are handled.) */ template <typename Func> - auto then(Func&& func) && noexcept { - return mongo::Future(std::move(_impl).then(std::forward<Func>(func))); + /*see above*/ auto then(Func&& func) && noexcept { + return wrap<Func, T>(std::move(this->_impl).then(std::forward<Func>(func))); } /** * Callbacks passed to onCompletion() are called if the input Future completes with or without * an error. * - * The callback can either produce a replacement value (which must be a T), return a replacement - * Future<T> (such as by retrying), or return/throw a replacement error. + * The callback takes a StatusOrStatusWith<T> and can return anything (see above for how Statusy + * and Futurey returns are handled.) */ template <typename Func> - auto onCompletion(Func&& func) && noexcept { - return mongo::Future(std::move(_impl).onCompletion(std::forward<Func>(func))); + /*see above*/ auto onCompletion(Func&& func) && noexcept { + return wrap<Func, Status>(std::move(this->_impl).onCompletion(std::forward<Func>(func))); } /** @@ -276,29 +406,39 @@ public: * * Note that this will only catch errors produced by earlier stages; it is not registering a * general error handler for the entire chain. + * + * The callback takes a non-OK Status and returns a possibly-wrapped T (see above for how + * Statusy and Futurey returns are handled.) */ template <typename Func> - Future<T> onError(Func&& func) && noexcept { - return mongo::Future(std::move(_impl).onError(std::forward<Func>(func))); + /*see above*/ auto onError(Func&& func) && noexcept { + return wrap<Func, Status>(std::move(this->_impl).onError(std::forward<Func>(func))); } /** * Same as the other two onErrors but only calls the callback if the code matches the template * parameter. Otherwise lets the error propagate unchanged. + * + * The callback takes a non-OK Status and returns a possibly-wrapped T (see above for how + * Statusy and Futurey returns are handled.) */ template <ErrorCodes::Error code, typename Func> - Future<T> onError(Func&& func) && noexcept { - return mongo::Future(std::move(_impl).template onError<code>(std::forward<Func>(func))); + /*see above*/ auto onError(Func&& func) && noexcept { + return wrap<Func, Status>( + std::move(this->_impl).template onError<code>(std::forward<Func>(func))); } /** * Similar to the first two onErrors, but only calls the callback if the category matches * the template parameter. Otherwise lets the error propagate unchanged. + * + * The callback takes a non-OK Status and returns a possibly-wrapped T (see above for how + * Statusy and Futurey returns are handled.) */ template <ErrorCategory category, typename Func> - Future<T> onErrorCategory(Func&& func) && noexcept { - return mongo::Future( - std::move(_impl).template onErrorCategory<category>(std::forward<Func>(func))); + /*see above*/ auto onErrorCategory(Func&& func) && noexcept { + return wrap<Func, Status>( + std::move(this->_impl).template onErrorCategory<category>(std::forward<Func>(func))); } // @@ -318,20 +458,24 @@ public: * Callback is called if the input completes successfully. * * This can be used to inform some outside system of the result. + * + * The callback takes a const T& and must return void. */ template <typename Func> Future<T> tap(Func&& func) && noexcept { - return mongo::Future(std::move(_impl).tap(std::forward<Func>(func))); + return Future<T>(std::move(this->_impl).tap(std::forward<Func>(func))); } /** * Callback is called if the input completes with an error. * * This can be used to log. + * + * The callback takes a non-OK Status and must return void. */ template <typename Func> Future<T> tapError(Func&& func) && noexcept { - return mongo::Future(std::move(_impl).tapError(std::forward<Func>(func))); + return Future<T>(std::move(this->_impl).tapError(std::forward<Func>(func))); } /** @@ -340,55 +484,199 @@ public: * This can be used for cleanup. Some other libraries name the equivalent method finally to * match the common semantic from other languages. * - * Warning: If func takes a StatusWith<T>, it requires copying the value on success. If that is - * too expensive, it can be avoided by either providing a function object with separate - * Status/const T& overloads, or by using a generic lambda if you don't need to consult the - * value for your cleanup. + * The callback takes a StatusOrStatusWith<T> and must return void. */ template <typename Func> Future<T> tapAll(Func&& func) && noexcept { - return mongo::Future(std::move(_impl).tapAll(std::forward<Func>(func))); - } - - /** - * Ignores the return value of a future, transforming it down into a Future<void>. - * - * This only ignores values, not errors. Those remain propogated until an onError handler. - * - * Equivalent to then([](auto&&){}); - */ - Future<void> ignoreValue() && noexcept { - return mongo::Future(std::move(_impl).ignoreValue()); + return Future<T>(std::move(this->_impl).tapAll(std::forward<Func>(func))); } private: template <typename> + friend class ExecutorFuture; + template <typename> friend class Future; template <typename> friend class future_details::FutureImpl; friend class Promise<T>; - explicit Future(future_details::SharedStateHolder<T_unless_void>&& impl) - : _impl(std::move(impl)) {} + using SemiFuture<T>::unsafeToInlineFuture; - explicit Future(Impl&& impl) : _impl(std::move(impl)) {} - operator Impl &&() && { - return std::move(_impl); + template <typename Func, typename Arg, typename U> + static auto wrap(future_details::FutureImpl<U>&& impl) { + using namespace future_details; + return FutureContinuationKind<NormalizedCallResult<Func, Arg>, U>(std::move(impl)); } +}; - template <typename U> - auto propagateResultTo(U&& arg) && { - return std::move(_impl).propagateResultTo(std::forward<U>(arg)); +// Deduction Guides +template <typename T, + typename = std::enable_if_t<!isStatusOrStatusWith<T> && !future_details::isFutureLike<T>>> +Future(T)->Future<T>; +template <typename T> +Future(StatusWith<T>)->Future<T>; + +/** + * An ExecutorFuture is like a Future that ensures that all callbacks are run on a supplied + * executor. + * + * IMPORTANT: Executors are allowed to refuse work by invoking their task callbacks with a non-OK + * Status. In that event, callbacks passed to continuation functions WILL NOT RUN. Instead, the + * error status will propagate down the future chain until it would run a callback on an executor + * that doesn't refuse the work, or it is extracted by calling a blocking get() method. Destructors + * for these callbacks can run in any context, so be suspicious of callbacks that capture Promises + * because they will propagate out BrokenPromise if the executor refuses work. + */ +template <typename T> +class ExecutorFuture : private SemiFuture<T> { + using Impl = typename SemiFuture<T>::Impl; + using T_unless_void = typename SemiFuture<T>::T_unless_void; + +public: + /** + * Default construction is disallowed to ensure that every ExecutorFuture has an associated + * Executor (unless it has been moved-from). + */ + ExecutorFuture() = delete; + + ExecutorFuture(ExecutorPtr exec, Status status) + : SemiFuture<T>(std::move(status)), _exec(std::move(exec)) {} + + // These should not be used with T=void. + ExecutorFuture(ExecutorPtr exec, T_unless_void val) + : SemiFuture<T>(std::move(val)), _exec(std::move(exec)) { + static_assert(!std::is_void_v<T>); + } + ExecutorFuture(ExecutorPtr exec, StatusWith<T_unless_void> sw) + : SemiFuture<T>(std::move(sw)), _exec(std::move(exec)) { + static_assert(!std::is_void_v<T>); } - Impl _impl; + template <typename U = T, typename = std::enable_if_t<std::is_void_v<U>>> + explicit ExecutorFuture(ExecutorPtr exec) : SemiFuture<void>(), _exec(std::move(exec)) {} + + /** + * Re-export the accessor API of SemiFuture. The access API of ExecutorFuture is a superset, but + * you can't create an ExecutorFuture without supplying an executor. + */ + using value_type = T; + using SemiFuture<T>::share; + using SemiFuture<T>::isReady; + using SemiFuture<T>::wait; + using SemiFuture<T>::waitNoThrow; + using SemiFuture<T>::get; + using SemiFuture<T>::getNoThrow; + using SemiFuture<T>::semi; + using SemiFuture<T>::thenRunOn; + + ExecutorFuture<void> ignoreValue() && noexcept { + return ExecutorFuture<void>(std::move(_exec), std::move(this->_impl).ignoreValue()); + } + + // + // Provide the callback-taking API from Future (except for the taps). All callbacks will be run + // on the executor associated with this ExecutorFuture. See class comment for how we handle + // executors that refuse work. + // + // All methods that return non-void will return an ExecutorFuture bound to the same executor as + // this. + // + // There is no tap support because we can't easily be both non-intrusive in the value flow and + // schedule on an executor that is allowed to fail. In particular, the inability to copy + // move-only values means that we would need to refer directly into the internal SharedState + // objects and keep them alive longer that we otherwise would. If there is a real need for this, + // it should be doable, but will be fairly complicated. + // + + template <typename Func> + void getAsync(Func&& func) && noexcept { + static_assert(std::is_void_v<decltype(func(std::declval<StatusOrStatusWith<T>>()))>, + "func passed to getAsync must return void"); + + // Can't use wrapCB since we don't want to return a future, just schedule a non-chainable + // callback. + std::move(this->_impl).getAsync([ + exec = std::move(_exec), // Unlike wrapCB this can move because we won't need it later. + func = std::forward<Func>(func) + ](StatusOrStatusWith<T> arg) mutable noexcept { + exec->schedule([ func = std::move(func), + arg = std::move(arg) ](Status execStatus) mutable noexcept { + if (execStatus.isOK()) + func(std::move(arg)); + }); + }); + } + + template <typename Func> + auto then(Func&& func) && noexcept { + return mongo::ExecutorFuture(std::move(_exec), + std::move(this->_impl).then(wrapCB(std::forward<Func>(func)))); + } + + template <typename Func> + auto onCompletion(Func&& func) && noexcept { + return mongo::ExecutorFuture( + std::move(_exec), + std::move(this->_impl).onCompletion(wrapCB(std::forward<Func>(func)))); + } + + template <typename Func> + ExecutorFuture<T> onError(Func&& func) && noexcept { + return mongo::ExecutorFuture( + std::move(_exec), std::move(this->_impl).onError(wrapCB(std::forward<Func>(func)))); + } + + template <ErrorCodes::Error code, typename Func> + ExecutorFuture<T> onError(Func&& func) && noexcept { + return mongo::ExecutorFuture( + std::move(_exec), + std::move(this->_impl).template onError<code>(wrapCB(std::forward<Func>(func)))); + } + + template <ErrorCategory category, typename Func> + ExecutorFuture<T> onErrorCategory(Func&& func) && noexcept { + return mongo::ExecutorFuture( + std::move(_exec), + std::move(this->_impl) + .template onErrorCategory<category>(wrapCB(std::forward<Func>(func)))); + } + +private: + // This *must* take exec by ref to ensure it isn't moved from while evaluating wrapCB above. + ExecutorFuture(ExecutorPtr&& exec, Impl&& impl) : SemiFuture<T>(std::move(impl)), _exec(exec) { + dassert(_exec); + } + + /** + * Wraps func in a callback that takes the argument it would and returns an appropriately typed + * Future<U>, then schedules a task on _exec to complete the associated promise with the result + * of calling func with that argument. + */ + template <typename Func> + auto wrapCB(Func&& func); + + using SemiFuture<T>::unsafeToInlineFuture; + + template <typename> + friend class ExecutorFuture; + template <typename> + friend class SemiFuture; + template <typename> + friend class future_details::FutureImpl; + + ExecutorPtr _exec; }; // Deduction Guides +template <typename T, + typename = std::enable_if_t<!isStatusOrStatusWith<T> && !future_details::isFutureLike<T>>> +ExecutorFuture(ExecutorPtr, T)->ExecutorFuture<T>; template <typename T> -Future(T)->Future<T>; +ExecutorFuture(ExecutorPtr, future_details::FutureImpl<T>)->ExecutorFuture<T>; template <typename T> -Future(StatusWith<T>)->Future<T>; +ExecutorFuture(ExecutorPtr, StatusWith<T>)->ExecutorFuture<T>; +ExecutorFuture(ExecutorPtr)->ExecutorFuture<void>; + /** * This class represents the producer side of a Future. @@ -541,8 +829,8 @@ private: * All methods that are present do the same as on a Future<T> so see it for documentation. * * Unlike Future<T> it only supports blocking operation, not chained continuations. This is intended - * to protect the promise-completer's execution context from needing to perform arbitrary - * operations requested by other subsystem's continuations. + * to protect the promise-completer's execution context from needing to perform arbitrary operations + * requested by other subsystem's continuations. * TODO Support continuation chaining when supplied with an executor to run them on. * * A SharedSemiFuture may be passed between threads, but only one thread may use it at a time. @@ -622,7 +910,8 @@ private: }; // Deduction Guides -template <typename T> +template <typename T, + typename = std::enable_if_t<!isStatusOrStatusWith<T> && !future_details::isFutureLike<T>>> SharedSemiFuture(T)->SharedSemiFuture<T>; template <typename T> SharedSemiFuture(StatusWith<T>)->SharedSemiFuture<T>; @@ -758,14 +1047,67 @@ inline auto makePromiseFuture() { * FutureContinuationResult<std::function<int(bool)>, NotBool> SFINAE-safe substitution failure. */ template <typename Func, typename... Args> -using FutureContinuationResult = typename future_details::FutureContinuationResultImpl< - std::invoke_result_t<Func, Args&&...>>::type; +using FutureContinuationResult = + future_details::UnwrappedType<std::invoke_result_t<Func, Args&&...>>; // // Implementations of methods that couldn't be defined in the class due to ordering requirements. // template <typename T> +template <typename Func> +auto ExecutorFuture<T>::wrapCB(Func&& func) { + using namespace future_details; + return [ + func = std::forward<Func>(func), + exec = _exec // can't move this! + ](auto&&... args) mutable noexcept + ->Future<UnwrappedType<decltype(func(std::forward<decltype(args)>(args)...))>> { + auto[promise, future] = makePromiseFuture< + UnwrappedType<decltype(func(std::forward<decltype(args)>(args)...))>>(); + + exec->schedule([ + promise = std::move(promise), + func = std::move(func), + argsT = + std::tuple<std::decay_t<decltype(args)>...>(std::forward<decltype(args)>(args)...) + ](Status execStatus) mutable noexcept { + if (execStatus.isOK()) { + promise.setWith([&] { + return [&](auto nullary) { + // Using a lambda taking a nullary lambda here to work around an MSVC2017 + // bug that caused it to not ignore the other side of the constexpr-if. + // TODO Make this less silly once we upgrade to 2019. + IF_CONSTEXPR(!isFutureLike<decltype(nullary())>) { + return nullary(); + } + else { + // Cheat and convert to an inline Future since we know we will schedule + // further user callbacks onto an executor. + return nullary().unsafeToInlineFuture(); + } + }([&] { return std::apply(func, std::move(argsT)); }); + }); + } else { + promise.setError(std::move(execStatus)); + } + }); + + return std::move(future); + }; +} + +template <typename T> + inline ExecutorFuture<T> SemiFuture<T>::thenRunOn(ExecutorPtr exec) && noexcept { + return ExecutorFuture<T>(std::move(exec), std::move(_impl)); +} + +template <typename T> + Future<T> SemiFuture<T>::unsafeToInlineFuture() && noexcept { + return Future<T>(std::move(_impl)); +} + +template <typename T> inline SharedSemiFuture<future_details::FakeVoidToVoid<T>> future_details::FutureImpl<T>::share() && noexcept { using Out = SharedSemiFuture<FakeVoidToVoid<T>>; diff --git a/src/mongo/util/future_impl.h b/src/mongo/util/future_impl.h index e47a7e3308c..bc0fb86ec48 100644 --- a/src/mongo/util/future_impl.h +++ b/src/mongo/util/future_impl.h @@ -57,6 +57,12 @@ template <typename T> class Future; template <typename T> +class SemiFuture; + +template <typename T> +class ExecutorFuture; + +template <typename T> class SharedPromise; template <typename T> @@ -68,34 +74,50 @@ class FutureImpl; template <> class FutureImpl<void>; -// Using extern constexpr to prevent the compiler from allocating storage as a poor man's c++17 -// inline constexpr variable. -// TODO delete extern in c++17 because inline is the default for constexper variables. +// TODO: delete this and just use isFutureLike once SharedSemiFuture is chainable. template <typename T> -extern constexpr bool isFuture = false; +inline constexpr bool isChainable = false; template <typename T> -extern constexpr bool isFuture<Future<T>> = true; +inline constexpr bool isChainable<Future<T>> = true; +template <typename T> +inline constexpr bool isChainable<SemiFuture<T>> = true; +template <typename T> +inline constexpr bool isChainable<ExecutorFuture<T>> = true; template <typename T> -extern constexpr bool isFutureLike = false; +inline constexpr bool isFutureLike = false; +template <typename T> +inline constexpr bool isFutureLike<Future<T>> = true; template <typename T> -extern constexpr bool isFutureLike<Future<T>> = true; +inline constexpr bool isFutureLike<SemiFuture<T>> = true; template <typename T> -extern constexpr bool isFutureLike<SharedSemiFuture<T>> = true; +inline constexpr bool isFutureLike<ExecutorFuture<T>> = true; +template <typename T> +inline constexpr bool isFutureLike<SharedSemiFuture<T>> = true; template <typename T> struct UnwrappedTypeImpl { - static_assert(!isFuture<T>); + static_assert(!isFutureLike<T>); static_assert(!isStatusOrStatusWith<T>); using type = T; }; template <typename T> -using UnwrappedType = typename UnwrappedTypeImpl<T>::type; -template <typename T> struct UnwrappedTypeImpl<Future<T>> { using type = T; }; template <typename T> +struct UnwrappedTypeImpl<SemiFuture<T>> { + using type = T; +}; +template <typename T> +struct UnwrappedTypeImpl<ExecutorFuture<T>> { + using type = T; +}; +template <typename T> +struct UnwrappedTypeImpl<SharedSemiFuture<T>> { + using type = T; +}; +template <typename T> struct UnwrappedTypeImpl<FutureImpl<T>> { using type = T; }; @@ -107,6 +129,36 @@ template <> struct UnwrappedTypeImpl<Status> { using type = void; }; +template <typename T> +using UnwrappedType = typename UnwrappedTypeImpl<T>::type; + +template <typename T> +struct FutureContinuationKindImpl { + static_assert(!isFutureLike<T>); + template <typename U> + using type = Future<U>; +}; +template <typename T> +struct FutureContinuationKindImpl<Future<T>> { + template <typename U> + using type = Future<U>; +}; +template <typename T> +struct FutureContinuationKindImpl<SemiFuture<T>> { + template <typename U> + using type = SemiFuture<U>; +}; +template <typename T> +struct FutureContinuationKindImpl<ExecutorFuture<T>> { + // Weird but right. ExecutorFuture needs to know the executor prior to running the continuation, + // and in this case it doesn't. + template <typename U> + using type = SemiFuture<U>; +}; +template <typename T> +struct FutureContinuationKindImpl<SharedSemiFuture<T>>; // Temporarily disabled. +template <typename T, typename U> +using FutureContinuationKind = typename FutureContinuationKindImpl<T>::template type<U>; template <typename T> struct AddRefUnlessVoidImpl { @@ -269,7 +321,7 @@ inline typename StatusWithResult::value_type throwingCall(Func&& func, Args&&... template <typename Func, typename... Args> using RawNormalizedCallResult = - decltype(throwingCall(std::declval<Func>(), std::declval<Args>()...)); + decltype(throwingCall(std::declval<Func>(), std::declval<VoidToFakeVoid<Args>>()...)); template <typename Func, typename... Args> using NormalizedCallResult = @@ -278,23 +330,6 @@ using NormalizedCallResult = RawNormalizedCallResult<Func, Args...>>; template <typename T> -struct FutureContinuationResultImpl { - using type = T; -}; -template <typename T> -struct FutureContinuationResultImpl<Future<T>> { - using type = T; -}; -template <typename T> -struct FutureContinuationResultImpl<StatusWith<T>> { - using type = T; -}; -template <> -struct FutureContinuationResultImpl<Status> { - using type = void; -}; - -template <typename T> struct SharedStateImpl; template <typename T> @@ -762,7 +797,7 @@ public: template <typename Func, typename Result = NormalizedCallResult<Func, T>, - typename = std::enable_if_t<!isFuture<Result>>> + typename = std::enable_if_t<!isChainable<Result>>> FutureImpl<Result> then(Func&& func) && noexcept { return generalImpl( // on ready success: @@ -785,7 +820,7 @@ public: template <typename Func, typename RawResult = NormalizedCallResult<Func, T>, - typename = std::enable_if_t<isFuture<RawResult>>, + typename = std::enable_if_t<isChainable<RawResult>>, typename UnwrappedResult = typename RawResult::value_type> FutureImpl<UnwrappedResult> then(Func&& func) && noexcept { return generalImpl( @@ -819,23 +854,21 @@ public: } template <typename Func, - typename Result = NormalizedCallResult<Func, Status>, - typename = std::enable_if_t<!isFuture<Result>>> + typename Result = NormalizedCallResult<Func, StatusOrStatusWith<T>>, + typename = std::enable_if_t<!isChainable<Result>>> FutureImpl<Result> onCompletion(Func&& func) && noexcept { - static_assert(std::is_same<Result, NormalizedCallResult<Func, T>>::value, - "func passed to Future<T>::onCompletion must return the same type for " - "arguments of Status and T"); + using Wrapper = StatusOrStatusWith<T>; return generalImpl( // on ready success: [&](T&& val) { return FutureImpl<Result>::makeReady( - statusCall(std::forward<Func>(func), std::move(val))); + statusCall(std::forward<Func>(func), Wrapper(std::move(val)))); }, // on ready failure: [&](Status&& status) { return FutureImpl<Result>::makeReady( - statusCall(std::forward<Func>(func), std::move(status))); + statusCall(std::forward<Func>(func), Wrapper(std::move(status)))); }, // on not ready yet: [&] { @@ -843,29 +876,25 @@ public: SharedState<T> * input, SharedState<Result> * output) mutable noexcept { if (!input->status.isOK()) return output->setFromStatusWith( - statusCall(func, std::move(input->status))); + statusCall(func, Wrapper(std::move(input->status)))); - output->setFromStatusWith(statusCall(func, std::move(*input->data))); + output->setFromStatusWith(statusCall(func, Wrapper(std::move(*input->data)))); }); }); } template <typename Func, - typename RawResult = NormalizedCallResult<Func, Status>, - typename = std::enable_if_t<isFuture<RawResult>>, + typename RawResult = NormalizedCallResult<Func, StatusOrStatusWith<T>>, + typename = std::enable_if_t<isChainable<RawResult>>, typename UnwrappedResult = typename RawResult::value_type> FutureImpl<UnwrappedResult> onCompletion(Func&& func) && noexcept { - static_assert(std::is_same<UnwrappedResult, - typename NormalizedCallResult<Func, T>::value_type>::value, - "func passed to Future<T>::onCompletion must return the same type for " - "arguments of Status and T"); - + using Wrapper = StatusOrStatusWith<T>; return generalImpl( // on ready success: [&](T&& val) { try { return FutureImpl<UnwrappedResult>( - throwingCall(std::forward<Func>(func), std::move(val))); + throwingCall(std::forward<Func>(func), Wrapper(std::move(val)))); } catch (const DBException& ex) { return FutureImpl<UnwrappedResult>::makeReady(ex.toStatus()); } @@ -874,7 +903,7 @@ public: [&](Status&& status) { try { return FutureImpl<UnwrappedResult>( - throwingCall(std::forward<Func>(func), std::move(status))); + throwingCall(std::forward<Func>(func), Wrapper(std::move(status)))); } catch (const DBException& ex) { return FutureImpl<UnwrappedResult>::makeReady(ex.toStatus()); } @@ -886,7 +915,8 @@ public: SharedState<UnwrappedResult> * output) mutable noexcept { if (!input->status.isOK()) { try { - throwingCall(func, std::move(input->status)).propagateResultTo(output); + throwingCall(func, Wrapper(std::move(input->status))) + .propagateResultTo(output); } catch (const DBException& ex) { output->setError(ex.toStatus()); } @@ -895,7 +925,8 @@ public: } try { - throwingCall(func, std::move(*input->data)).propagateResultTo(output); + throwingCall(func, Wrapper(std::move(*input->data))) + .propagateResultTo(output); } catch (const DBException& ex) { output->setError(ex.toStatus()); } @@ -905,7 +936,7 @@ public: template <typename Func, typename Result = RawNormalizedCallResult<Func, Status>, - typename = std::enable_if_t<!isFuture<Result>>> + typename = std::enable_if_t<!isChainable<Result>>> FutureImpl<FakeVoidToVoid<T>> onError(Func&& func) && noexcept { static_assert( std::is_same<Result, T>::value, @@ -932,7 +963,7 @@ public: template <typename Func, typename Result = RawNormalizedCallResult<Func, Status>, - typename = std::enable_if_t<isFuture<Result>>, + typename = std::enable_if_t<isChainable<Result>>, typename = void> FutureImpl<FakeVoidToVoid<T>> onError(Func&& func) && noexcept { static_assert( @@ -989,10 +1020,9 @@ public: template <ErrorCategory category, typename Func> FutureImpl<FakeVoidToVoid<T>> onErrorCategory(Func&& func) && noexcept { using Result = RawNormalizedCallResult<Func, Status>; - static_assert( - std::is_same<Result, T>::value || std::is_same<Result, FutureImpl<T>>::value || - (std::is_same<T, FakeVoid>::value && std::is_same<Result, FutureImpl<void>>::value), - "func passed to Future<T>::onErrorCategory must return T, StatusWith<T>, or Future<T>"); + static_assert(std::is_same_v<VoidToFakeVoid<UnwrappedType<Result>>, T>, + "func passed to Future<T>::onErrorCategory must return T, StatusWith<T>, " + "or Future<T>"); if (_immediate || (isReady() && _shared->status.isOK())) return std::move(*this); @@ -1027,14 +1057,15 @@ public: template <typename Func> FutureImpl<FakeVoidToVoid<T>> tapAll(Func&& func) && noexcept { - static_assert(std::is_void<decltype(call(func, std::declval<const T&>()))>::value, - "func passed to tapAll must return void"); - static_assert(std::is_void<decltype(call(func, std::declval<const Status&>()))>::value, - "func passed to tapAll must return void"); + static_assert( + std::is_void<decltype(call(func, std::declval<const StatusOrStatusWith<T>&>()))>::value, + "func passed to tapAll must return void"); - return tapImpl(std::forward<Func>(func), - [](Func && func, const T& val) noexcept { call(func, val); }, - [](Func && func, const Status& status) noexcept { call(func, status); }); + using Wrapper = StatusOrStatusWith<T>; + return tapImpl( + std::forward<Func>(func), + [](Func && func, const T& val) noexcept { call(func, Wrapper(val)); }, + [](Func && func, const Status& status) noexcept { call(func, Wrapper(status)); }); } FutureImpl<void> ignoreValue() && noexcept; @@ -1223,6 +1254,5 @@ template <typename T> return std::move(*this).then([](auto&&) {}); } - } // namespace future_details } // namespace mongo diff --git a/src/mongo/util/future_test_edge_cases.cpp b/src/mongo/util/future_test_edge_cases.cpp index d5b19e2c818..11e024ab3d7 100644 --- a/src/mongo/util/future_test_edge_cases.cpp +++ b/src/mongo/util/future_test_edge_cases.cpp @@ -40,6 +40,112 @@ namespace mongo { namespace { +// Test FutureContinuationResult<Func, Args...>: +static_assert(std::is_same<FutureContinuationResult<std::function<void()>>, void>::value); +static_assert(std::is_same<FutureContinuationResult<std::function<Status()>>, void>::value); +static_assert(std::is_same<FutureContinuationResult<std::function<Future<void>()>>, void>::value); +static_assert(std::is_same<FutureContinuationResult<std::function<int()>>, int>::value); +static_assert(std::is_same<FutureContinuationResult<std::function<StatusWith<int>()>>, int>::value); +static_assert(std::is_same<FutureContinuationResult<std::function<Future<int>()>>, int>::value); +static_assert(std::is_same<FutureContinuationResult<std::function<int(bool)>, bool>, int>::value); + +template <typename T> +auto overloadCheck(T) -> FutureContinuationResult<std::function<std::true_type(bool)>, T>; +auto overloadCheck(...) -> std::false_type; + +static_assert(decltype(overloadCheck(bool()))::value); // match. +static_assert(!decltype(overloadCheck(std::string()))::value); // SFINAE-failure. + +// Don't allow banned conversions: +static_assert(!std::is_constructible_v<SemiFuture<int>, SemiFuture<double>>); +static_assert(!std::is_constructible_v<SemiFuture<int>, SemiFuture<void>>); +static_assert(!std::is_constructible_v<SemiFuture<void>, SemiFuture<int>>); +static_assert(!std::is_constructible_v<SemiFuture<void>, SemiFuture<double>>); + +static_assert(!std::is_constructible_v<Future<int>, Future<double>>); +static_assert(!std::is_constructible_v<Future<int>, Future<void>>); +static_assert(!std::is_constructible_v<Future<void>, Future<int>>); +static_assert(!std::is_constructible_v<Future<void>, Future<double>>); + +static_assert(!std::is_constructible_v<Future<int>, SemiFuture<int>>); +static_assert(!std::is_constructible_v<Future<int>, SemiFuture<double>>); +static_assert(!std::is_constructible_v<Future<int>, SemiFuture<void>>); +static_assert(!std::is_constructible_v<Future<void>, SemiFuture<int>>); +static_assert(!std::is_constructible_v<Future<void>, SemiFuture<double>>); +static_assert(!std::is_constructible_v<Future<void>, SemiFuture<void>>); + +// This isn't currently allowed for implementation reasons, but it isn't fundamentally undesirable. +// We may want to allow it at some point. +#ifndef _MSC_VER +// https://developercommunity.visualstudio.com/content/problem/507821/is-constructible.html +static_assert(!std::is_constructible_v<SemiFuture<int>, Future<int>>); +#endif + +// Check the return types of then-chaining a Future with a function that returns a SemiFuture or an +// ExecutorFuture. +static_assert(std::is_same_v<decltype(Future<void>().then(std::function<SemiFuture<void>()>())), + SemiFuture<void>>); +static_assert(std::is_same_v<decltype(Future<int>().then(std::function<SemiFuture<void>(int)>())), + SemiFuture<void>>); +static_assert(std::is_same_v<decltype(Future<void>().then(std::function<SemiFuture<int>()>())), + SemiFuture<int>>); +static_assert(std::is_same_v<decltype(Future<int>().then(std::function<SemiFuture<int>(int)>())), + SemiFuture<int>>); +static_assert(std::is_same_v< // + decltype(Future<void>().then(std::function<ExecutorFuture<void>()>())), + SemiFuture<void>>); +static_assert(std::is_same_v< // + decltype(Future<int>().then(std::function<ExecutorFuture<void>(int)>())), + SemiFuture<void>>); +static_assert(std::is_same_v< // + decltype(Future<void>().then(std::function<ExecutorFuture<int>()>())), + SemiFuture<int>>); +static_assert(std::is_same_v< // + decltype(Future<int>().then(std::function<ExecutorFuture<int>(int)>())), + SemiFuture<int>>); + + +// Check deduction guides: +static_assert(std::is_same_v<decltype(SemiFuture(0)), SemiFuture<int>>); +static_assert(std::is_same_v<decltype(SemiFuture(StatusWith(0))), SemiFuture<int>>); +static_assert(std::is_same_v<decltype(Future(0)), Future<int>>); +static_assert(std::is_same_v<decltype(Future(StatusWith(0))), Future<int>>); +static_assert(std::is_same_v<decltype(SharedSemiFuture(0)), SharedSemiFuture<int>>); +static_assert(std::is_same_v<decltype(SharedSemiFuture(StatusWith(0))), SharedSemiFuture<int>>); + +static_assert(std::is_same_v< // + decltype(ExecutorFuture(ExecutorPtr())), + ExecutorFuture<void>>); +static_assert(std::is_same_v< // + decltype(ExecutorFuture(ExecutorPtr(), 0)), + ExecutorFuture<int>>); +static_assert(std::is_same_v< // + decltype(ExecutorFuture(ExecutorPtr(), StatusWith(0))), + ExecutorFuture<int>>); + +template <template <typename...> typename FutureLike, typename... Args> +auto ctadCheck(int) -> decltype(FutureLike(std::declval<Args>()...), std::true_type()); +template <template <typename...> typename FutureLike, typename... Args> +std::false_type ctadCheck(...); + +// Future() and Future(status) are both banned even though they could resolve to Future<void> +// It just seems too likely to lead to mistakes. +static_assert(!decltype(ctadCheck<Future>(0))::value); +static_assert(!decltype(ctadCheck<Future, Status>(0))::value); + +static_assert(!decltype(ctadCheck<Future, SemiFuture<int>>(0))::value); +static_assert(!decltype(ctadCheck<SemiFuture, Future<int>>(0))::value); +static_assert(!decltype(ctadCheck<ExecutorFuture, SemiFuture<int>>(0))::value); +static_assert(decltype(ctadCheck<Future, Future<int>>(0))::value); +static_assert(decltype(ctadCheck<SemiFuture, SemiFuture<int>>(0))::value); +static_assert(decltype(ctadCheck<ExecutorFuture, ExecutorFuture<int>>(0))::value); + +// sanity checks of ctadCheck: (watch those watchmen!) +static_assert(!decltype(ctadCheck<std::basic_string>(0))::value); +static_assert(decltype(ctadCheck<std::basic_string, std::string>(0))::value); +static_assert(decltype(ctadCheck<Future, int>(0))::value); +static_assert(decltype(ctadCheck<Future, StatusWith<int>>(0))::value); + // This is the motivating case for SharedStateBase::isJustForContinuation. Without that logic, there // would be a long chain of SharedStates, growing longer with each recursion. That logic exists to // limit it to a fixed-size chain. diff --git a/src/mongo/util/future_test_executor_future.cpp b/src/mongo/util/future_test_executor_future.cpp new file mode 100644 index 00000000000..564d0e69cda --- /dev/null +++ b/src/mongo/util/future_test_executor_future.cpp @@ -0,0 +1,202 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/util/future.h" + +#include "mongo/unittest/unittest.h" +#include "mongo/util/future_test_utils.h" + +namespace mongo { +namespace { +TEST(Executor_Future, Success_getAsync) { + FUTURE_SUCCESS_TEST( + [] {}, + [](/*Future<void>*/ auto&& fut) { + auto exec = InlineCountingExecutor::make(); + auto pf = makePromiseFuture<void>(); + ExecutorFuture<void>(exec).thenRunOn(exec).getAsync([outside = std::move(pf.promise)]( + Status status) mutable { + ASSERT_OK(status); + outside.emplaceValue(); + }); + ASSERT_EQ(std::move(pf.future).getNoThrow(), Status::OK()); + ASSERT_EQ(exec->tasksRun.load(), 1); + }); +} + +TEST(Executor_Future, Reject_getAsync) { + FUTURE_SUCCESS_TEST( + [] {}, + [](/*Future<void>*/ auto&& fut) { + auto exec = RejectingExecutor::make(); + auto pf = makePromiseFuture<void>(); + std::move(fut).thenRunOn(exec).getAsync([promise = std::move(pf.promise)]( + Status status) mutable { + promise.emplaceValue(); // shouldn't be run anyway. + FAIL("how did I run!?!?!"); + }); + + // Promise is destroyed without calling the callback. + ASSERT_EQ(std::move(pf.future).getNoThrow(), ErrorCodes::BrokenPromise); + }); +} + +TEST(Executor_Future, Success_then) { + FUTURE_SUCCESS_TEST([] {}, + [](/*Future<void>*/ auto&& fut) { + auto exec = InlineCountingExecutor::make(); + ASSERT_EQ(std::move(fut).thenRunOn(exec).then([]() { return 3; }).get(), + 3); + ASSERT_EQ(exec->tasksRun.load(), 1); + }); +} +TEST(Executor_Future, Reject_then) { + FUTURE_SUCCESS_TEST([] {}, + [](/*Future<void>*/ auto&& fut) { + auto exec = RejectingExecutor::make(); + ASSERT_EQ(std::move(fut) + .thenRunOn(exec) + .then([]() { + FAIL("where am I running?"); + return 42; + }) + .getNoThrow(), + ErrorCodes::ShutdownInProgress); + }); +} + +TEST(Executor_Future, Fail_then) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { + auto exec = InlineCountingExecutor::make(); + ASSERT_EQ(std::move(fut) + .thenRunOn(exec) + .then([]() { + FAIL("then() callback was called"); + return int(); + }) + .getNoThrow(), + failStatus()); + ASSERT_EQ(exec->tasksRun.load(), 0); + }); +} + +TEST(Executor_Future, Success_onError) { + FUTURE_SUCCESS_TEST([] { return 3; }, + [](/*Future<int>*/ auto&& fut) { + auto exec = InlineCountingExecutor::make(); + ASSERT_EQ(std::move(fut) + .thenRunOn(exec) + .onError([](Status&&) { + FAIL("onError() callback was called"); + return 42; + }) + .get(), + 3); + ASSERT_EQ(exec->tasksRun.load(), 0); + }); +} + +TEST(Executor_Future, Fail_onErrorSimple) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { + auto exec = InlineCountingExecutor::make(); + ASSERT_EQ(std::move(fut) + .thenRunOn(exec) + .onError([](Status s) { + ASSERT_EQ(s, failStatus()); + return 3; + }) + .get(), + 3); + ASSERT_EQ(exec->tasksRun.load(), 1); + }); +} + +TEST(Executor_Future, Fail_onErrorCode_OtherCode) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { + auto exec = InlineCountingExecutor::make(); + ASSERT_EQ( + std::move(fut) + .thenRunOn(exec) + .template onError<ErrorCodes::BadValue>([](Status s) { FAIL("wrong code, sir"); }) + .getNoThrow(), + failStatus()); + ASSERT_EQ(exec->tasksRun.load(), 0); + }); +} + +TEST(Executor_Future, Success_then_onError_onError_then) { + FUTURE_SUCCESS_TEST([] {}, + [](/*Future<void>*/ auto&& fut) { + auto exec = InlineCountingExecutor::make(); + ASSERT_EQ( + std::move(fut) + .thenRunOn(exec) + .then([] { return failStatus(); }) + .onError([](Status s) { ASSERT_EQ(s, failStatus()); }) + .onError([](Status) { FAIL("how did you get this number?"); }) + .then([] { return 3; }) + .get(), + 3); + + // 1 would also be valid if we did the optimization to not reschedule if + // running on the same executor. + ASSERT_EQ(exec->tasksRun.load(), 3); + }); +} + +TEST(Executor_Future, Success_reject_recoverToFallback) { + FUTURE_SUCCESS_TEST([] {}, + [](/*Future<void>*/ auto&& fut) { + auto rejecter = RejectingExecutor::make(); + auto accepter = InlineCountingExecutor::make(); + + auto res = std::move(fut) + .thenRunOn(rejecter) + .then([] { FAIL("then()"); }) + .onError([](Status) { FAIL("onError()"); }) + .onCompletion([](Status) { FAIL("onCompletion()"); }) + .thenRunOn(accepter) + .then([] { + FAIL("error?"); + return 42; + }) + .onError([](Status s) { + ASSERT_EQ(s, ErrorCodes::ShutdownInProgress); + return 3; + }) + .get(); + ASSERT_EQ(res, 3); + + ASSERT_EQ(accepter->tasksRun.load(), 1); + }); +} +} // namespace +} // namespace mongo diff --git a/src/mongo/util/future_test_future_int.cpp b/src/mongo/util/future_test_future_int.cpp index d976f751662..738084c6b52 100644 --- a/src/mongo/util/future_test_future_int.cpp +++ b/src/mongo/util/future_test_future_int.cpp @@ -40,69 +40,59 @@ namespace mongo { namespace { -MONGO_STATIC_ASSERT(std::is_same<FutureContinuationResult<std::function<void()>>, void>::value); -MONGO_STATIC_ASSERT(std::is_same<FutureContinuationResult<std::function<Status()>>, void>::value); -MONGO_STATIC_ASSERT( - std::is_same<FutureContinuationResult<std::function<Future<void>()>>, void>::value); -MONGO_STATIC_ASSERT(std::is_same<FutureContinuationResult<std::function<int()>>, int>::value); -MONGO_STATIC_ASSERT( - std::is_same<FutureContinuationResult<std::function<StatusWith<int>()>>, int>::value); -MONGO_STATIC_ASSERT( - std::is_same<FutureContinuationResult<std::function<Future<int>()>>, int>::value); -MONGO_STATIC_ASSERT( - std::is_same<FutureContinuationResult<std::function<int(bool)>, bool>, int>::value); - -template <typename T> -auto overloadCheck(T) -> FutureContinuationResult<std::function<std::true_type(bool)>, T>; -auto overloadCheck(...) -> std::false_type; - -MONGO_STATIC_ASSERT(decltype(overloadCheck(bool()))::value); // match. -MONGO_STATIC_ASSERT(!decltype(overloadCheck(std::string()))::value); // SFINAE-failure. - - TEST(Future, Success_getLvalue) { - FUTURE_SUCCESS_TEST([] { return 1; }, [](Future<int>&& fut) { ASSERT_EQ(fut.get(), 1); }); + FUTURE_SUCCESS_TEST([] { return 1; }, + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(fut.get(), 1); }); } TEST(Future, Success_getConstLvalue) { - FUTURE_SUCCESS_TEST([] { return 1; }, [](const Future<int>& fut) { ASSERT_EQ(fut.get(), 1); }); + FUTURE_SUCCESS_TEST([] { return 1; }, + [](const /*Future<int>*/ auto& fut) { ASSERT_EQ(fut.get(), 1); }); } TEST(Future, Success_getRvalue) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { ASSERT_EQ(std::move(fut).get(), 1); }); + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut).get(), 1); }); } TEST(Future, Success_getNothrowLvalue) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { ASSERT_EQ(fut.getNoThrow(), 1); }); + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(fut.getNoThrow(), 1); }); } TEST(Future, Success_getNothrowConstLvalue) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](const Future<int>& fut) { ASSERT_EQ(fut.getNoThrow(), 1); }); + [](const /*Future<int>*/ auto& fut) { ASSERT_EQ(fut.getNoThrow(), 1); }); } TEST(Future, Success_getNothrowRvalue) { - FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { ASSERT_EQ(std::move(fut).getNoThrow(), 1); }); + FUTURE_SUCCESS_TEST( + [] { return 1; }, + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut).getNoThrow(), 1); }); +} + +TEST(Future, Success_semi_get) { + FUTURE_SUCCESS_TEST( + [] { return 1; }, + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut).semi().get(), 1); }); } TEST(Future, Success_shared_get) { - FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { ASSERT_EQ(std::move(fut).share().get(), 1); }); + FUTURE_SUCCESS_TEST( + [] { return 1; }, + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut).share().get(), 1); }); } TEST(Future, Success_shared_getNothrow) { FUTURE_SUCCESS_TEST( [] { return 1; }, - [](Future<int>&& fut) { ASSERT_EQ(std::move(fut).share().getNoThrow(), 1); }); + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut).share().getNoThrow(), 1); }); } TEST(Future, Success_getAsync) { FUTURE_SUCCESS_TEST( [] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { auto pf = makePromiseFuture<int>(); std::move(fut).getAsync([outside = std::move(pf.promise)](StatusWith<int> sw) mutable { ASSERT_OK(sw); @@ -113,44 +103,52 @@ TEST(Future, Success_getAsync) { } TEST(Future, Fail_getLvalue) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { ASSERT_THROWS_failStatus(fut.get()); }); + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_THROWS_failStatus(fut.get()); }); } TEST(Future, Fail_getConstLvalue) { - FUTURE_FAIL_TEST<int>([](const Future<int>& fut) { ASSERT_THROWS_failStatus(fut.get()); }); + FUTURE_FAIL_TEST<int>( + [](const /*Future<int>*/ auto& fut) { ASSERT_THROWS_failStatus(fut.get()); }); } TEST(Future, Fail_getRvalue) { FUTURE_FAIL_TEST<int>( - [](Future<int>&& fut) { ASSERT_THROWS_failStatus(std::move(fut).get()); }); + [](/*Future<int>*/ auto&& fut) { ASSERT_THROWS_failStatus(std::move(fut).get()); }); } TEST(Future, Fail_getNothrowLvalue) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus()); }); + FUTURE_FAIL_TEST<int>( + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus()); }); } TEST(Future, Fail_getNothrowConstLvalue) { FUTURE_FAIL_TEST<int>( - [](const Future<int>& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus()); }); + [](const /*Future<int>*/ auto& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus()); }); } TEST(Future, Fail_getNothrowRvalue) { FUTURE_FAIL_TEST<int>( - [](Future<int>&& fut) { ASSERT_EQ(std::move(fut).getNoThrow(), failStatus()); }); + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut).getNoThrow(), failStatus()); }); +} + +TEST(Future, Fail_semi_get) { + FUTURE_FAIL_TEST<int>( + [](/*Future<int>*/ auto&& fut) { ASSERT_THROWS_failStatus(std::move(fut).semi().get()); }); } TEST(Future, Fail_shared_get) { FUTURE_FAIL_TEST<int>( - [](Future<int>&& fut) { ASSERT_THROWS_failStatus(std::move(fut).share().get()); }); + [](/*Future<int>*/ auto&& fut) { ASSERT_THROWS_failStatus(std::move(fut).share().get()); }); } TEST(Future, Fail_shared_getNothrow) { - FUTURE_FAIL_TEST<int>( - [](Future<int>&& fut) { ASSERT_EQ(std::move(fut).share().getNoThrow(), failStatus()); }); + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { + ASSERT_EQ(std::move(fut).share().getNoThrow(), failStatus()); + }); } TEST(Future, Fail_getAsync) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { auto pf = makePromiseFuture<int>(); std::move(fut).getAsync([outside = std::move(pf.promise)](StatusWith<int> sw) mutable { ASSERT(!sw.isOK()); @@ -162,7 +160,7 @@ TEST(Future, Fail_getAsync) { TEST(Future, Success_isReady) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { const auto id = stdx::this_thread::get_id(); while (!fut.isReady()) { } @@ -175,7 +173,7 @@ TEST(Future, Success_isReady) { } TEST(Future, Fail_isReady) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { const auto id = stdx::this_thread::get_id(); while (!fut.isReady()) { } @@ -189,7 +187,7 @@ TEST(Future, Fail_isReady) { TEST(Future, Success_wait) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { fut.wait(); ASSERT_EQ(fut.get(), 1); }); @@ -197,21 +195,21 @@ TEST(Future, Success_wait) { TEST(Future, Success_waitNoThrow) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_OK(fut.waitNoThrow()); ASSERT_EQ(fut.get(), 1); }); } TEST(Future, Fail_wait) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { fut.wait(); ASSERT_THROWS_failStatus(fut.get()); }); } TEST(Future, Fail_waitNoThrow) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_OK(fut.waitNoThrow()); ASSERT_THROWS_failStatus(fut.get()); }); @@ -247,14 +245,14 @@ TEST(Future, isReady_shared_TSAN_OK) { TEST(Future, Success_thenSimple) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut).then([](int i) { return i + 2; }).get(), 3); }); } TEST(Future, Success_thenSimpleAuto) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut).then([](auto i) { return i + 2; }).get(), 3); }); } @@ -262,7 +260,7 @@ TEST(Future, Success_thenSimpleAuto) { TEST(Future, Success_thenVoid) { FUTURE_SUCCESS_TEST( [] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ( std::move(fut).then([](int i) { ASSERT_EQ(i, 1); }).then([] { return 3; }).get(), 3); @@ -271,7 +269,7 @@ TEST(Future, Success_thenVoid) { TEST(Future, Success_thenStatus) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](int i) { ASSERT_EQ(i, 1); @@ -284,22 +282,25 @@ TEST(Future, Success_thenStatus) { } TEST(Future, Success_thenError_Status) { - FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { - auto fut2 = std::move(fut).then( - [](int i) { return Status(ErrorCodes::BadValue, "oh no!"); }); - MONGO_STATIC_ASSERT(std::is_same<decltype(fut2), Future<void>>::value); - ASSERT_THROWS(fut2.get(), ExceptionFor<ErrorCodes::BadValue>); - }); + FUTURE_SUCCESS_TEST( + [] { return 1; }, + [](/*Future<int>*/ auto&& fut) { + auto fut2 = + std::move(fut).then([](int i) { return Status(ErrorCodes::BadValue, "oh no!"); }); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert(std::is_same_v<typename decltype(fut2)::value_type, void>); + ASSERT_THROWS(fut2.get(), ExceptionFor<ErrorCodes::BadValue>); + }); } TEST(Future, Success_thenError_StatusWith) { FUTURE_SUCCESS_TEST( [] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { auto fut2 = std::move(fut).then( [](int i) { return StatusWith<double>(ErrorCodes::BadValue, "oh no!"); }); - MONGO_STATIC_ASSERT(std::is_same<decltype(fut2), Future<double>>::value); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert(std::is_same_v<typename decltype(fut2)::value_type, double>); ASSERT_THROWS(fut2.get(), ExceptionFor<ErrorCodes::BadValue>); }); } @@ -307,7 +308,7 @@ TEST(Future, Success_thenError_StatusWith) { TEST(Future, Success_thenFutureImmediate) { FUTURE_SUCCESS_TEST( [] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ( std::move(fut).then([](int i) { return Future<int>::makeReady(i + 2); }).get(), 3); }); @@ -315,7 +316,7 @@ TEST(Future, Success_thenFutureImmediate) { TEST(Future, Success_thenFutureReady) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](int i) { auto pf = makePromiseFuture<int>(); @@ -330,15 +331,26 @@ TEST(Future, Success_thenFutureReady) { TEST(Future, Success_thenFutureAsync) { FUTURE_SUCCESS_TEST( [] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut).then([](int i) { return async([i] { return i + 2; }); }).get(), 3); }); } +TEST(Future, Success_thenSemiFutureAsync) { + FUTURE_SUCCESS_TEST([] { return 1; }, + [](/*Future<int>*/ auto&& fut) { + ASSERT_EQ( + std::move(fut) + .then([](int i) { return async([i] { return i + 2; }).semi(); }) + .get(), + 3); + }); +} + TEST(Future, Success_thenFutureAsyncThrow) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](int i) { uasserted(ErrorCodes::BadValue, "oh no!"); @@ -350,7 +362,7 @@ TEST(Future, Success_thenFutureAsyncThrow) { } TEST(Future, Fail_thenSimple) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](int i) { FAIL("then() callback was called"); @@ -362,7 +374,7 @@ TEST(Future, Fail_thenSimple) { } TEST(Future, Fail_thenFutureAsync) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](int i) { FAIL("then() callback was called"); @@ -375,7 +387,7 @@ TEST(Future, Fail_thenFutureAsync) { TEST(Future, Success_onErrorSimple) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status) { FAIL("onError() callback was called"); @@ -389,7 +401,7 @@ TEST(Future, Success_onErrorSimple) { TEST(Future, Success_onErrorFutureAsync) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status) { FAIL("onError() callback was called"); @@ -402,7 +414,7 @@ TEST(Future, Success_onErrorFutureAsync) { } TEST(Future, Fail_onErrorSimple) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); @@ -414,7 +426,7 @@ TEST(Future, Fail_onErrorSimple) { } TEST(Future, Fail_onErrorError_throw) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { auto fut2 = std::move(fut).onError([](Status s) -> int { ASSERT_EQ(s, failStatus()); uasserted(ErrorCodes::BadValue, "oh no!"); @@ -424,7 +436,7 @@ TEST(Future, Fail_onErrorError_throw) { } TEST(Future, Fail_onErrorError_StatusWith) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { auto fut2 = std::move(fut).onError([](Status s) { ASSERT_EQ(s, failStatus()); return StatusWith<int>(ErrorCodes::BadValue, "oh no!"); @@ -434,7 +446,7 @@ TEST(Future, Fail_onErrorError_StatusWith) { } TEST(Future, Fail_onErrorFutureImmediate) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); @@ -446,7 +458,7 @@ TEST(Future, Fail_onErrorFutureImmediate) { } TEST(Future, Fail_onErrorFutureReady) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); @@ -460,7 +472,7 @@ TEST(Future, Fail_onErrorFutureReady) { } TEST(Future, Fail_onErrorFutureAsync) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); @@ -473,9 +485,9 @@ TEST(Future, Fail_onErrorFutureAsync) { TEST(Future, Success_onErrorCode) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) - .onError<ErrorCodes::InternalError>([](Status) { + .template onError<ErrorCodes::InternalError>([](Status) { FAIL("onError<code>() callback was called"); return 0; }) @@ -486,36 +498,37 @@ TEST(Future, Success_onErrorCode) { } TEST(Future, Fail_onErrorCodeMatch) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { - auto res = - std::move(fut) - .onError([](Status s) { - ASSERT_EQ(s, failStatus()); - return StatusWith<int>(ErrorCodes::InternalError, ""); - }) - .onError<ErrorCodes::InternalError>([](Status&&) { return StatusWith<int>(3); }) - .getNoThrow(); + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { + auto res = std::move(fut) + .onError([](Status s) { + ASSERT_EQ(s, failStatus()); + return StatusWith<int>(ErrorCodes::InternalError, ""); + }) + .template onError<ErrorCodes::InternalError>( + [](Status&&) { return StatusWith<int>(3); }) + .getNoThrow(); ASSERT_EQ(res, 3); }); } TEST(Future, Fail_onErrorCodeMatchFuture) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { auto res = std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); return StatusWith<int>(ErrorCodes::InternalError, ""); }) - .onError<ErrorCodes::InternalError>([](Status&&) { return Future<int>(3); }) + .template onError<ErrorCodes::InternalError>( + [](Status&&) { return Future<int>(3); }) .getNoThrow(); ASSERT_EQ(res, 3); }); } TEST(Future, Fail_onErrorCodeMismatch) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) - .onError<ErrorCodes::InternalError>([](Status s) -> int { + .template onError<ErrorCodes::InternalError>([](Status s) -> int { FAIL("Why was this called?") << s; MONGO_UNREACHABLE; }) @@ -529,28 +542,28 @@ TEST(Future, Fail_onErrorCodeMismatch) { } TEST(Future, Success_onErrorCategory) { - FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { - ASSERT_EQ( - std::move(fut) - .onErrorCategory<ErrorCategory::NetworkError>([](Status) { - FAIL("onErrorCategory<category>() callback was called"); - return 0; - }) - .then([](int i) { return i + 2; }) - .get(), - 3); - }); + FUTURE_SUCCESS_TEST( + [] { return 1; }, + [](/*Future<int>*/ auto&& fut) { + ASSERT_EQ(std::move(fut) + .template onErrorCategory<ErrorCategory::NetworkError>([](Status) { + FAIL("onErrorCategory<category>() callback was called"); + return 0; + }) + .then([](int i) { return i + 2; }) + .get(), + 3); + }); } TEST(Future, Fail_onErrorCategoryMatch) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { auto res = std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); return StatusWith<int>(ErrorCodes::HostUnreachable, ""); }) - .onErrorCategory<ErrorCategory::NetworkError>( + .template onErrorCategory<ErrorCategory::NetworkError>( [](Status&&) { return StatusWith<int>(3); }) .getNoThrow(); ASSERT_EQ(res, 3); @@ -558,9 +571,9 @@ TEST(Future, Fail_onErrorCategoryMatch) { } TEST(Future, Fail_onErrorCategoryMismatch) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) - .onErrorCategory<ErrorCategory::NetworkError>([](Status s) -> int { + .template onErrorCategory<ErrorCategory::NetworkError>([](Status s) -> int { FAIL("Why was this called?") << s; MONGO_UNREACHABLE; }) @@ -574,25 +587,26 @@ TEST(Future, Fail_onErrorCategoryMismatch) { } TEST(Future, Success_tap) { - FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { - bool tapCalled = false; - ASSERT_EQ(std::move(fut) - .tap([&tapCalled](int i) { - ASSERT_EQ(i, 1); - tapCalled = true; - }) - .then([](int i) { return i + 2; }) - .get(), - 3); - ASSERT(tapCalled); - }); + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsTap>([] { return 1; }, + [](/*Future<int>*/ auto&& fut) { + bool tapCalled = false; + ASSERT_EQ( + std::move(fut) + .tap([&tapCalled](int i) { + ASSERT_EQ(i, 1); + tapCalled = true; + }) + .then([](int i) { return i + 2; }) + .get(), + 3); + ASSERT(tapCalled); + }); } TEST(Future, Success_tapError) { - FUTURE_SUCCESS_TEST( + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsTap>( [] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .tapError([](Status s) { FAIL("tapError() callback was called"); }) .then([](int i) { return i + 2; }) @@ -602,46 +616,24 @@ TEST(Future, Success_tapError) { } TEST(Future, Success_tapAll_StatusWith) { - FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { - bool tapCalled = false; - ASSERT_EQ(std::move(fut) - .tapAll([&tapCalled](StatusWith<int> sw) { - ASSERT_EQ(sw, 1); - tapCalled = true; - }) - .then([](int i) { return i + 2; }) - .get(), - 3); - ASSERT(tapCalled); - }); -} - -TEST(Future, Success_tapAll_Overloaded) { - FUTURE_SUCCESS_TEST( + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsTap>( [] { return 1; }, - [](Future<int>&& fut) { - struct Callback { - void operator()(int i) { - ASSERT_EQ(i, 1); - called = true; - } - void operator()(Status status) { - FAIL("Status overload called with ") << status; - } - bool called = false; - }; - Callback callback; - - ASSERT_EQ( - std::move(fut).tapAll(std::ref(callback)).then([](int i) { return i + 2; }).get(), - 3); - ASSERT(callback.called); + [](/*Future<int>*/ auto&& fut) { + bool tapCalled = false; + ASSERT_EQ(std::move(fut) + .tapAll([&tapCalled](StatusWith<int> sw) { + ASSERT_EQ(sw, 1); + tapCalled = true; + }) + .then([](int i) { return i + 2; }) + .get(), + 3); + ASSERT(tapCalled); }); } TEST(Future, Fail_tap) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int, kNoExecutorFuture_needsTap>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .tap([](int i) { FAIL("tap() callback was called"); }) .onError([](Status s) { @@ -654,7 +646,7 @@ TEST(Future, Fail_tap) { } TEST(Future, Fail_tapError) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int, kNoExecutorFuture_needsTap>([](/*Future<int>*/ auto&& fut) { bool tapCalled = false; ASSERT_EQ(std::move(fut) .tapError([&tapCalled](Status s) { @@ -672,7 +664,7 @@ TEST(Future, Fail_tapError) { } TEST(Future, Fail_tapAll_StatusWith) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int, kNoExecutorFuture_needsTap>([](/*Future<int>*/ auto&& fut) { bool tapCalled = false; ASSERT_EQ(std::move(fut) .tapAll([&tapCalled](StatusWith<int> sw) { @@ -689,37 +681,10 @@ TEST(Future, Fail_tapAll_StatusWith) { }); } -TEST(Future, Fail_tapAll_Overloaded) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { - struct Callback { - void operator()(int i) { - FAIL("int overload called with ") << i; - } - void operator()(Status status) { - ASSERT_EQ(status, failStatus()); - called = true; - } - bool called = false; - }; - Callback callback; - - ASSERT_EQ(std::move(fut) - .tapAll(std::ref(callback)) - .onError([](Status s) { - ASSERT_EQ(s, failStatus()); - return 3; - }) - .get(), - 3); - - ASSERT(callback.called); - }); -} - TEST(Future, Success_onCompletionSimple) { FUTURE_SUCCESS_TEST( [] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<int> i) { return i.getValue() + 2; }) .get(), @@ -727,32 +692,10 @@ TEST(Future, Success_onCompletionSimple) { }); } -TEST(Future, Success_onCompletionMultiOverload) { - FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { - struct Callback { - int operator()(int i) { - ASSERT_EQ(i, 1); - called = true; - return i + 2; - } - int operator()(Status status) { - FAIL("Status overload called with ") << status; - return int(); - } - bool called = false; - }; - - Callback callback; - ASSERT_EQ(std::move(fut).onCompletion(std::ref(callback)).get(), 3); - ASSERT(callback.called); - }); -} - TEST(Future, Success_onCompletionVoid) { FUTURE_SUCCESS_TEST( [] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<int> i) { ASSERT_EQ(i.getValue(), 1); }) .onCompletion([](Status s) { @@ -766,7 +709,7 @@ TEST(Future, Success_onCompletionVoid) { TEST(Future, Success_onCompletionStatus) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<int> i) { ASSERT_EQ(i, 1); @@ -782,31 +725,33 @@ TEST(Future, Success_onCompletionStatus) { } TEST(Future, Success_onCompletionError_Status) { - FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { - auto fut2 = std::move(fut).onCompletion([](StatusWith<int> i) { - return Status(ErrorCodes::BadValue, "oh no!"); - }); - MONGO_STATIC_ASSERT(std::is_same<decltype(fut2), Future<void>>::value); - ASSERT_THROWS(fut2.get(), ExceptionFor<ErrorCodes::BadValue>); - }); + FUTURE_SUCCESS_TEST( + [] { return 1; }, + [](/*Future<int>*/ auto&& fut) { + auto fut2 = std::move(fut).onCompletion( + [](StatusWith<int> i) { return Status(ErrorCodes::BadValue, "oh no!"); }); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert(std::is_same_v<typename decltype(fut2)::value_type, void>); + ASSERT_THROWS(fut2.get(), ExceptionFor<ErrorCodes::BadValue>); + }); } TEST(Future, Success_onCompletionError_StatusWith) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { auto fut2 = std::move(fut).onCompletion([](StatusWith<int> i) { return StatusWith<double>(ErrorCodes::BadValue, "oh no!"); }); - MONGO_STATIC_ASSERT( - std::is_same<decltype(fut2), Future<double>>::value); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert( + std::is_same_v<typename decltype(fut2)::value_type, double>); ASSERT_THROWS(fut2.get(), ExceptionFor<ErrorCodes::BadValue>); }); } TEST(Future, Success_onCompletionFutureImmediate) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<int> i) { return Future<int>::makeReady(i.getValue() + 2); @@ -818,7 +763,7 @@ TEST(Future, Success_onCompletionFutureImmediate) { TEST(Future, Success_onCompletionFutureReady) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<int> i) { auto pf = makePromiseFuture<int>(); @@ -832,7 +777,7 @@ TEST(Future, Success_onCompletionFutureReady) { TEST(Future, Success_onCompletionFutureAsync) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<int> i) { return async([i = i.getValue()] { return i + 2; }); @@ -844,7 +789,7 @@ TEST(Future, Success_onCompletionFutureAsync) { TEST(Future, Success_onCompletionFutureAsyncThrow) { FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { + [](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<int> i) { uasserted(ErrorCodes::BadValue, "oh no!"); @@ -856,7 +801,7 @@ TEST(Future, Success_onCompletionFutureAsyncThrow) { } TEST(Future, Fail_onCompletionSimple) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<int> i) { ASSERT_NOT_OK(i); @@ -869,7 +814,7 @@ TEST(Future, Fail_onCompletionSimple) { } TEST(Future, Fail_onCompletionError_throw) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { auto fut2 = std::move(fut).onCompletion([](StatusWith<int> s) -> int { ASSERT_EQ(s.getStatus(), failStatus()); uasserted(ErrorCodes::BadValue, "oh no!"); @@ -879,7 +824,7 @@ TEST(Future, Fail_onCompletionError_throw) { } TEST(Future, Fail_onCompletionError_StatusWith) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { auto fut2 = std::move(fut).onCompletion([](StatusWith<int> s) { ASSERT_EQ(s.getStatus(), failStatus()); return StatusWith<int>(ErrorCodes::BadValue, "oh no!"); @@ -889,7 +834,7 @@ TEST(Future, Fail_onCompletionError_StatusWith) { } TEST(Future, Fail_onCompletionFutureImmediate) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<int> s) { ASSERT_EQ(s.getStatus(), failStatus()); @@ -901,7 +846,7 @@ TEST(Future, Fail_onCompletionFutureImmediate) { } TEST(Future, Fail_onCompletionFutureReady) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int>([](/*Future<int>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<int> s) { ASSERT_EQ(s.getStatus(), failStatus()); diff --git a/src/mongo/util/future_test_future_move_only.cpp b/src/mongo/util/future_test_future_move_only.cpp index dedf4ece469..6419915d003 100644 --- a/src/mongo/util/future_test_future_move_only.cpp +++ b/src/mongo/util/future_test_future_move_only.cpp @@ -76,52 +76,59 @@ std::ostream& operator<<(std::ostream& stream, const Widget& widget) { TEST(Future_MoveOnly, Success_getLvalue) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { ASSERT_EQ(fut.get(), 1); }); + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(fut.get(), 1); }); } TEST(Future_MoveOnly, Success_getConstLvalue) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](const Future<Widget>& fut) { ASSERT_EQ(fut.get(), 1); }); + [](const /*Future<Widget>*/ auto& fut) { ASSERT_EQ(fut.get(), 1); }); } TEST(Future_MoveOnly, Success_getRvalue) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { ASSERT_EQ(std::move(fut).get(), 1); }); + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut).get(), 1); }); +} + +TEST(Future_MoveOnly, Success_semi_get) { + FUTURE_SUCCESS_TEST( + [] { return Widget(1); }, + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut).semi().get(), 1); }); } TEST(Future_MoveOnly, Success_shared_get) { - FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { ASSERT_EQ(std::move(fut).share().get(), 1); }); + FUTURE_SUCCESS_TEST( + [] { return Widget(1); }, + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut).share().get(), 1); }); } #if 0 // Needs copy TEST(Future_MoveOnly, Success_getNothrowLvalue) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { ASSERT_EQ(fut.getNoThrow(), 1); }); + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(fut.getNoThrow(), 1); }); } TEST(Future_MoveOnly, Success_getNothrowConstLvalue) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](const Future<Widget>& fut) { ASSERT_EQ(fut.getNoThrow(), 1); }); + [](const /*Future<Widget>*/ auto& fut) { ASSERT_EQ(fut.getNoThrow(), 1); }); } TEST(Future_MoveOnly, Success_shared_getNothrow) { FUTURE_SUCCESS_TEST( [] { return Widget(1); }, - [](Future<Widget>&& fut) { ASSERT_EQ(std::move(fut).share().getNoThrow(), 1); }); + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut).share().getNoThrow(), 1); }); } #endif TEST(Future_MoveOnly, Success_getNothrowRvalue) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(uassertStatusOK(std::move(fut).getNoThrow()).val, 1); }); } TEST(Future_MoveOnly, Success_getAsync) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { auto pf = makePromiseFuture<Widget>(); std::move(fut).getAsync([outside = std::move(pf.promise)]( StatusWith<Widget> sw) mutable { @@ -133,49 +140,57 @@ TEST(Future_MoveOnly, Success_getAsync) { } TEST(Future_MoveOnly, Fail_getLvalue) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { ASSERT_THROWS_failStatus(fut.get()); }); + FUTURE_FAIL_TEST<Widget>( + [](/*Future<Widget>*/ auto&& fut) { ASSERT_THROWS_failStatus(fut.get()); }); } TEST(Future_MoveOnly, Fail_getConstLvalue) { FUTURE_FAIL_TEST<Widget>( - [](const Future<Widget>& fut) { ASSERT_THROWS_failStatus(fut.get()); }); + [](const /*Future<Widget>*/ auto& fut) { ASSERT_THROWS_failStatus(fut.get()); }); } TEST(Future_MoveOnly, Fail_getRvalue) { FUTURE_FAIL_TEST<Widget>( - [](Future<Widget>&& fut) { ASSERT_THROWS_failStatus(std::move(fut).get()); }); + [](/*Future<Widget>*/ auto&& fut) { ASSERT_THROWS_failStatus(std::move(fut).get()); }); +} + +TEST(Future_MoveOnly, Fail_semi_get) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { + ASSERT_THROWS_failStatus(std::move(fut).semi().get()); + }); } TEST(Future_MoveOnly, Fail_shared_get) { - FUTURE_FAIL_TEST<Widget>( - [](Future<Widget>&& fut) { ASSERT_THROWS_failStatus(std::move(fut).share().get()); }); + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { + ASSERT_THROWS_failStatus(std::move(fut).share().get()); + }); } #if 0 // Needs copy TEST(Future_MoveOnly, Fail_getNothrowLvalue) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus); }); + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus); }); } TEST(Future_MoveOnly, Fail_getNothrowConstLvalue) { FUTURE_FAIL_TEST<Widget>( - [](const Future<Widget>& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus); }); + [](const /*Future<Widget>*/ auto& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus); }); } TEST(Future_MoveOnly, Fail_shared_getNothrow) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut).share().getNoThrow().getStatus(), failStatus()); }); } #endif TEST(Future_MoveOnly, Fail_getNothrowRvalue) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut).getNoThrow().getStatus(), failStatus()); }); } TEST(Future_MoveOnly, Fail_getAsync) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { auto pf = makePromiseFuture<Widget>(); std::move(fut).getAsync([outside = std::move(pf.promise)](StatusWith<Widget> sw) mutable { ASSERT(!sw.isOK()); @@ -187,21 +202,21 @@ TEST(Future_MoveOnly, Fail_getAsync) { TEST(Future_MoveOnly, Success_thenSimple) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut).then([](Widget i) { return i + 2; }).get(), 3); }); } TEST(Future_MoveOnly, Success_thenSimpleAuto) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut).then([](auto&& i) { return i + 2; }).get(), 3); }); } TEST(Future_MoveOnly, Success_thenVoid) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](Widget i) { ASSERT_EQ(i, 1); }) .then([] { return Widget(3); }) @@ -212,7 +227,7 @@ TEST(Future_MoveOnly, Success_thenVoid) { TEST(Future_MoveOnly, Success_thenStatus) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](Widget i) { ASSERT_EQ(i, 1); @@ -226,10 +241,12 @@ TEST(Future_MoveOnly, Success_thenStatus) { TEST(Future_MoveOnly, Success_thenError_Status) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { auto fut2 = std::move(fut).then( [](Widget i) { return Status(ErrorCodes::BadValue, "oh no!"); }); - MONGO_STATIC_ASSERT(std::is_same<decltype(fut2), Future<void>>::value); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert( + std::is_same_v<typename decltype(fut2)::value_type, void>); ASSERT_EQ(fut2.getNoThrow(), ErrorCodes::BadValue); }); } @@ -237,10 +254,11 @@ TEST(Future_MoveOnly, Success_thenError_Status) { TEST(Future_MoveOnly, Success_thenError_StatusWith) { FUTURE_SUCCESS_TEST( [] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { auto fut2 = std::move(fut).then( [](Widget i) { return StatusWith<double>(ErrorCodes::BadValue, "oh no!"); }); - MONGO_STATIC_ASSERT(std::is_same<decltype(fut2), Future<double>>::value); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert(std::is_same_v<typename decltype(fut2)::value_type, double>); ASSERT_EQ(fut2.getNoThrow(), ErrorCodes::BadValue); }); } @@ -248,7 +266,7 @@ TEST(Future_MoveOnly, Success_thenError_StatusWith) { TEST(Future_MoveOnly, Success_thenFutureImmediate) { FUTURE_SUCCESS_TEST( [] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](Widget i) { return Future<Widget>::makeReady(Widget(i + 2)); }) .get(), @@ -258,7 +276,7 @@ TEST(Future_MoveOnly, Success_thenFutureImmediate) { TEST(Future_MoveOnly, Success_thenFutureReady) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](Widget i) { auto pf = makePromiseFuture<Widget>(); @@ -272,7 +290,7 @@ TEST(Future_MoveOnly, Success_thenFutureReady) { TEST(Future_MoveOnly, Success_thenFutureAsync) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([&](Widget i) { return async([i = i.val] { return Widget(i + 2); }); @@ -282,9 +300,22 @@ TEST(Future_MoveOnly, Success_thenFutureAsync) { }); } +TEST(Future_MoveOnly, Success_thenSemiFutureAsync) { + FUTURE_SUCCESS_TEST([] { return Widget(1); }, + [](/*Future<Widget>*/ auto&& fut) { + ASSERT_EQ( + std::move(fut) + .then([&](Widget i) { + return async([i = i.val] { return Widget(i + 2); }).semi(); + }) + .get(), + 3); + }); +} + TEST(Future_MoveOnly, Success_thenFutureAsyncThrow) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](Widget i) { uasserted(ErrorCodes::BadValue, "oh no!"); @@ -296,7 +327,7 @@ TEST(Future_MoveOnly, Success_thenFutureAsyncThrow) { } TEST(Future_MoveOnly, Fail_thenSimple) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](Widget i) { FAIL("then() callback was called"); @@ -308,7 +339,7 @@ TEST(Future_MoveOnly, Fail_thenSimple) { } TEST(Future_MoveOnly, Fail_thenFutureAsync) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([](Widget i) { FAIL("then() callback was called"); @@ -321,7 +352,7 @@ TEST(Future_MoveOnly, Fail_thenFutureAsync) { TEST(Future_MoveOnly, Success_onErrorSimple) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status) { FAIL("onError() callback was called"); @@ -335,7 +366,7 @@ TEST(Future_MoveOnly, Success_onErrorSimple) { TEST(Future_MoveOnly, Success_onErrorFutureAsync) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status) { FAIL("onError() callback was called"); @@ -348,7 +379,7 @@ TEST(Future_MoveOnly, Success_onErrorFutureAsync) { } TEST(Future_MoveOnly, Fail_onErrorSimple) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(uassertStatusOK(std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); @@ -359,7 +390,7 @@ TEST(Future_MoveOnly, Fail_onErrorSimple) { }); } TEST(Future_MoveOnly, Fail_onErrorError_throw) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { auto fut2 = std::move(fut).onError([](Status s) -> Widget { ASSERT_EQ(s, failStatus()); uasserted(ErrorCodes::BadValue, "oh no!"); @@ -369,7 +400,7 @@ TEST(Future_MoveOnly, Fail_onErrorError_throw) { } TEST(Future_MoveOnly, Fail_onErrorError_StatusWith) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { auto fut2 = std::move(fut).onError([](Status s) { ASSERT_EQ(s, failStatus()); return StatusWith<Widget>(ErrorCodes::BadValue, "oh no!"); @@ -379,7 +410,7 @@ TEST(Future_MoveOnly, Fail_onErrorError_StatusWith) { } TEST(Future_MoveOnly, Fail_onErrorFutureImmediate) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); @@ -391,7 +422,7 @@ TEST(Future_MoveOnly, Fail_onErrorFutureImmediate) { } TEST(Future_MoveOnly, Fail_onErrorFutureReady) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); @@ -405,7 +436,7 @@ TEST(Future_MoveOnly, Fail_onErrorFutureReady) { } TEST(Future_MoveOnly, Fail_onErrorFutureAsync) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([&](Status s) { ASSERT_EQ(s, failStatus()); @@ -417,25 +448,26 @@ TEST(Future_MoveOnly, Fail_onErrorFutureAsync) { } TEST(Future_MoveOnly, Success_tap) { - FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { - bool tapCalled = false; - ASSERT_EQ(std::move(fut) - .tap([&tapCalled](const Widget& i) { - ASSERT_EQ(i, 1); - tapCalled = true; - }) - .then([](Widget i) { return i + 2; }) - .get(), - 3); - ASSERT(tapCalled); - }); + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsTap>( + [] { return Widget(1); }, + [](/*Future<Widget>*/ auto&& fut) { + bool tapCalled = false; + ASSERT_EQ(std::move(fut) + .tap([&tapCalled](const Widget& i) { + ASSERT_EQ(i, 1); + tapCalled = true; + }) + .then([](Widget i) { return i + 2; }) + .get(), + 3); + ASSERT(tapCalled); + }); } TEST(Future_MoveOnly, Success_tapError) { - FUTURE_SUCCESS_TEST( + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsTap>( [] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .tapError([](Status s) { FAIL("tapError() callback was called"); }) .then([](Widget i) { return i + 2; }) @@ -446,47 +478,25 @@ TEST(Future_MoveOnly, Success_tapError) { #if 0 // Needs copy TEST(Future_MoveOnly, Success_tapAll_StatusWith) { - FUTURE_SUCCESS_TEST([]{return Widget(1);}, [](Future<Widget>&& fut) { - bool tapCalled = false; - ASSERT_EQ(std::move(fut) - .tapAll([&tapCalled](StatusWith<Widget> sw) { - ASSERT_EQ(uassertStatusOK(sw).val, 1); - tapCalled = true; - }) - .then([](Widget i) { return i + 2; }) - .get(), - 3); - ASSERT(tapCalled); - }); + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsTap>( + [] { return Widget(1); }, + [](/*Future<Widget>*/ auto&& fut) { + bool tapCalled = false; + ASSERT_EQ(std::move(fut) + .tapAll([&tapCalled](StatusWith<Widget> sw) { + ASSERT_EQ(uassertStatusOK(sw).val, 1); + tapCalled = true; + }) + .then([](Widget i) { return i + 2; }) + .get(), + 3); + ASSERT(tapCalled); + }); } #endif -TEST(Future_MoveOnly, Success_tapAll_Overloaded) { - FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { - struct Callback { - void operator()(const Widget& i) { - ASSERT_EQ(i, 1); - called = true; - } - void operator()(Status status) { - FAIL("Status overload called with ") << status; - } - bool called = false; - }; - Callback callback; - - ASSERT_EQ(std::move(fut) - .tapAll(std::ref(callback)) - .then([](Widget i) { return i + 2; }) - .get(), - 3); - ASSERT(callback.called); - }); -} - TEST(Future_MoveOnly, Fail_tap) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget, kNoExecutorFuture_needsTap>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .tap([](const Widget& i) { FAIL("tap() callback was called"); }) .onError([](Status s) { @@ -499,7 +509,7 @@ TEST(Future_MoveOnly, Fail_tap) { } TEST(Future_MoveOnly, Fail_tapError) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget, kNoExecutorFuture_needsTap>([](/*Future<Widget>*/ auto&& fut) { bool tapCalled = false; ASSERT_EQ(std::move(fut) .tapError([&tapCalled](Status s) { @@ -518,7 +528,7 @@ TEST(Future_MoveOnly, Fail_tapError) { #if 0 // Needs copy TEST(Future_MoveOnly, Fail_tapAll_StatusWith) { - FUTURE_FAIL_TEST<Widget>( [](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget, kNoExecutorFuture_needsTap>( [](/*Future<Widget>*/ auto&& fut) { bool tapCalled = false; ASSERT_EQ(std::move(fut) .tapAll([&tapCalled](StatusWith<Widget> sw) { @@ -536,37 +546,10 @@ TEST(Future_MoveOnly, Fail_tapAll_StatusWith) { } #endif -TEST(Future_MoveOnly, Fail_tapAll_Overloaded) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { - struct Callback { - void operator()(const Widget& i) { - FAIL("Widget overload called with ") << i; - } - void operator()(Status status) { - ASSERT_EQ(status, failStatus()); - called = true; - } - bool called = false; - }; - Callback callback; - - ASSERT_EQ(std::move(fut) - .tapAll(std::ref(callback)) - .onError([](Status s) { - ASSERT_EQ(s, failStatus()); - return Widget(3); - }) - .get(), - 3); - - ASSERT(callback.called); - }); -} - TEST(Future_MoveOnly, Success_onCompletionSimple) { FUTURE_SUCCESS_TEST( [] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<Widget> i) { return i.getValue() + 2; }) .get(), @@ -574,31 +557,10 @@ TEST(Future_MoveOnly, Success_onCompletionSimple) { }); } -TEST(Future_MoveOnly, Success_onCompletionMultiOverload) { - FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { - struct Callback { - Widget operator()(const Widget& i) { - called = true; - return i + 2; - } - Widget operator()(Status status) { - MONGO_UNREACHABLE; - } - bool called = false; - }; - Callback callback; - - ASSERT_EQ(std::move(fut).onCompletion(std::ref(callback)).get(), 3); - - ASSERT(callback.called); - }); -} - TEST(Future_MoveOnly, Success_onCompletionVoid) { FUTURE_SUCCESS_TEST( [] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<Widget> i) { ASSERT_EQ(i.getValue(), 1); }) .onCompletion([](Status s) { @@ -612,7 +574,7 @@ TEST(Future_MoveOnly, Success_onCompletionVoid) { TEST(Future_MoveOnly, Success_onCompletionStatus) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<Widget> i) { ASSERT_EQ(i.getValue(), 1); @@ -628,31 +590,33 @@ TEST(Future_MoveOnly, Success_onCompletionStatus) { } TEST(Future_MoveOnly, Success_onCompletionError_Status) { - FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { - auto fut2 = std::move(fut).onCompletion([](StatusWith<Widget> i) { - return Status(ErrorCodes::BadValue, "oh no!"); - }); - MONGO_STATIC_ASSERT(std::is_same<decltype(fut2), Future<void>>::value); - ASSERT_EQ(fut2.getNoThrow(), ErrorCodes::BadValue); - }); + FUTURE_SUCCESS_TEST( + [] { return Widget(1); }, + [](/*Future<Widget>*/ auto&& fut) { + auto fut2 = std::move(fut).onCompletion( + [](StatusWith<Widget> i) { return Status(ErrorCodes::BadValue, "oh no!"); }); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert(std::is_same_v<typename decltype(fut2)::value_type, void>); + ASSERT_EQ(fut2.getNoThrow(), ErrorCodes::BadValue); + }); } TEST(Future_MoveOnly, Success_onCompletionError_StatusWith) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { auto fut2 = std::move(fut).onCompletion([](StatusWith<Widget> i) { return StatusWith<double>(ErrorCodes::BadValue, "oh no!"); }); - MONGO_STATIC_ASSERT( - std::is_same<decltype(fut2), Future<double>>::value); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert( + std::is_same_v<typename decltype(fut2)::value_type, double>); ASSERT_EQ(fut2.getNoThrow(), ErrorCodes::BadValue); }); } TEST(Future_MoveOnly, Success_onCompletionFutureImmediate) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<Widget> i) { return Future<Widget>::makeReady( @@ -665,7 +629,7 @@ TEST(Future_MoveOnly, Success_onCompletionFutureImmediate) { TEST(Future_MoveOnly, Success_onCompletionFutureReady) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<Widget> i) { auto pf = makePromiseFuture<Widget>(); @@ -679,7 +643,7 @@ TEST(Future_MoveOnly, Success_onCompletionFutureReady) { TEST(Future_MoveOnly, Success_onCompletionFutureAsync) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([&](StatusWith<Widget> i) { return async( @@ -692,7 +656,7 @@ TEST(Future_MoveOnly, Success_onCompletionFutureAsync) { TEST(Future_MoveOnly, Success_onCompletionFutureAsyncThrow) { FUTURE_SUCCESS_TEST([] { return Widget(1); }, - [](Future<Widget>&& fut) { + [](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<Widget> i) { uasserted(ErrorCodes::BadValue, "oh no!"); @@ -704,7 +668,7 @@ TEST(Future_MoveOnly, Success_onCompletionFutureAsyncThrow) { } TEST(Future_MoveOnly, Fail_onCompletionSimple) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<Widget> i) { ASSERT_NOT_OK(i); @@ -716,7 +680,7 @@ TEST(Future_MoveOnly, Fail_onCompletionSimple) { } TEST(Future_MoveOnly, Fail_onCompletionFutureAsync) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<Widget> i) { ASSERT_NOT_OK(i); @@ -728,7 +692,7 @@ TEST(Future_MoveOnly, Fail_onCompletionFutureAsync) { } TEST(Future_MoveOnly, Fail_onCompletionError_throw) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { auto fut2 = std::move(fut).onCompletion([](StatusWith<Widget> s) -> Widget { ASSERT_EQ(s.getStatus(), failStatus()); uasserted(ErrorCodes::BadValue, "oh no!"); @@ -738,7 +702,7 @@ TEST(Future_MoveOnly, Fail_onCompletionError_throw) { } TEST(Future_MoveOnly, Fail_onCompletionError_StatusWith) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { auto fut2 = std::move(fut).onCompletion([](StatusWith<Widget> s) { ASSERT_EQ(s.getStatus(), failStatus()); return StatusWith<Widget>(ErrorCodes::BadValue, "oh no!"); @@ -748,7 +712,7 @@ TEST(Future_MoveOnly, Fail_onCompletionError_StatusWith) { } TEST(Future_MoveOnly, Fail_onCompletionFutureImmediate) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<Widget> s) { ASSERT_EQ(s.getStatus(), failStatus()); @@ -760,7 +724,7 @@ TEST(Future_MoveOnly, Fail_onCompletionFutureImmediate) { } TEST(Future_MoveOnly, Fail_onCompletionFutureReady) { - FUTURE_FAIL_TEST<Widget>([](Future<Widget>&& fut) { + FUTURE_FAIL_TEST<Widget>([](/*Future<Widget>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](StatusWith<Widget> s) { ASSERT_EQ(s.getStatus(), failStatus()); diff --git a/src/mongo/util/future_test_future_void.cpp b/src/mongo/util/future_test_future_void.cpp index 8f46ec83439..48deec156a7 100644 --- a/src/mongo/util/future_test_future_void.cpp +++ b/src/mongo/util/future_test_future_void.cpp @@ -41,46 +41,52 @@ namespace mongo { namespace { TEST(Future_Void, Success_getLvalue) { - FUTURE_SUCCESS_TEST([] {}, [](Future<void>&& fut) { fut.get(); }); + FUTURE_SUCCESS_TEST([] {}, [](/*Future<void>*/ auto&& fut) { fut.get(); }); } TEST(Future_Void, Success_getConstLvalue) { - FUTURE_SUCCESS_TEST([] {}, [](const Future<void>& fut) { fut.get(); }); + FUTURE_SUCCESS_TEST([] {}, [](const /*Future<void>*/ auto& fut) { fut.get(); }); } TEST(Future_Void, Success_getRvalue) { - FUTURE_SUCCESS_TEST([] {}, [](Future<void>&& fut) { std::move(fut).get(); }); + FUTURE_SUCCESS_TEST([] {}, [](/*Future<void>*/ auto&& fut) { std::move(fut).get(); }); } TEST(Future_Void, Success_getNothrowLvalue) { - FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { ASSERT_EQ(fut.getNoThrow(), Status::OK()); }); + FUTURE_SUCCESS_TEST( + [] {}, [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(fut.getNoThrow(), Status::OK()); }); } TEST(Future_Void, Success_getNothrowConstLvalue) { - FUTURE_SUCCESS_TEST([] {}, - [](const Future<void>& fut) { ASSERT_EQ(fut.getNoThrow(), Status::OK()); }); + FUTURE_SUCCESS_TEST( + [] {}, [](const /*Future<void>*/ auto& fut) { ASSERT_EQ(fut.getNoThrow(), Status::OK()); }); } TEST(Future_Void, Success_getNothrowRvalue) { FUTURE_SUCCESS_TEST( - [] {}, [](Future<void>&& fut) { ASSERT_EQ(std::move(fut).getNoThrow(), Status::OK()); }); + [] {}, + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut).getNoThrow(), Status::OK()); }); +} + +TEST(Future_Void, Success_semi_get) { + FUTURE_SUCCESS_TEST([] {}, [](/*Future<void>*/ auto&& fut) { std::move(fut).semi().get(); }); } TEST(Future_Void, Success_shared_get) { - FUTURE_SUCCESS_TEST([] {}, [](Future<void>&& fut) { std::move(fut).share().get(); }); + FUTURE_SUCCESS_TEST([] {}, [](/*Future<void>*/ auto&& fut) { std::move(fut).share().get(); }); } TEST(Future_Void, Success_shared_getNothrow) { - FUTURE_SUCCESS_TEST( - [] {}, - [](Future<void>&& fut) { ASSERT_EQ(std::move(fut).share().getNoThrow(), Status::OK()); }); + FUTURE_SUCCESS_TEST([] {}, + [](/*Future<void>*/ auto&& fut) { + ASSERT_EQ(std::move(fut).share().getNoThrow(), Status::OK()); + }); } TEST(Future_Void, Success_getAsync) { FUTURE_SUCCESS_TEST( [] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { auto pf = makePromiseFuture<void>(); std::move(fut).getAsync([outside = std::move(pf.promise)](Status status) mutable { ASSERT_OK(status); @@ -91,44 +97,54 @@ TEST(Future_Void, Success_getAsync) { } TEST(Future_Void, Fail_getLvalue) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { ASSERT_THROWS_failStatus(fut.get()); }); + FUTURE_FAIL_TEST<void>( + [](/*Future<void>*/ auto&& fut) { ASSERT_THROWS_failStatus(fut.get()); }); } TEST(Future_Void, Fail_getConstLvalue) { - FUTURE_FAIL_TEST<void>([](const Future<void>& fut) { ASSERT_THROWS_failStatus(fut.get()); }); + FUTURE_FAIL_TEST<void>( + [](const /*Future<void>*/ auto& fut) { ASSERT_THROWS_failStatus(fut.get()); }); } TEST(Future_Void, Fail_getRvalue) { FUTURE_FAIL_TEST<void>( - [](Future<void>&& fut) { ASSERT_THROWS_failStatus(std::move(fut).get()); }); + [](/*Future<void>*/ auto&& fut) { ASSERT_THROWS_failStatus(std::move(fut).get()); }); } TEST(Future_Void, Fail_getNothrowLvalue) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus()); }); + FUTURE_FAIL_TEST<void>( + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus()); }); } TEST(Future_Void, Fail_getNothrowConstLvalue) { FUTURE_FAIL_TEST<void>( - [](const Future<void>& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus()); }); + [](const /*Future<void>*/ auto& fut) { ASSERT_EQ(fut.getNoThrow(), failStatus()); }); } TEST(Future_Void, Fail_getNothrowRvalue) { FUTURE_FAIL_TEST<void>( - [](Future<void>&& fut) { ASSERT_EQ(std::move(fut).getNoThrow(), failStatus()); }); + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut).getNoThrow(), failStatus()); }); } -TEST(Future_Void, Fail_share_getRvalue) { +TEST(Future_Void, Fail_semi_get) { FUTURE_FAIL_TEST<void>( - [](Future<void>&& fut) { ASSERT_THROWS_failStatus(std::move(fut).share().get()); }); + [](/*Future<void>*/ auto&& fut) { ASSERT_THROWS_failStatus(std::move(fut).semi().get()); }); +} + +TEST(Future_Void, Fail_share_getRvalue) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { + ASSERT_THROWS_failStatus(std::move(fut).share().get()); + }); } TEST(Future_Void, Fail_share_getNothrow) { - FUTURE_FAIL_TEST<void>( - [](Future<void>&& fut) { ASSERT_EQ(std::move(fut).share().getNoThrow(), failStatus()); }); + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { + ASSERT_EQ(std::move(fut).share().getNoThrow(), failStatus()); + }); } TEST(Future_Void, Fail_getAsync) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { auto pf = makePromiseFuture<void>(); std::move(fut).getAsync([outside = std::move(pf.promise)](Status status) mutable { ASSERT(!status.isOK()); @@ -140,7 +156,7 @@ TEST(Future_Void, Fail_getAsync) { TEST(Future_Void, Success_isReady) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { const auto id = stdx::this_thread::get_id(); while (!fut.isReady()) { } @@ -153,7 +169,7 @@ TEST(Future_Void, Success_isReady) { } TEST(Future_Void, Fail_isReady) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { const auto id = stdx::this_thread::get_id(); while (!fut.isReady()) { } @@ -188,42 +204,46 @@ TEST(Future_Void, isReady_share_TSAN_OK) { } TEST(Future_Void, Success_thenSimple) { - FUTURE_SUCCESS_TEST( - [] {}, - [](Future<void>&& fut) { ASSERT_EQ(std::move(fut).then([]() { return 3; }).get(), 3); }); + FUTURE_SUCCESS_TEST([] {}, + [](/*Future<void>*/ auto&& fut) { + ASSERT_EQ(std::move(fut).then([]() { return 3; }).get(), 3); + }); } TEST(Future_Void, Success_thenVoid) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut).then([] {}).then([] { return 3; }).get(), 3); }); } TEST(Future_Void, Success_thenStatus) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut).then([] {}).then([] { return 3; }).get(), 3); }); } TEST(Future_Void, Success_thenError_Status) { - FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { - auto fut2 = std::move(fut).then( - []() { return Status(ErrorCodes::BadValue, "oh no!"); }); - MONGO_STATIC_ASSERT(std::is_same<decltype(fut2), Future<void>>::value); - ASSERT_EQ(fut2.getNoThrow(), ErrorCodes::BadValue); - }); + FUTURE_SUCCESS_TEST( + [] {}, + [](/*Future<void>*/ auto&& fut) { + auto fut2 = + std::move(fut).then([]() { return Status(ErrorCodes::BadValue, "oh no!"); }); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert(std::is_same_v<typename decltype(fut2)::value_type, void>); + ASSERT_EQ(fut2.getNoThrow(), ErrorCodes::BadValue); + }); } TEST(Future_Void, Success_thenError_StatusWith) { FUTURE_SUCCESS_TEST( [] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { auto fut2 = std::move(fut).then( []() { return StatusWith<double>(ErrorCodes::BadValue, "oh no!"); }); - MONGO_STATIC_ASSERT(std::is_same<decltype(fut2), Future<double>>::value); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert(std::is_same_v<typename decltype(fut2)::value_type, double>); ASSERT_EQ(fut2.getNoThrow(), ErrorCodes::BadValue); }); } @@ -231,14 +251,14 @@ TEST(Future_Void, Success_thenError_StatusWith) { TEST(Future_Void, Success_thenFutureImmediate) { FUTURE_SUCCESS_TEST( [] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut).then([]() { return Future<int>::makeReady(3); }).get(), 3); }); } TEST(Future_Void, Success_thenFutureReady) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([]() { auto pf = makePromiseFuture<int>(); @@ -253,13 +273,22 @@ TEST(Future_Void, Success_thenFutureReady) { TEST(Future_Void, Success_thenFutureAsync) { FUTURE_SUCCESS_TEST( [] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut).then([]() { return async([] { return 3; }); }).get(), 3); }); } +TEST(Future_Void, Success_thenSemiFutureAsync) { + FUTURE_SUCCESS_TEST( + [] {}, + [](/*Future<void>*/ auto&& fut) { + ASSERT_EQ(std::move(fut).then([]() { return async([] { return 3; }).semi(); }).get(), + 3); + }); +} + TEST(Future_Void, Fail_thenSimple) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([]() { FAIL("then() callback was called"); @@ -271,7 +300,7 @@ TEST(Future_Void, Fail_thenSimple) { } TEST(Future_Void, Fail_thenFutureAsync) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .then([]() { FAIL("then() callback was called"); @@ -284,7 +313,7 @@ TEST(Future_Void, Fail_thenFutureAsync) { TEST(Future_Void, Success_onErrorSimple) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ( std::move(fut) .onError([](Status) { FAIL("onError() callback was called"); }) @@ -296,7 +325,7 @@ TEST(Future_Void, Success_onErrorSimple) { TEST(Future_Void, Success_onErrorFutureAsync) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status) { FAIL("onError() callback was called"); @@ -309,7 +338,7 @@ TEST(Future_Void, Success_onErrorFutureAsync) { } TEST(Future_Void, Fail_onErrorSimple) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); }) .then([] { return 3; }) @@ -319,7 +348,7 @@ TEST(Future_Void, Fail_onErrorSimple) { } TEST(Future_Void, Fail_onErrorError_throw) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { auto fut2 = std::move(fut).onError([](Status s) { ASSERT_EQ(s, failStatus()); uasserted(ErrorCodes::BadValue, "oh no!"); @@ -329,7 +358,7 @@ TEST(Future_Void, Fail_onErrorError_throw) { } TEST(Future_Void, Fail_onErrorError_Status) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { auto fut2 = std::move(fut).onError([](Status s) { ASSERT_EQ(s, failStatus()); return Status(ErrorCodes::BadValue, "oh no!"); @@ -339,7 +368,7 @@ TEST(Future_Void, Fail_onErrorError_Status) { } TEST(Future_Void, Fail_onErrorFutureImmediate) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); @@ -352,7 +381,7 @@ TEST(Future_Void, Fail_onErrorFutureImmediate) { } TEST(Future_Void, Fail_onErrorFutureReady) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); @@ -367,7 +396,7 @@ TEST(Future_Void, Fail_onErrorFutureReady) { } TEST(Future_Void, Fail_onErrorFutureAsync) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onError([&](Status s) { ASSERT_EQ(s, failStatus()); @@ -381,9 +410,9 @@ TEST(Future_Void, Fail_onErrorFutureAsync) { TEST(Future_Void, Success_onErrorCode) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) - .onError<ErrorCodes::InternalError>([](Status) { + .template onError<ErrorCodes::InternalError>([](Status) { FAIL("onError<code>() callback was called"); }) .then([] { return 3; }) @@ -393,30 +422,31 @@ TEST(Future_Void, Success_onErrorCode) { } TEST(Future_Void, Fail_onErrorCodeMatch) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { bool called = false; - auto res = std::move(fut) - .onError([](Status s) { - ASSERT_EQ(s, failStatus()); - return Status(ErrorCodes::InternalError, ""); - }) - .onError<ErrorCodes::InternalError>([&](Status&&) { called = true; }) - .then([] { return 3; }) - .getNoThrow(); + auto res = + std::move(fut) + .onError([](Status s) { + ASSERT_EQ(s, failStatus()); + return Status(ErrorCodes::InternalError, ""); + }) + .template onError<ErrorCodes::InternalError>([&](Status&&) { called = true; }) + .then([] { return 3; }) + .getNoThrow(); ASSERT_EQ(res, 3); ASSERT(called); }); } TEST(Future_Void, Fail_onErrorCodeMatchFuture) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { bool called = false; auto res = std::move(fut) .onError([](Status s) { ASSERT_EQ(s, failStatus()); return Status(ErrorCodes::InternalError, ""); }) - .onError<ErrorCodes::InternalError>([&](Status&&) { + .template onError<ErrorCodes::InternalError>([&](Status&&) { called = true; return Future<void>::makeReady(); }) @@ -428,9 +458,9 @@ TEST(Future_Void, Fail_onErrorCodeMatchFuture) { } TEST(Future_Void, Fail_onErrorCodeMismatch) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) - .onError<ErrorCodes::InternalError>( + .template onError<ErrorCodes::InternalError>( [](Status s) { FAIL("Why was this called?") << s; }) .onError([](Status s) { ASSERT_EQ(s, failStatus()); }) .then([] { return 3; }) @@ -440,9 +470,9 @@ TEST(Future_Void, Fail_onErrorCodeMismatch) { } TEST(Future_Void, Success_tap) { - FUTURE_SUCCESS_TEST( + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsTap>( [] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { bool tapCalled = false; ASSERT_EQ( std::move(fut).tap([&tapCalled] { tapCalled = true; }).then([] { return 3; }).get(), @@ -452,9 +482,9 @@ TEST(Future_Void, Success_tap) { } TEST(Future_Void, Success_tapError) { - FUTURE_SUCCESS_TEST( + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsTap>( [] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .tapError([](Status s) { FAIL("tapError() callback was called"); }) .then([] { return 3; }) @@ -464,43 +494,24 @@ TEST(Future_Void, Success_tapError) { } TEST(Future_Void, Success_tapAll_StatusWith) { - FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { - bool tapCalled = false; - ASSERT_EQ(std::move(fut) - .tapAll([&tapCalled](Status s) { - ASSERT_OK(s); - tapCalled = true; - }) - .then([] { return 3; }) - .get(), - 3); - ASSERT(tapCalled); - }); -} - -TEST(Future_Void, Success_tapAll_Overloaded) { - FUTURE_SUCCESS_TEST( - [] {}, - [](Future<void>&& fut) { - struct Callback { - void operator()() { - called = true; - } - void operator()(Status status) { - FAIL("Status overload called with ") << status; - } - bool called = false; - }; - Callback callback; - - ASSERT_EQ(std::move(fut).tapAll(std::ref(callback)).then([] { return 3; }).get(), 3); - ASSERT(callback.called); - }); + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsTap>([] {}, + [](/*Future<void>*/ auto&& fut) { + bool tapCalled = false; + ASSERT_EQ( + std::move(fut) + .tapAll([&tapCalled](Status s) { + ASSERT_OK(s); + tapCalled = true; + }) + .then([] { return 3; }) + .get(), + 3); + ASSERT(tapCalled); + }); } TEST(Future_Void, Fail_tap) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void, kNoExecutorFuture_needsTap>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .tap([] { FAIL("tap() callback was called"); }) .onError([](Status s) { ASSERT_EQ(s, failStatus()); }) @@ -511,7 +522,7 @@ TEST(Future_Void, Fail_tap) { } TEST(Future_Void, Fail_tapError) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void, kNoExecutorFuture_needsTap>([](/*Future<void>*/ auto&& fut) { bool tapCalled = false; ASSERT_EQ(std::move(fut) .tapError([&tapCalled](Status s) { @@ -527,11 +538,11 @@ TEST(Future_Void, Fail_tapError) { } TEST(Future_Void, Fail_tapAll_StatusWith) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void, kNoExecutorFuture_needsTap>([](/*Future<void>*/ auto&& fut) { bool tapCalled = false; ASSERT_EQ(std::move(fut) - .tapAll([&tapCalled](StatusWith<int> sw) { - ASSERT_EQ(sw.getStatus(), failStatus()); + .tapAll([&tapCalled](Status sw) { + ASSERT_EQ(sw, failStatus()); tapCalled = true; }) .onError([](Status s) { ASSERT_EQ(s, failStatus()); }) @@ -542,34 +553,9 @@ TEST(Future_Void, Fail_tapAll_StatusWith) { }); } -TEST(Future_Void, Fail_tapAll_Overloaded) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { - struct Callback { - void operator()() { - FAIL("() overload called"); - } - void operator()(Status status) { - ASSERT_EQ(status, failStatus()); - called = true; - } - bool called = false; - }; - Callback callback; - - ASSERT_EQ(std::move(fut) - .tapAll(std::ref(callback)) - .onError([](Status s) { ASSERT_EQ(s, failStatus()); }) - .then([] { return 3; }) - .get(), - 3); - - ASSERT(callback.called); - }); -} - TEST(Future_Void, Success_onCompletionSimple) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](Status status) { ASSERT_OK(status); @@ -582,7 +568,7 @@ TEST(Future_Void, Success_onCompletionSimple) { TEST(Future_Void, Success_onCompletionVoid) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](Status status) { ASSERT_OK(status); }) .then([]() { return 3; }) @@ -593,32 +579,35 @@ TEST(Future_Void, Success_onCompletionVoid) { TEST(Future_Void, Success_onCompletionError_Status) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { auto fut2 = std::move(fut).onCompletion([](Status status) { ASSERT_OK(status); return Status(ErrorCodes::BadValue, "oh no!"); }); - MONGO_STATIC_ASSERT(std::is_same<decltype(fut2), Future<void>>::value); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert( + std::is_same_v<typename decltype(fut2)::value_type, void>); ASSERT_EQ(fut2.getNoThrow(), ErrorCodes::BadValue); }); } TEST(Future_Void, Success_onCompletionError_StatusWith) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { auto fut2 = std::move(fut).onCompletion([](Status status) { ASSERT_OK(status); return StatusWith<double>(ErrorCodes::BadValue, "oh no!"); }); - MONGO_STATIC_ASSERT( - std::is_same<decltype(fut2), Future<double>>::value); + static_assert(future_details::isFutureLike<decltype(fut2)>); + static_assert( + std::is_same_v<typename decltype(fut2)::value_type, double>); ASSERT_EQ(fut2.getNoThrow(), ErrorCodes::BadValue); }); } TEST(Future_Void, Success_onCompletionFutureImmediate) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](Status status) { ASSERT_OK(status); @@ -631,7 +620,7 @@ TEST(Future_Void, Success_onCompletionFutureImmediate) { TEST(Future_Void, Success_onCompletionFutureReady) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](Status status) { ASSERT_OK(status); @@ -646,7 +635,7 @@ TEST(Future_Void, Success_onCompletionFutureReady) { TEST(Future_Void, Success_onCompletionFutureAsync) { FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { + [](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](Status status) { ASSERT_OK(status); @@ -658,7 +647,7 @@ TEST(Future_Void, Success_onCompletionFutureAsync) { } TEST(Future_Void, Fail_onCompletionSimple) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](Status s) { ASSERT_EQ(s, failStatus()); }) .then([] { return 3; }) @@ -668,7 +657,7 @@ TEST(Future_Void, Fail_onCompletionSimple) { } TEST(Future_Void, Fail_onCompletionError_throw) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { auto fut2 = std::move(fut).onCompletion([](Status s) { ASSERT_EQ(s, failStatus()); uasserted(ErrorCodes::BadValue, "oh no!"); @@ -678,7 +667,7 @@ TEST(Future_Void, Fail_onCompletionError_throw) { } TEST(Future_Void, Fail_onCompletionError_Status) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { auto fut2 = std::move(fut).onCompletion([](Status s) { ASSERT_EQ(s, failStatus()); return Status(ErrorCodes::BadValue, "oh no!"); @@ -688,7 +677,7 @@ TEST(Future_Void, Fail_onCompletionError_Status) { } TEST(Future_Void, Fail_onCompletionFutureImmediate) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](Status s) { ASSERT_EQ(s, failStatus()); @@ -701,7 +690,7 @@ TEST(Future_Void, Fail_onCompletionFutureImmediate) { } TEST(Future_Void, Fail_onCompletionFutureReady) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([](Status s) { ASSERT_EQ(s, failStatus()); @@ -716,7 +705,7 @@ TEST(Future_Void, Fail_onCompletionFutureReady) { } TEST(Future_Void, Fail_onCompletionFutureAsync) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void>([](/*Future<void>*/ auto&& fut) { ASSERT_EQ(std::move(fut) .onCompletion([&](Status s) { ASSERT_EQ(s, failStatus()); diff --git a/src/mongo/util/future_test_promise_int.cpp b/src/mongo/util/future_test_promise_int.cpp index 7d371a4358d..a74fc1e52e4 100644 --- a/src/mongo/util/future_test_promise_int.cpp +++ b/src/mongo/util/future_test_promise_int.cpp @@ -41,16 +41,17 @@ namespace mongo { namespace { TEST(Promise, Success_setFrom) { - FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { - auto pf = makePromiseFuture<int>(); - pf.promise.setFrom(std::move(fut)); - ASSERT_EQ(std::move(pf.future).get(), 1); - }); + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsPromiseSetFrom>( + [] { return 1; }, + [](/*Future<int>*/ auto&& fut) { + auto pf = makePromiseFuture<int>(); + pf.promise.setFrom(std::move(fut)); + ASSERT_EQ(std::move(pf.future).get(), 1); + }); } TEST(Promise, Fail_setFrom) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int, kNoExecutorFuture_needsPromiseSetFrom>([](/*Future<int>*/ auto&& fut) { auto pf = makePromiseFuture<int>(); pf.promise.setFrom(std::move(fut)); ASSERT_THROWS_failStatus(std::move(pf.future).get()); @@ -85,16 +86,17 @@ TEST(Promise, Fail_setWith_StatusWith) { } TEST(Promise, Success_setWith_Future) { - FUTURE_SUCCESS_TEST([] { return 1; }, - [](Future<int>&& fut) { - auto pf = makePromiseFuture<int>(); - pf.promise.setWith([&] { return std::move(fut); }); - ASSERT_EQ(std::move(pf.future).get(), 1); - }); + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsPromiseSetFrom>( + [] { return 1; }, + [](/*Future<int>*/ auto&& fut) { + auto pf = makePromiseFuture<int>(); + pf.promise.setWith([&] { return std::move(fut); }); + ASSERT_EQ(std::move(pf.future).get(), 1); + }); } TEST(Promise, Fail_setWith_Future) { - FUTURE_FAIL_TEST<int>([](Future<int>&& fut) { + FUTURE_FAIL_TEST<int, kNoExecutorFuture_needsPromiseSetFrom>([](/*Future<int>*/ auto&& fut) { auto pf = makePromiseFuture<int>(); pf.promise.setWith([&] { return std::move(fut); }); ASSERT_THROWS_failStatus(std::move(pf.future).get()); diff --git a/src/mongo/util/future_test_promise_void.cpp b/src/mongo/util/future_test_promise_void.cpp index 32fa8c3efc9..3ef9731e7a7 100644 --- a/src/mongo/util/future_test_promise_void.cpp +++ b/src/mongo/util/future_test_promise_void.cpp @@ -41,16 +41,18 @@ namespace mongo { namespace { TEST(Promise_void, Success_setFrom) { - FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { - auto pf = makePromiseFuture<void>(); - pf.promise.setFrom(std::move(fut)); - ASSERT_OK(std::move(pf.future).getNoThrow()); - }); + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsPromiseSetFrom>( + [] {}, + [](/*Future<void>*/ auto&& fut) { + // This intentionally doesn't work with ExecutorFuture. + auto pf = makePromiseFuture<void>(); + pf.promise.setFrom(std::move(fut)); + ASSERT_OK(std::move(pf.future).getNoThrow()); + }); } TEST(Promise_void, Fail_setFrom) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void, kNoExecutorFuture_needsPromiseSetFrom>([](/*Future<void>*/ auto&& fut) { auto pf = makePromiseFuture<void>(); pf.promise.setFrom(std::move(fut)); ASSERT_THROWS_failStatus(std::move(pf.future).get()); @@ -82,16 +84,17 @@ TEST(Promise_void, Fail_setWith_Status) { } TEST(Promise_void, Success_setWith_Future) { - FUTURE_SUCCESS_TEST([] {}, - [](Future<void>&& fut) { - auto pf = makePromiseFuture<void>(); - pf.promise.setWith([&] { return std::move(fut); }); - ASSERT_OK(std::move(pf.future).getNoThrow()); - }); + FUTURE_SUCCESS_TEST<kNoExecutorFuture_needsPromiseSetFrom>( + [] {}, + [](/*Future<void>*/ auto&& fut) { + auto pf = makePromiseFuture<void>(); + pf.promise.setWith([&] { return std::move(fut); }); + ASSERT_OK(std::move(pf.future).getNoThrow()); + }); } TEST(Promise_void, Fail_setWith_Future) { - FUTURE_FAIL_TEST<void>([](Future<void>&& fut) { + FUTURE_FAIL_TEST<void, kNoExecutorFuture_needsPromiseSetFrom>([](/*Future<void>*/ auto&& fut) { auto pf = makePromiseFuture<void>(); pf.promise.setWith([&] { return std::move(fut); }); ASSERT_THROWS_failStatus(std::move(pf.future).get()); diff --git a/src/mongo/util/future_test_utils.h b/src/mongo/util/future_test_utils.h index c9f1641efad..a51d86445d6 100644 --- a/src/mongo/util/future_test_utils.h +++ b/src/mongo/util/future_test_utils.h @@ -41,6 +41,40 @@ namespace mongo { +enum DoExecutorFuture : bool { + // Force exemptions to say *why* they shouldn't test ExecutorFuture to ensure that if the + // reason stops applying (eg, if we implement ExecutorFuture::tap()) we can delete the enum + // value and recover the test coverage. + kNoExecutorFuture_needsTap = false, + kNoExecutorFuture_needsPromiseSetFrom = false, + kDoExecutorFuture = true, +}; + +class InlineCountingExecutor final : public OutOfLineExecutor { +public: + void schedule(Task task) noexcept override { + task(Status::OK()); + tasksRun.fetchAndAdd(1); + } + + static auto make() { + return std::make_shared<InlineCountingExecutor>(); + } + + AtomicWord<int32_t> tasksRun{0}; +}; + +class RejectingExecutor final : public OutOfLineExecutor { +public: + void schedule(Task task) noexcept override { + task(Status(ErrorCodes::ShutdownInProgress, "")); + } + + static auto make() { + return std::make_shared<RejectingExecutor>(); + } +}; + template <typename T, typename Func> void completePromise(Promise<T>* promise, Func&& func) { promise->emplaceValue(func()); @@ -88,7 +122,8 @@ inline Status failStatus() { // Tests a Future completed by completionExpr using testFunc. The Future will be completed in // various ways to maximize test coverage. -template <typename CompletionFunc, +template <DoExecutorFuture doExecutorFuture = kDoExecutorFuture, + typename CompletionFunc, typename TestFunc, typename = std::enable_if_t<!std::is_void<std::result_of_t<CompletionFunc()>>::value>> void FUTURE_SUCCESS_TEST(const CompletionFunc& completion, const TestFunc& test) { @@ -105,9 +140,15 @@ void FUTURE_SUCCESS_TEST(const CompletionFunc& completion, const TestFunc& test) { // async future test(async([&] { return completion(); })); } + + IF_CONSTEXPR(doExecutorFuture) { // immediate executor future + auto exec = InlineCountingExecutor::make(); + test(Future<CompletionType>::makeReady(completion()).thenRunOn(exec)); + } } -template <typename CompletionFunc, +template <DoExecutorFuture doExecutorFuture = kDoExecutorFuture, + typename CompletionFunc, typename TestFunc, typename = std::enable_if_t<std::is_void<std::result_of_t<CompletionFunc()>>::value>, typename = void> @@ -127,9 +168,17 @@ void FUTURE_SUCCESS_TEST(const CompletionFunc& completion, const TestFunc& test) { // async future test(async([&] { return completion(); })); } + + IF_CONSTEXPR(doExecutorFuture) { // immediate executor future + completion(); + auto exec = InlineCountingExecutor::make(); + test(Future<CompletionType>::makeReady().thenRunOn(exec)); + } } -template <typename CompletionType, typename TestFunc> +template <typename CompletionType, + DoExecutorFuture doExecutorFuture = kDoExecutorFuture, + typename TestFunc> void FUTURE_FAIL_TEST(const TestFunc& test) { { // immediate future test(Future<CompletionType>::makeReady(failStatus())); @@ -146,5 +195,9 @@ void FUTURE_FAIL_TEST(const TestFunc& test) { MONGO_UNREACHABLE; })); } + IF_CONSTEXPR(doExecutorFuture) { // immediate executor future + auto exec = InlineCountingExecutor::make(); + test(Future<CompletionType>::makeReady(failStatus()).thenRunOn(exec)); + } } } // namespace mongo diff --git a/src/mongo/util/if_constexpr.h b/src/mongo/util/if_constexpr.h new file mode 100644 index 00000000000..bf1dbdf8867 --- /dev/null +++ b/src/mongo/util/if_constexpr.h @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +// Terrible hack to work around clang-format being out of date. +// TODO sed this away and delete this file when we upgrade clang-format. +#define IF_CONSTEXPR \ + if \ + constexpr diff --git a/src/mongo/util/out_of_line_executor.h b/src/mongo/util/out_of_line_executor.h index 367ee700fcc..8371d878e3f 100644 --- a/src/mongo/util/out_of_line_executor.h +++ b/src/mongo/util/out_of_line_executor.h @@ -30,8 +30,7 @@ #pragma once #include "mongo/base/status.h" -#include "mongo/stdx/functional.h" -#include "mongo/util/future.h" +#include "mongo/util/functional.h" namespace mongo { @@ -39,11 +38,16 @@ namespace mongo { * Provides the minimal api for a simple out of line executor that can run non-cancellable * callbacks. * - * Adds in a minimal amount of support for futures. - * * The contract for scheduling work on an executor is that it never blocks the caller. It doesn't * necessarily need to offer forward progress guarantees, but actual calls to schedule() should not * deadlock. + * + * If you manage the lifetime of your executor using a shared_ptr, you can begin a chain of + * execution like this: + * ExecutorFuture(myExec) + * .then([] { return doThing1(); }) + * .then([] { return doThing2(); }) + * ... */ class OutOfLineExecutor { public: @@ -51,26 +55,6 @@ public: public: /** - * Invokes the callback on the executor, as in schedule(), returning a future with its result. - * That future may be ready by the time the caller returns, which means that continuations - * chained on the returned future may be invoked on the caller of execute's stack. - */ - template <typename Callback> - Future<FutureContinuationResult<Callback>> execute(Callback&& cb) { - auto[promise, future] = makePromiseFuture<FutureContinuationResult<Callback>>(); - - schedule([ cb = std::forward<Callback>(cb), p = std::move(promise) ](auto status) mutable { - if (!status.isOK()) { - p.setError(status); - return; - } - p.setWith(std::move(cb)); - }); - - return std::move(future); - } - - /** * Delegates invocation of the Task to this executor * * Execution of the Task can happen in one of three contexts: @@ -90,4 +74,6 @@ protected: ~OutOfLineExecutor() noexcept {} }; +using ExecutorPtr = std::shared_ptr<OutOfLineExecutor>; + } // namespace mongo |