From 4aad7bee6c9605480c55cd6fe274fc693654b7bc Mon Sep 17 00:00:00 2001 From: Matt Diener Date: Thu, 26 May 2022 16:31:24 +0000 Subject: SERVER-66128 Add Policy to Future Func-using API --- src/mongo/util/future.h | 239 +++++++++++++++++++++++++++++++------------ src/mongo/util/future_impl.h | 110 ++++++++++++++------ 2 files changed, 256 insertions(+), 93 deletions(-) (limited to 'src/mongo') diff --git a/src/mongo/util/future.h b/src/mongo/util/future.h index cfdb724a101..57e560f0cf2 100644 --- a/src/mongo/util/future.h +++ b/src/mongo/util/future.h @@ -369,10 +369,11 @@ public: * * TODO decide how to handle func throwing. */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableExactR>) - void getAsync(Func&& func) && noexcept { - std::move(this->_impl).getAsync(std::forward(func)); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableExactR>&& + isFuturePolicy) + void getAsync(Policy policy, Func&& func) && noexcept { + std::move(this->_impl).getAsync(policy, std::forward(func)); } // @@ -410,10 +411,10 @@ public: * The callback takes a T and can return anything (see above for how Statusy and Futurey returns * are handled.) */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallable) - /*see above*/ auto then(Func&& func) && noexcept { - return wrap(std::move(this->_impl).then(std::forward(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallable&& isFuturePolicy) + /*see above*/ auto then(Policy policy, Func&& func) && noexcept { + return wrap(std::move(this->_impl).then(policy, std::forward(func))); } /** @@ -423,10 +424,11 @@ public: * The callback takes a StatusOrStatusWith and can return anything (see above for how Statusy * and Futurey returns are handled.) */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallable>) - /*see above*/ auto onCompletion(Func&& func) && noexcept { - return wrap(std::move(this->_impl).onCompletion(std::forward(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallable>&& isFuturePolicy) + /*see above*/ auto onCompletion(Policy policy, Func&& func) && noexcept { + return wrap( + std::move(this->_impl).onCompletion(policy, std::forward(func))); } /** @@ -442,10 +444,10 @@ public: * 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) - REQUIRES(future_details::isCallableR) - /*see above*/ auto onError(Func&& func) && noexcept { - return wrap(std::move(this->_impl).onError(std::forward(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableR&& isFuturePolicy) + /*see above*/ auto onError(Policy policy, Func&& func) && noexcept { + return wrap(std::move(this->_impl).onError(policy, std::forward(func))); } /** @@ -455,11 +457,11 @@ public: * 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) - REQUIRES(future_details::isCallableR) - /*see above*/ auto onError(Func&& func) && noexcept { + TEMPLATE(ErrorCodes::Error code, typename Policy, typename Func) + REQUIRES(future_details::isCallableR&& isFuturePolicy) + /*see above*/ auto onError(Policy policy, Func&& func) && noexcept { return wrap( - std::move(this->_impl).template onError(std::forward(func))); + std::move(this->_impl).template onError(policy, std::forward(func))); } /** @@ -469,11 +471,12 @@ public: * 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) - REQUIRES(future_details::isCallableR) - /*see above*/ auto onErrorCategory(Func&& func) && noexcept { + TEMPLATE(ErrorCategory category, typename Policy, typename Func) + REQUIRES(future_details::isCallableR&& isFuturePolicy) + /*see above*/ auto onErrorCategory(Policy policy, Func&& func) && noexcept { return wrap( - std::move(this->_impl).template onErrorCategory(std::forward(func))); + std::move(this->_impl) + .template onErrorCategory(policy, std::forward(func))); } // @@ -496,10 +499,10 @@ public: * * The callback takes a const T& and must return void. */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableExactR) - Future tap(Func&& func) && noexcept { - return Future(std::move(this->_impl).tap(std::forward(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableExactR&& isFuturePolicy) + Future tap(Policy policy, Func&& func) && noexcept { + return Future(std::move(this->_impl).tap(policy, std::forward(func))); } /** @@ -509,10 +512,10 @@ public: * * The callback takes a non-OK Status and must return void. */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableExactR) - Future tapError(Func&& func) && noexcept { - return Future(std::move(this->_impl).tapError(std::forward(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableExactR&& isFuturePolicy) + Future tapError(Policy policy, Func&& func) && noexcept { + return Future(std::move(this->_impl).tapError(policy, std::forward(func))); } /** @@ -523,10 +526,11 @@ public: * * The callback takes a StatusOrStatusWith and must return void. */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableExactR>) - Future tapAll(Func&& func) && noexcept { - return Future(std::move(this->_impl).tapAll(std::forward(func))); + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableExactR>&& + isFuturePolicy) + Future tapAll(Policy policy, Func&& func) && noexcept { + return Future(std::move(this->_impl).tapAll(policy, std::forward(func))); } // Calling this on a Future is never strictly necessary, since this is already an inline @@ -534,6 +538,59 @@ public: // unsafeToInlineFuture for some future types but not others. using SemiFuture::unsafeToInlineFuture; + /** + * Providing a Policy to continuation functions is optional. All callers should eventually + * use the default policy by removing the tag altogether. + * + * TODO(SERVER-66126): Remove all tag-taking methods and associated default implementations. + */ + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableExactR>) + void getAsync(Func&& func) && noexcept { + std::move(*this).getAsync(destroyDefault, std::forward(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallable) + auto then(Func&& func) && noexcept { + return std::move(*this).then(destroyDefault, std::forward(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallable>) + auto onCompletion(Func&& func) && noexcept { + return std::move(*this).onCompletion(destroyDefault, std::forward(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableR) + auto onError(Func&& func) && noexcept { + return std::move(*this).onError(destroyDefault, std::forward(func)); + } + TEMPLATE(ErrorCodes::Error code, typename Func) + REQUIRES(future_details::isCallableR) + auto onError(Func&& func) && noexcept { + return std::move(*this).template onError(destroyDefault, std::forward(func)); + } + TEMPLATE(ErrorCategory category, typename Func) + REQUIRES(future_details::isCallableR) + auto onErrorCategory(Func&& func) && noexcept { + return std::move(*this).template onErrorCategory(destroyDefault, + std::forward(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableExactR) + Future tap(Func&& func) && noexcept { + return std::move(*this).tap(destroyDefault, std::forward(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableExactR) + Future tapError(Func&& func) && noexcept { + return std::move(*this).tapError(destroyDefault, std::forward(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableExactR>) + Future tapAll(Func&& func) && noexcept { + return std::move(*this).tapAll(destroyDefault, std::forward(func)); + } + private: template friend class ExecutorFuture; @@ -636,15 +693,16 @@ public: * Attach a completion callback to asynchronously consume this `ExecutorFuture`'s result. * \see `Future::getAsync()`. */ - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableExactR>) - void getAsync(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableExactR>&& + isFuturePolicy) + void getAsync(Policy policy, Func&& func) && noexcept { static_assert(std::is_void_v>()))>, "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([ + std::move(this->_impl).getAsync(policy, [ exec = std::move(_exec), // Unlike wrapCB this can move because we won't need it later. func = std::forward(func) ](StatusOrStatusWith arg) mutable noexcept { @@ -656,46 +714,50 @@ public: }); } - TEMPLATE(typename Func) - REQUIRES(future_details::isCallable) - auto then(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallable&& isFuturePolicy) + auto then(Policy policy, Func&& func) && noexcept { return mongo::ExecutorFuture( - std::move(_exec), std::move(this->_impl).then(wrapCB(std::forward(func)))); + std::move(_exec), + std::move(this->_impl).then(policy, wrapCB(policy, std::forward(func)))); } - TEMPLATE(typename Func) - REQUIRES(future_details::isCallable>) - auto onCompletion(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallable>&& isFuturePolicy) + auto onCompletion(Policy policy, Func&& func) && noexcept { return mongo::ExecutorFuture( std::move(_exec), std::move(this->_impl) - .onCompletion(wrapCB>(std::forward(func)))); + .onCompletion(policy, + wrapCB>(policy, std::forward(func)))); } - TEMPLATE(typename Func) - REQUIRES(future_details::isCallableR) - ExecutorFuture onError(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableR&& isFuturePolicy) + ExecutorFuture onError(Policy policy, Func&& func) && noexcept { return mongo::ExecutorFuture( std::move(_exec), - std::move(this->_impl).onError(wrapCB(std::forward(func)))); + std::move(this->_impl) + .onError(policy, wrapCB(policy, std::forward(func)))); } - TEMPLATE(ErrorCodes::Error code, typename Func) - REQUIRES(future_details::isCallableR) - ExecutorFuture onError(Func&& func) && noexcept { + TEMPLATE(ErrorCodes::Error code, typename Policy, typename Func) + REQUIRES(future_details::isCallableR&& isFuturePolicy) + ExecutorFuture onError(Policy policy, Func&& func) && noexcept { return mongo::ExecutorFuture( std::move(_exec), std::move(this->_impl) - .template onError(wrapCB(std::forward(func)))); + .template onError(policy, wrapCB(policy, std::forward(func)))); } - TEMPLATE(ErrorCategory category, typename Func) - REQUIRES(future_details::isCallableR) - ExecutorFuture onErrorCategory(Func&& func) && noexcept { + TEMPLATE(ErrorCategory category, typename Policy, typename Func) + REQUIRES(future_details::isCallableR&& isFuturePolicy) + ExecutorFuture onErrorCategory(Policy policy, Func&& func) && noexcept { return mongo::ExecutorFuture( std::move(_exec), std::move(this->_impl) - .template onErrorCategory(wrapCB(std::forward(func)))); + .template onErrorCategory( + policy, wrapCB(policy, std::forward(func)))); } /** @@ -706,6 +768,44 @@ public: */ using SemiFuture::unsafeToInlineFuture; + /** + * Providing a Policy to continuation functions is optional. All callers should eventually + * use the default policy by removing the tag altogether. + * + * TODO(SERVER-66126): Remove all tag-taking methods and associated default implementations. + */ + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableExactR>) + void getAsync(Func&& func) && noexcept { + std::move(*this).getAsync(destroyDefault, std::forward(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallable) + auto then(Func&& func) && noexcept { + return std::move(*this).then(destroyDefault, std::forward(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallable>) + auto onCompletion(Func&& func) && noexcept { + return std::move(*this).onCompletion(destroyDefault, std::forward(func)); + } + TEMPLATE(typename Func) + REQUIRES(future_details::isCallableR) + auto onError(Func&& func) && noexcept { + return std::move(*this).onError(destroyDefault, std::forward(func)); + } + TEMPLATE(ErrorCodes::Error code, typename Func) + REQUIRES(future_details::isCallableR) + auto onError(Func&& func) && noexcept { + return std::move(*this).template onError(destroyDefault, std::forward(func)); + } + TEMPLATE(ErrorCategory category, typename Func) + REQUIRES(future_details::isCallableR) + auto onErrorCategory(Func&& func) && noexcept { + return std::move(*this).template onErrorCategory(destroyDefault, + std::forward(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(std::move(impl)), _exec(exec) { @@ -717,8 +817,9 @@ private: * Future, then schedules a task on _exec to complete the associated promise with the result * of calling func with that argument. */ - template - auto wrapCB(Func&& func) { + TEMPLATE(typename RawArg, typename Policy, typename Func) + REQUIRES(isFuturePolicy) + auto wrapCB(Policy policy, Func&& func) { // Have to take care to never put void in argument position, since that is a hard error. using Result = typename std::conditional_t, std::invoke_result, @@ -828,10 +929,15 @@ public: * because this method will correctly propagate errors thrown from makeResult(), rather than * ErrorCodes::BrokenPromise. */ + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableR&& isFuturePolicy) + void setWith(Policy policy, Func&& func) noexcept { + setFrom(Future::makeReady().then(policy, std::forward(func))); + } TEMPLATE(typename Func) REQUIRES(future_details::isCallableR) void setWith(Func&& func) noexcept { - setFrom(Future::makeReady().then(std::forward(func))); + setWith(destroyDefault, std::forward(func)); } /** @@ -1116,10 +1222,15 @@ public: return SharedSemiFuture(_sharedState); } + TEMPLATE(typename Policy, typename Func) + REQUIRES(future_details::isCallableR&& isFuturePolicy) + void setWith(Policy policy, Func&& func) noexcept { + setFrom(Future::makeReady().then(policy, std::forward(func))); + } TEMPLATE(typename Func) REQUIRES(future_details::isCallableR) void setWith(Func&& func) noexcept { - setFrom(Future::makeReady().then(std::forward(func))); + setWith(destroyDefault, std::forward(func)); } /** diff --git a/src/mongo/util/future_impl.h b/src/mongo/util/future_impl.h index b1ef0ffd955..a2a5d963675 100644 --- a/src/mongo/util/future_impl.h +++ b/src/mongo/util/future_impl.h @@ -52,6 +52,47 @@ namespace mongo { +struct FuturePolicy {}; + +template +inline constexpr bool isFuturePolicy = std::is_base_of_v; + +/** + * Transitional tags for specifying destruction order semantics for continuations. + * The long-term goal is to eliminate `destroyWeak` via manual review of existing + * continuations. + * A continuation can be switched to `destroyStrong` when it has been determined + * that subsequent continuations do not depend on the lifetime of its captures. + * The plan is to remove these transitional tags altogether after _all_ continuations + * have been thus converted to the strong semantics specification. + */ +template +struct CleanupFuturePolicy : FuturePolicy { + static constexpr bool strongCleanup = strongCleanupValue; +}; +using WeakFuturePolicy = CleanupFuturePolicy; +using StrongFuturePolicy = CleanupFuturePolicy; + +/** + * The passed-in continuation function may or may not be cleared + * immediately after the function runs. In some contexts the entire + * continuation chain will run and callbacks are destroyed as the stack + * unwinds. In other contexts, each stage of the continuation will destroy its + * callback immediately following execution. + */ +inline constexpr WeakFuturePolicy destroyWeak{}; + +/** + * The passed-in continuation function will always be cleared + * immediately after the function runs, and before the subsequent continuation runs. + */ +inline constexpr StrongFuturePolicy destroyStrong{}; + +/** + * Used by Future implementation details to apply a consistent default FuturePolicy. + */ +inline constexpr WeakFuturePolicy destroyDefault{}; + template class Promise; @@ -881,8 +922,9 @@ public: return _shared.getNoThrow(interruptible); } - template - void getAsync(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy) + void getAsync(Policy policy, Func&& func) && noexcept { static_assert(std::is_void>()))>::value, "func passed to getAsync must return void"); @@ -905,8 +947,9 @@ public: }); } - template - auto then(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy) + auto then(Policy policy, Func&& func) && noexcept { using Result = NormalizedCallResult; if constexpr (!isFutureLike) { return generalImpl( @@ -959,8 +1002,9 @@ public: } } - template - auto onCompletion(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy) + auto onCompletion(Policy policy, Func&& func) && noexcept { using Wrapper = StatusOrStatusWith; using Result = NormalizedCallResult>; if constexpr (!isFutureLike) { @@ -1034,8 +1078,9 @@ public: } } - template - FutureImpl> onError(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy) + FutureImpl> onError(Policy policy, Func&& func) && noexcept { using Result = NormalizedCallResult; static_assert( std::is_same>, T>::value, @@ -1088,8 +1133,9 @@ public: } } - template - FutureImpl> onError(Func&& func) && noexcept { + TEMPLATE(ErrorCodes::Error code, typename Policy, typename Func) + REQUIRES(isFuturePolicy) + FutureImpl> onError(Policy policy, Func&& func) && noexcept { using Result = NormalizedCallResult; static_assert( std::is_same_v, FakeVoidToVoid>, @@ -1100,15 +1146,17 @@ public: // TODO in C++17 with constexpr if this can be done cleaner and more efficiently by not // throwing. - return std::move(*this).onError([func = std::forward(func)](Status&& status) mutable { - if (status != code) - uassertStatusOK(status); - return throwingCall(func, std::move(status)); - }); + return std::move(*this).onError(policy, + [func = std::forward(func)](Status&& status) mutable { + if (status != code) + uassertStatusOK(status); + return throwingCall(func, std::move(status)); + }); } - template - FutureImpl> onErrorCategory(Func&& func) && noexcept { + TEMPLATE(ErrorCategory category, typename Policy, typename Func) + REQUIRES(isFuturePolicy) + FutureImpl> onErrorCategory(Policy policy, Func&& func) && noexcept { using Result = NormalizedCallResult; static_assert(std::is_same_v, FakeVoidToVoid>, "func passed to Future::onErrorCategory must return T, StatusWith, " @@ -1117,15 +1165,17 @@ public: if (_immediate || (isReady() && _shared->status.isOK())) return std::move(*this); - return std::move(*this).onError([func = std::forward(func)](Status&& status) mutable { - if (!ErrorCodes::isA(status)) - uassertStatusOK(status); - return throwingCall(func, std::move(status)); - }); + return std::move(*this).onError(policy, + [func = std::forward(func)](Status&& status) mutable { + if (!ErrorCodes::isA(status)) + uassertStatusOK(status); + return throwingCall(func, std::move(status)); + }); } - template - FutureImpl> tap(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy) + FutureImpl> tap(Policy policy, Func&& func) && noexcept { static_assert(std::is_void()))>::value, "func passed to tap must return void"); @@ -1134,8 +1184,9 @@ public: [](Func && func, const Status& status) noexcept {}); } - template - FutureImpl> tapError(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy) + FutureImpl> tapError(Policy policy, Func&& func) && noexcept { static_assert(std::is_void()))>::value, "func passed to tapError must return void"); @@ -1143,8 +1194,9 @@ public: ](Func && func, const Status& status) noexcept { call(func, status); }); } - template - FutureImpl> tapAll(Func&& func) && noexcept { + TEMPLATE(typename Policy, typename Func) + REQUIRES(isFuturePolicy) + FutureImpl> tapAll(Policy policy, Func&& func) && noexcept { static_assert( std::is_void&>()))>::value, "func passed to tapAll must return void"); @@ -1340,7 +1392,7 @@ private: template inline FutureImpl FutureImpl::ignoreValue() && noexcept { - return std::move(*this).then([](auto&&) {}); + return std::move(*this).then(destroyDefault, [](auto&&) {}); } } // namespace future_details -- cgit v1.2.1