summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathias Stearn <mathias@10gen.com>2019-03-21 19:01:26 -0400
committerMathias Stearn <mathias@10gen.com>2019-04-11 18:38:43 -0400
commitf0f894395d2676c5b93fc99a978970f0ffce8127 (patch)
tree14621a1b7d22728a4e644288019de848b9d87e28
parent342e6f115ee06cfa226a3c3006f87d9bd50bda8e (diff)
downloadmongo-f0f894395d2676c5b93fc99a978970f0ffce8127.tar.gz
SERVER-36359 Introduce SemiFuture<T> and ExecutorFuture<T>
-rw-r--r--src/mongo/client/remote_command_targeter.h4
-rw-r--r--src/mongo/client/remote_command_targeter_factory_mock.cpp4
-rw-r--r--src/mongo/client/remote_command_targeter_mock.cpp2
-rw-r--r--src/mongo/client/remote_command_targeter_mock.h4
-rw-r--r--src/mongo/client/remote_command_targeter_rs.cpp2
-rw-r--r--src/mongo/client/remote_command_targeter_rs.h4
-rw-r--r--src/mongo/client/remote_command_targeter_standalone.cpp2
-rw-r--r--src/mongo/client/remote_command_targeter_standalone.h4
-rw-r--r--src/mongo/client/replica_set_monitor.cpp6
-rw-r--r--src/mongo/client/replica_set_monitor.h4
-rw-r--r--src/mongo/executor/thread_pool_task_executor.cpp2
-rw-r--r--src/mongo/executor/thread_pool_task_executor.h2
-rw-r--r--src/mongo/util/SConscript5
-rw-r--r--src/mongo/util/future.h514
-rw-r--r--src/mongo/util/future_impl.h160
-rw-r--r--src/mongo/util/future_test_edge_cases.cpp106
-rw-r--r--src/mongo/util/future_test_executor_future.cpp202
-rw-r--r--src/mongo/util/future_test_future_int.cpp403
-rw-r--r--src/mongo/util/future_test_future_move_only.cpp300
-rw-r--r--src/mongo/util/future_test_future_void.cpp291
-rw-r--r--src/mongo/util/future_test_promise_int.cpp30
-rw-r--r--src/mongo/util/future_test_promise_void.cpp31
-rw-r--r--src/mongo/util/future_test_utils.h59
-rw-r--r--src/mongo/util/if_constexpr.h36
-rw-r--r--src/mongo/util/out_of_line_executor.h34
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