| // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef VM_TOOLS_CONCIERGE_FUTURE_H_ |
| #define VM_TOOLS_CONCIERGE_FUTURE_H_ |
| |
| #include <atomic> |
| #include <memory> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/optional.h> |
| #include <base/run_loop.h> |
| #include <base/synchronization/condition_variable.h> |
| #include <base/task_runner.h> |
| #include <base/threading/sequenced_task_runner_handle.h> |
| |
| #include "vm_tools/concierge/apply_impl.h" |
| |
| // A future class and utilities adapted to the Chrome OS code base. It can be |
| // used to post jobs to the same/different threads, and to add async support |
| // to methods. Please refer to FutureTest_Tutorial of future_test.cc on how to |
| // use this class. |
| // |
| // * Regarding "NoReject" (|Then| vs |ThenNoReject|, |
| // and |Async| vs |AsyncNoReject|) |
| // |
| // Use the NoReject variant if there is no need to reject a promise in your |
| // function. It allows you to return the output directly without using |
| // `return vm_tools::Resolve<T>(val);`, which reduces the amount of boilerplate |
| // code. |
| // |
| // The non "NoReject" func gives the freedom to use both |Resolve| and |Reject|. |
| // |
| // * Support of std::tuple and std::array |
| // |
| // If the value type of the Future is either std::tuple or std::array, the value |
| // is unpacked automatically for the next |Then| func. See FutureTest_Tuple |
| // and FutureTest_Array for example. |
| namespace vm_tools { |
| |
| template <typename T, typename Error> |
| class Future; |
| |
| template <typename T, typename Error> |
| class Promise; |
| |
| // A struct to store both the value and the error for |Future::Get|. |
| // In a common C++ implementation of futures, this is not need as the exception |
| // is thrown if any when |Get| is called. |
| template <typename T, |
| typename Error = void, |
| class Enable1 = void, |
| class Enable2 = void> |
| struct GetResult; |
| |
| template <typename T, typename Error> |
| struct GetResult<T, |
| Error, |
| typename std::enable_if_t<!std::is_void<T>::value>, |
| typename std::enable_if_t<!std::is_void<Error>::value>> { |
| T val; |
| Error err; |
| bool rejected = false; |
| }; |
| |
| template <typename T, typename Error> |
| struct GetResult<T, |
| Error, |
| typename std::enable_if_t<std::is_void<T>::value>, |
| typename std::enable_if_t<!std::is_void<Error>::value>> { |
| Error err; |
| bool rejected = false; |
| }; |
| |
| template <typename T, typename Error> |
| struct GetResult<T, |
| Error, |
| typename std::enable_if_t<!std::is_void<T>::value>, |
| typename std::enable_if_t<std::is_void<Error>::value>> { |
| T val; |
| bool rejected = false; |
| }; |
| |
| template <typename T, typename Error> |
| struct GetResult<T, |
| Error, |
| typename std::enable_if_t<std::is_void<T>::value>, |
| typename std::enable_if_t<std::is_void<Error>::value>> { |
| bool rejected = false; |
| }; |
| |
| template <typename T, typename Error = void> |
| static typename std::enable_if_t<!std::is_void<T>::value, GetResult<T, Error>> |
| Resolve(T val) { |
| GetResult<T, Error> ret; |
| ret.val = std::move(val); |
| ret.rejected = false; |
| return ret; |
| } |
| |
| template <typename T, typename Error = void> |
| static typename std::enable_if_t<std::is_void<T>::value, GetResult<T, Error>> |
| Resolve() { |
| GetResult<void, Error> ret; |
| ret.rejected = false; |
| return ret; |
| } |
| |
| template <typename T, typename Error = void> |
| typename std::enable_if_t<!std::is_void<Error>::value, GetResult<T, Error>> |
| Reject(Error err) { |
| GetResult<T, Error> ret; |
| ret.err = std::move(err); |
| ret.rejected = true; |
| return ret; |
| } |
| |
| template <typename T, typename Error = void> |
| typename std::enable_if_t<std::is_void<Error>::value, GetResult<T, void>> |
| Reject() { |
| GetResult<T, void> ret; |
| ret.rejected = true; |
| return ret; |
| } |
| |
| namespace internal { |
| |
| template <typename T, typename Error> |
| struct SharedState { |
| SharedState() : cv(&mutex) {} |
| mutable base::Lock mutex; |
| base::ConditionVariable cv; |
| GetResult<T, Error> ret; |
| bool done = false; |
| scoped_refptr<base::TaskRunner> task_runner; |
| base::OnceClosure then_func; |
| }; |
| |
| template <typename F> |
| struct get_future_type; |
| |
| template <template <typename, typename> class F, typename T, typename Error> |
| struct get_future_type<F<T, Error>> { |
| using type = T; |
| }; |
| |
| template <class T, class Error, class U = T> |
| struct is_future : std::false_type {}; |
| |
| template <class F, class Error> |
| struct is_future<F, Error, Future<typename get_future_type<F>::type, Error>> |
| : std::true_type {}; |
| }; // namespace internal |
| |
| // Error: User defined error type used in |Reject| and |OnReject|. |
| template <typename T, typename Error = void> |
| class Future { |
| public: |
| Future() = default; |
| explicit Future(std::shared_ptr<internal::SharedState<T, Error>> state) |
| : state_(std::move(state)) {} |
| Future(Future&&) = default; |
| Future(const Future&) = delete; |
| Future& operator=(const Future&) = delete; |
| |
| Future& operator=(Future&&) = default; |
| |
| // |func| will be posted to the task_runner when this future is fulfilled by |
| // returning |vm_tools::Resolve<T>(val)| or |vm_tools::Reject<T>(err)|. |
| // |
| // Returns the future of the posted func. Task_runner of this future class is |
| // inherited by the returned future. |
| // |
| // |OnReject| can be used to handle the rejection if there was a reject. If |
| // the rejection is not handled, the subsequent Then funcs will be bypassed |
| // and rejection signal will be propagated. |
| template <typename T_then, typename... Ts> |
| Future<T_then, Error> Then( |
| base::OnceCallback<GetResult<T_then, Error>(Ts...)> func); |
| template <typename T_then, typename... Ts> |
| Future<T_then, Error> ThenNoReject(base::OnceCallback<T_then(Ts...)> func); |
| |
| // |func| is triggered if |vm_tools::Reject<T>(err)| is returned by previous |
| // |Then| func. The chain can be resumed by returning |
| // |vm_tools::Resolve<T>(val)|, or keep skipping by returning |
| // |vm_tools::Reject<T>(err)| within |func|. |
| // |
| // Returns the future of the posted func. task_runner of this future class is |
| // inherited by the returned future. |
| template <typename... ErrorOrVoid> |
| Future<T, Error> OnReject( |
| base::OnceCallback<GetResult<T, Error>(ErrorOrVoid...)> func); |
| |
| // Wait for the promise to be fulfilled, return the result and reset the |
| // shared state. In other words, the future becomes invalid after Get() |
| // returns. |
| // |
| // Use this method if all the tasks in the chain are posted to other threads, |
| // not the current thread. If not, use |GetWithRunLoop|. |
| GetResult<T, Error> Get(); |
| |
| // Wait for the result. Returns true if the result is ready before the |
| // timeout, and false otherwise. |
| // |
| // Same as |Get|, this method can only be used if all the tasks in the chain |
| // are posted to other threads, not the current thread. |
| bool WaitFor(base::TimeDelta duration); |
| |
| // This function is the same as |Get|, except that it uses a RunLoop to yield |
| // the current thread to the tasks posted in the chain. It is essentially a |
| // nested run loop because the current thread should already have one. |
| // |
| // WARNING: Use this at your own risk. It is easy to run into deadlocks (non |
| // recursive mutex) and other problems with a nested run loop. |
| GetResult<T, Error> GetWithRunLoop( |
| base::RunLoop::Type type = base::RunLoop::Type::kNestableTasksAllowed); |
| |
| // Update the |task_runner|. |Then|/|OnReject| functions will be posted to |
| // this task_runner |
| Future<T, Error> Via(scoped_refptr<base::TaskRunner> task_runner) { |
| DCHECK(task_runner); |
| base::AutoLock guard(state_->mutex); |
| state_->task_runner = task_runner; |
| return std::move(*this); |
| } |
| |
| // Returns true if the promise has been fulfilled. False otherwise. |
| bool IsDone() const { |
| base::AutoLock guard(state_->mutex); |
| return state_->done; |
| } |
| |
| // Flatten a nested future. Useful when making an async call within an async |
| // function. |
| template <typename U = T, typename UError = Error> |
| typename std::enable_if_t<internal::is_future<U, UError>::value, T> |
| Flatten() { |
| return std::move(*this).Then(base::BindOnce([](T f) { return f.Get(); })); |
| } |
| |
| private: |
| template <typename U = T, typename UError = Error> |
| static typename std::enable_if_t<std::is_void<UError>::value> |
| RejectFuncHelper(base::OnceCallback<GetResult<T, void>()> reject_func, |
| Promise<T, void> p, |
| GetResult<T, void> ret) { |
| p.SetResult(std::move(reject_func).Run()); |
| } |
| template <typename U = T, typename UError = Error> |
| static typename std::enable_if_t<!std::is_void<UError>::value> |
| RejectFuncHelper(base::OnceCallback<GetResult<T, UError>(UError)> reject_func, |
| Promise<T, UError> p, |
| GetResult<T, UError> ret) { |
| p.SetResult(std::move(reject_func).Run(std::move(ret.err))); |
| } |
| |
| template <typename T_then, typename UError = Error> |
| static typename std::enable_if_t<std::is_void<UError>::value> |
| ResolveFuncHelper(base::OnceCallback<GetResult<T_then, Error>()> resolve_func, |
| Promise<T_then, Error> p, |
| GetResult<T, Error> ret) { |
| p.SetResult(std::move(resolve_func).Run()); |
| } |
| |
| template <typename T_then, typename... Ts, typename UError = Error> |
| static typename std::enable_if_t<std::is_void<UError>::value> |
| ResolveFuncHelper( |
| base::OnceCallback<GetResult<T_then, Error>(Ts...)> resolve_func, |
| Promise<T_then, Error> p, |
| GetResult<T, Error> ret) { |
| p.SetResult(internal::Apply(std::move(resolve_func), std::move(ret.val))); |
| } |
| |
| template <typename T_then, typename UError = Error> |
| static typename std::enable_if_t<std::is_void<UError>::value> PropagateReject( |
| Promise<T_then, void> p, GetResult<T, void> ret) { |
| p.SetResult(Reject<T_then>()); |
| } |
| |
| template <typename T_then, typename UError = Error> |
| static typename std::enable_if_t<!std::is_void<UError>::value> |
| PropagateReject(Promise<T_then, UError> p, GetResult<T, UError> ret) { |
| p.SetResult(Reject<T_then, Error>(ret.err)); |
| } |
| |
| template <typename T_then, typename... Ts> |
| Future<T_then, Error> ThenHelper( |
| scoped_refptr<base::TaskRunner> task_runner, |
| base::OnceCallback<void(Promise<T_then, Error>, GetResult<T, Error>)> |
| resolve_func, |
| base::OnceCallback<void(Promise<T_then, Error>, GetResult<T, Error>)> |
| reject_func); |
| |
| std::shared_ptr<internal::SharedState<T, Error>> state_; |
| }; |
| |
| template <typename T, typename Error = void> |
| class Promise { |
| public: |
| Promise() { state_ = std::make_shared<internal::SharedState<T, Error>>(); } |
| explicit Promise(std::shared_ptr<internal::SharedState<T, Error>> state) |
| : state_(std::move(state)) {} |
| Promise(Promise&&) = default; |
| Promise(const Promise&) = delete; |
| Promise& operator=(const Promise&) = delete; |
| |
| Promise& operator=(Promise&&) = default; |
| |
| // Returns a future that can be used to wait for this promise to be fulfilled. |
| Future<T, Error> GetFuture(scoped_refptr<base::TaskRunner> task_runner) { |
| base::AutoLock guard(state_->mutex); |
| state_->task_runner = task_runner; |
| return Future<T, Error>(state_); |
| } |
| |
| // Fulfill this promise. The shared state will be released upon this. |
| template <typename U = T> |
| typename std::enable_if_t<std::is_void<U>::value> SetValue(); |
| template <typename U = T> |
| typename std::enable_if_t<!std::is_void<U>::value> SetValue(U val); |
| |
| // Reject this promise. The shared state will be released upon this. |
| template <typename UError = Error> |
| typename std::enable_if_t<std::is_void<UError>::value> Reject(); |
| template <typename UError = Error> |
| typename std::enable_if_t<!std::is_void<UError>::value> Reject(UError err); |
| |
| void SetResult(GetResult<T, Error> ret); |
| |
| private: |
| // Lock state_.mutex before calling this |
| void SetValueHelperLocked(); |
| |
| std::shared_ptr<internal::SharedState<T, Error>> state_; |
| }; |
| |
| // ------ Promise impl ------ |
| |
| template <typename T, typename Error> |
| void Promise<T, Error>::SetValueHelperLocked() { |
| DCHECK(!state_->done); |
| state_->done = true; |
| state_->cv.Signal(); |
| |
| // Handle "then" |
| if (!state_->then_func.is_null()) { |
| std::move(state_->then_func).Run(); |
| } |
| } |
| |
| template <typename T, typename Error> |
| template <typename U> |
| typename std::enable_if_t<std::is_void<U>::value> |
| Promise<T, Error>::SetValue() { |
| base::AutoLock guard(state_->mutex); |
| state_->ret = vm_tools::Resolve<void, Error>(); |
| SetValueHelperLocked(); |
| } |
| |
| template <typename T, typename Error> |
| template <typename U> |
| typename std::enable_if_t<!std::is_void<U>::value> Promise<T, Error>::SetValue( |
| U val) { |
| base::AutoLock guard(state_->mutex); |
| state_->ret = vm_tools::Resolve<T, Error>(std::move(val)); |
| SetValueHelperLocked(); |
| } |
| |
| template <typename T, typename Error> |
| template <typename UError> |
| typename std::enable_if_t<std::is_void<UError>::value> |
| Promise<T, Error>::Reject() { |
| base::AutoLock guard(state_->mutex); |
| state_->ret = vm_tools::Reject<T, void>(); |
| state_->done = true; |
| state_->cv.Signal(); |
| |
| if (!state_->then_func.is_null()) { |
| std::move(state_->then_func).Run(); |
| } |
| } |
| |
| template <typename T, typename Error> |
| template <typename UError> |
| typename std::enable_if_t<!std::is_void<UError>::value> |
| Promise<T, Error>::Reject(UError err) { |
| base::AutoLock guard(state_->mutex); |
| state_->ret = vm_tools::Reject<T, Error>(std::move(err)); |
| state_->done = true; |
| state_->cv.Signal(); |
| |
| if (!state_->then_func.is_null()) { |
| std::move(state_->then_func).Run(); |
| } |
| } |
| |
| template <typename T, typename Error> |
| void Promise<T, Error>::SetResult(GetResult<T, Error> ret) { |
| base::AutoLock guard(state_->mutex); |
| state_->ret = std::move(ret); |
| SetValueHelperLocked(); |
| } |
| |
| // ------ Future impl ------ |
| |
| template <typename T, typename Error> |
| template <typename T_then, typename... Ts> |
| Future<T_then, Error> Future<T, Error>::ThenHelper( |
| scoped_refptr<base::TaskRunner> task_runner, |
| base::OnceCallback<void(Promise<T_then, Error>, GetResult<T, Error>)> |
| resolve_func, |
| base::OnceCallback<void(Promise<T_then, Error>, GetResult<T, Error>)> |
| reject_func) { |
| base::AutoLock guard(state_->mutex); |
| if (task_runner) { |
| state_->task_runner = task_runner; |
| } |
| Promise<T_then, Error> promise; |
| Future<T_then, Error> future = promise.GetFuture(state_->task_runner); |
| internal::SharedState<T, Error>* pState = state_.get(); |
| base::OnceCallback<void(Future<T, Error>, Promise<T_then, Error>)> |
| wrapped_func = base::BindOnce( |
| [](base::OnceCallback<void(Promise<T_then, Error>, |
| GetResult<T, Error>)> resolve_func, |
| base::OnceCallback<void(Promise<T_then, Error>, |
| GetResult<T, Error>)> reject_func, |
| Future<T, Error> old_future, Promise<T_then, Error> p) { |
| GetResult<T, Error> ret = old_future.Get(); |
| if (ret.rejected) { |
| std::move(reject_func).Run(std::move(p), std::move(ret)); |
| } else { |
| std::move(resolve_func).Run(std::move(p), std::move(ret)); |
| } |
| }, |
| std::move(resolve_func), std::move(reject_func)); |
| |
| base::OnceClosure post_func = base::BindOnce( |
| [](scoped_refptr<base::TaskRunner> task_runner, base::OnceClosure func) { |
| CHECK(task_runner); |
| task_runner->PostTask(FROM_HERE, std::move(func)); |
| }, |
| state_->task_runner, |
| base::BindOnce(std::move(wrapped_func), std::move(*this), |
| std::move(promise))); |
| if (pState->done) { |
| // post immediately |
| std::move(post_func).Run(); |
| } else { |
| // Promise::SetValue/Reject will run this func |
| pState->then_func = std::move(post_func); |
| } |
| return future; |
| } |
| |
| template <typename T, typename Error> |
| template <typename T_then, typename... Ts> |
| Future<T_then, Error> Future<T, Error>::Then( |
| base::OnceCallback<GetResult<T_then, Error>(Ts...)> func) { |
| return ThenHelper<T_then, Ts...>( |
| nullptr, |
| base::BindOnce( |
| [](base::OnceCallback<GetResult<T_then, Error>(Ts...)> resolve_func, |
| Promise<T_then, Error> p, GetResult<T, Error> ret) { |
| ResolveFuncHelper(std::move(resolve_func), std::move(p), |
| std::move(ret)); |
| }, |
| std::move(func)), |
| base::BindOnce(&Future<T, Error>::PropagateReject<T_then>)); |
| } |
| |
| namespace internal { |
| |
| template <typename Error, typename T_then, typename... Ts> |
| typename std::enable_if_t<!std::is_void<T_then>::value, |
| base::OnceCallback<GetResult<T_then, Error>(Ts...)>> |
| FutureBind(base::OnceCallback<T_then(Ts...)> func) { |
| return base::BindOnce( |
| [](base::OnceCallback<T_then(Ts...)> func, Ts... args) { |
| return Resolve<T_then, Error>(std::move(func).Run(std::move(args)...)); |
| }, |
| std::move(func)); |
| } |
| |
| template <typename Error, typename T_then, typename... Ts> |
| typename std::enable_if_t<std::is_void<T_then>::value, |
| base::OnceCallback<GetResult<T_then, Error>(Ts...)>> |
| FutureBind(base::OnceCallback<T_then(Ts...)> func) { |
| return base::BindOnce( |
| [](base::OnceCallback<void(Ts...)> func, Ts... args) { |
| std::move(func).Run(std::move(args)...); |
| return Resolve<void, Error>(); |
| }, |
| std::move(func)); |
| } |
| }; // namespace internal |
| |
| template <typename T, typename Error> |
| template <typename T_then, typename... Ts> |
| Future<T_then, Error> Future<T, Error>::ThenNoReject( |
| base::OnceCallback<T_then(Ts...)> func) { |
| return Then(internal::FutureBind<Error>(std::move(func))); |
| } |
| |
| template <typename T, typename Error> |
| template <typename... ErrorOrVoid> |
| Future<T, Error> Future<T, Error>::OnReject( |
| base::OnceCallback<GetResult<T, Error>(ErrorOrVoid...)> func) { |
| return ThenHelper<T, T>( |
| nullptr, base::BindOnce([](Promise<T, Error> p, GetResult<T, Error> ret) { |
| p.SetResult(std::move(ret)); |
| }), |
| base::BindOnce( |
| [](base::OnceCallback<GetResult<T, Error>(ErrorOrVoid...)> func, |
| Promise<T, Error> p, GetResult<T, Error> ret) { |
| Future<T, Error>::RejectFuncHelper(std::move(func), std::move(p), |
| std::move(ret)); |
| }, |
| std::move(func))); |
| } |
| |
| template <typename T, typename Error> |
| GetResult<T, Error> Future<T, Error>::GetWithRunLoop(base::RunLoop::Type type) { |
| state_->mutex.Acquire(); |
| if (!state_->done) { |
| base::RunLoop loop(type); |
| DCHECK(state_->then_func.is_null()); |
| state_->then_func = loop.QuitClosure(); |
| state_->mutex.Release(); |
| loop.Run(); |
| state_->mutex.Acquire(); |
| } |
| |
| DCHECK(state_->done); |
| GetResult<T, Error> ret = std::move(state_->ret); |
| state_->mutex.Release(); |
| state_.reset(); |
| |
| return ret; |
| } |
| |
| template <typename T, typename Error> |
| bool Future<T, Error>::WaitFor(base::TimeDelta duration) { |
| base::AutoLock guard(state_->mutex); |
| if (!state_->done) |
| state_->cv.TimedWait(duration); |
| |
| return state_->done; |
| } |
| |
| template <typename T, typename Error> |
| GetResult<T, Error> Future<T, Error>::Get() { |
| state_->mutex.Acquire(); |
| while (!state_->done) |
| state_->cv.Wait(); |
| // There should only be one thread waiting for the cv. No need to signal |
| GetResult<T, Error> ret = std::move(state_->ret); |
| state_->mutex.Release(); |
| state_.reset(); |
| return ret; |
| } |
| |
| /* ------ Non class method declarations ------*/ |
| |
| // Post |func| to |task_runner|, and return a future that will be ready upon |
| // completion of the posted |func| |
| template <typename T, typename Error = void> |
| Future<T, Error> Async(scoped_refptr<base::TaskRunner> task_runner, |
| base::OnceCallback<GetResult<T, Error>()> func); |
| template <typename T, typename Error = void> |
| Future<T, Error> AsyncNoReject(scoped_refptr<base::TaskRunner> task_runner, |
| base::OnceCallback<T()> func); |
| |
| // Returns a future that will be ready when all the given futures are ready |
| // If any of the given futures is rejected, the returned future will be rejected |
| // as well |
| template <typename T, typename Error> |
| Future<std::vector<T>, Error> Collect( |
| scoped_refptr<base::TaskRunner> task_runner, |
| std::vector<Future<T, Error>> futures); |
| |
| // Returns a future that has already been resolved with the given |val|. |
| // This is useful for removing boilerplate code |
| template <typename T, typename Error = void> |
| std::enable_if_t<!std::is_void<T>::value, Future<T, Error>> ResolvedFuture( |
| T val, |
| scoped_refptr<base::TaskRunner> task_runner = |
| base::SequencedTaskRunnerHandle::Get()); |
| template <typename T, typename Error = void> |
| std::enable_if_t<std::is_void<T>::value, Future<T, Error>> ResolvedFuture( |
| scoped_refptr<base::TaskRunner> task_runner = |
| base::SequencedTaskRunnerHandle::Get()); |
| |
| /* ------ Non class method implementation ------ */ |
| |
| template <typename T, typename Error> |
| Future<T, Error> Flatten(Future<Future<T, Error>, Error> f) { |
| return f.Then(base::BindOnce([](Future<T, Error> f) { return f.Get(); })); |
| } |
| |
| template <typename T, typename Error> |
| Future<T, Error> Async(scoped_refptr<base::TaskRunner> task_runner, |
| base::OnceCallback<GetResult<T, Error>()> func) { |
| Promise<T, Error> p; |
| Future<T, Error> future = p.GetFuture(task_runner); |
| task_runner->PostTask(FROM_HERE, |
| base::BindOnce( |
| [](Promise<T, Error> p, |
| base::OnceCallback<GetResult<T, Error>()> func) { |
| p.SetResult(std::move(func).Run()); |
| }, |
| std::move(p), std::move(func))); |
| return future; |
| } |
| |
| template <typename T, typename Error> |
| Future<T, Error> AsyncNoReject(scoped_refptr<base::TaskRunner> task_runner, |
| base::OnceCallback<T()> func) { |
| Promise<T, Error> p; |
| Future<T, Error> future = p.GetFuture(task_runner); |
| task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](Promise<T, Error> p, |
| base::OnceCallback<GetResult<T, Error>()> func) { |
| p.SetResult(std::move(func).Run()); |
| }, |
| std::move(p), internal::FutureBind<Error>(std::move(func)))); |
| return future; |
| } |
| |
| template <typename T, typename Error> |
| Future<std::vector<T>, Error> Collect( |
| scoped_refptr<base::TaskRunner> task_runner, |
| std::vector<Future<T, Error>> futures) { |
| struct Context { |
| explicit Context(size_t n) { values.resize(n); } |
| void Reject(Error e) { |
| if (!rejected.fetch_or(1)) { // only reject once |
| promise.Reject(std::move(e)); |
| } |
| } |
| ~Context() { |
| if (!rejected.load()) { |
| promise.SetValue(std::move(values)); |
| } |
| } |
| std::atomic<uint8_t> rejected{0}; |
| Promise<std::vector<T>> promise; |
| std::vector<T> values; |
| }; |
| |
| std::shared_ptr<Context> ctx = std::make_shared<Context>(futures.size()); |
| |
| for (size_t i = 0; i < futures.size(); ++i) { |
| futures[i] |
| .Via(task_runner) |
| .Then(base::BindOnce( |
| [](std::shared_ptr<Context> ctx, size_t i, T val) { |
| ctx->values[i] = std::move(val); |
| return Resolve<void>(); |
| }, |
| ctx, i)) |
| .OnReject(base::BindOnce( |
| [](std::shared_ptr<Context> ctx, Error e) { |
| ctx->Reject(std::move(e)); |
| // Whatever as the future returned by this OnReject is not used |
| return Resolve<void>(); |
| }, |
| ctx)); |
| } |
| |
| return ctx->promise.GetFuture(task_runner); |
| } |
| |
| template <typename T> |
| Future<std::vector<T>, void> Collect( |
| scoped_refptr<base::TaskRunner> task_runner, |
| std::vector<Future<T, void>> futures) { |
| struct Context { |
| explicit Context(size_t n) { values.resize(n); } |
| void Reject() { |
| if (!rejected.fetch_or(1)) { // only reject once |
| promise.Reject(); |
| } |
| } |
| ~Context() { |
| if (!rejected.load()) { |
| promise.SetValue(std::move(values)); |
| } |
| } |
| std::atomic<uint8_t> rejected{0}; |
| Promise<std::vector<T>> promise; |
| std::vector<T> values; |
| }; |
| |
| std::shared_ptr<Context> ctx = std::make_shared<Context>(futures.size()); |
| |
| for (size_t i = 0; i < futures.size(); ++i) { |
| futures[i] |
| .Via(task_runner) |
| .Then(base::BindOnce( |
| [](std::shared_ptr<Context> ctx, size_t i, T val) { |
| ctx->values[i] = std::move(val); |
| return Resolve<void>(); |
| }, |
| ctx, i)) |
| .OnReject(base::BindOnce( |
| [](std::shared_ptr<Context> ctx) { |
| ctx->Reject(); |
| // Whatever as the future returned by this OnReject is not used |
| return Resolve<void>(); |
| }, |
| ctx)); |
| } |
| |
| return ctx->promise.GetFuture(task_runner); |
| } |
| |
| template <typename T, typename Error> |
| std::enable_if_t<!std::is_void<T>::value, Future<T, Error>> ResolvedFuture( |
| T val, scoped_refptr<base::TaskRunner> task_runner) { |
| Promise<T, Error> promise; |
| Future<T, Error> future = promise.GetFuture(task_runner); |
| promise.SetValue(std::move(val)); |
| return future; |
| } |
| |
| template <typename T, typename Error> |
| std::enable_if_t<std::is_void<T>::value, Future<T, Error>> ResolvedFuture( |
| scoped_refptr<base::TaskRunner> task_runner) { |
| Promise<void, Error> promise; |
| Future<void, Error> future = promise.GetFuture(task_runner); |
| promise.SetValue(); |
| return future; |
| } |
| |
| } // namespace vm_tools |
| |
| #endif // VM_TOOLS_CONCIERGE_FUTURE_H_ |