Commit 810a8608 authored by Alex Clarke's avatar Alex Clarke Committed by Commit Bot

Reland: Promises: Implement Promise<> handle with Then and Catch

Original patch https://crrev.com/c/1599620

MultiThreadedPromiseTest.CrossThreadThens had a race between the
resolve on thread b and thread a posting a bunch of tasks including
the quit closure which would sometimes arrive mid way through
thread b posting eligible thens.



Implements Then and Catch on top of AbstractPromise as well as
ManualPromiseResolver<> which is needed for testing at this
stage (PostTask doesn't yet return a promise). It's also
necessary for promises to interface with old style callback
based libraries which are extremely common in Chromium.

Design: https://docs.google.com/document/d/1l12PAJgEtlrqTXKiw6mk2cR2jP7FAfCCDr-DGIdiC9w/edit

TBR=fdoray@chromium.org, etiennep@chromium.org

Bug: 906125
Change-Id: I95bbd0f1a709643cd4a813e378b625a91f2abe7a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1617769Reviewed-by: default avatarAlex Clarke <alexclarke@chromium.org>
Commit-Queue: Alex Clarke <alexclarke@chromium.org>
Cr-Commit-Position: refs/heads/master@{#661171}
parent 05059d2b
......@@ -755,9 +755,14 @@ jumbo_component("base") {
"task/promise/abstract_promise.h",
"task/promise/dependent_list.cc",
"task/promise/dependent_list.h",
"task/promise/helpers.h",
"task/promise/no_op_promise_executor.cc",
"task/promise/no_op_promise_executor.h",
"task/promise/promise.h",
"task/promise/promise_result.h",
"task/promise/small_unique_object.h",
"task/promise/then_and_catch_executor.cc",
"task/promise/then_and_catch_executor.h",
"task/scoped_set_task_priority_for_current_thread.cc",
"task/scoped_set_task_priority_for_current_thread.h",
"task/sequence_manager/associated_thread_id.cc",
......@@ -2631,6 +2636,8 @@ test("base_unittests") {
"task/post_task_unittest.cc",
"task/promise/abstract_promise_unittest.cc",
"task/promise/dependent_list_unittest.cc",
"task/promise/helpers_unittest.cc",
"task/promise/promise_unittest.cc",
"task/scoped_set_task_priority_for_current_thread_unittest.cc",
"task/sequence_manager/atomic_flag_set_unittest.cc",
"task/sequence_manager/lazily_deallocated_deque_unittest.cc",
......@@ -3034,6 +3041,7 @@ if (enable_nocompile_tests) {
"observer_list_unittest.nc",
"optional_unittest.nc",
"strings/string16_unittest.nc",
"task/promise/promise_unittest.nc",
"task/task_traits_extension_unittest.nc",
"task/task_traits_unittest.nc",
"thread_annotations_unittest.nc",
......
......@@ -19,14 +19,15 @@ struct FakeBindState;
namespace internal {
class CallbackBase;
class CallbackBaseCopyable;
class BaseThenAndCatchExecutor;
class BindStateBase;
template <typename Functor, typename... BoundArgs>
struct BindState;
class CallbackBase;
class CallbackBaseCopyable;
struct BindStateBaseRefCountTraits {
static void Destruct(const BindStateBase*);
};
......@@ -135,6 +136,8 @@ class BASE_EXPORT CallbackBase {
void Reset();
protected:
friend class BaseThenAndCatchExecutor;
using InvokeFuncStorage = BindStateBase::InvokeFuncStorage;
// Returns true if this callback equals |other|. |other| may be null.
......
......@@ -47,6 +47,11 @@ const AbstractPromise* AbstractPromise::FindNonCurriedAncestor() const {
void AbstractPromise::AddAsDependentForAllPrerequisites() {
DCHECK(prerequisites_);
// Note a curried promise will eventually get to all its children and pass
// them catch responsibility through AddAsDependentForAllPrerequisites,
// although that'll be done lazily (only once they resolve/reject, so there
// is a possibility the DCHECKs might be racy.
for (AdjacencyListNode& node : prerequisites_->prerequisite_list) {
node.dependent_node.dependent = this;
......@@ -196,20 +201,6 @@ void AbstractPromise::MaybeInheritChecks(AbstractPromise* prerequisite) {
prerequisite->passed_catch_responsibility_ = true;
}
void AbstractPromise::PassCatchResponsibilityOntoDependentsForCurriedPromise(
DependentList::Node* dependent_list) {
CheckedAutoLock lock(GetCheckedLock());
if (!dependent_list)
return;
if (IsResolvedWithPromise()) {
for (DependentList::Node* node = dependent_list; node;
node = node->next.load(std::memory_order_relaxed)) {
node->dependent->MaybeInheritChecks(this);
}
}
}
AbstractPromise::LocationRef::LocationRef(const Location& from_here)
: from_here_(from_here) {}
......@@ -353,10 +344,6 @@ void AbstractPromise::OnResolvePostReadyDependents() {
DependentList::Node* dependent_list = dependents_.ConsumeOnceForResolve();
dependent_list = NonThreadSafeReverseList(dependent_list);
#if DCHECK_IS_ON()
PassCatchResponsibilityOntoDependentsForCurriedPromise(dependent_list);
#endif
// Propagate resolve to dependents.
DependentList::Node* next;
for (DependentList::Node* node = dependent_list; node; node = next) {
......@@ -374,10 +361,6 @@ void AbstractPromise::OnRejectPostReadyDependents() {
DependentList::Node* dependent_list = dependents_.ConsumeOnceForReject();
dependent_list = NonThreadSafeReverseList(dependent_list);
#if DCHECK_IS_ON()
PassCatchResponsibilityOntoDependentsForCurriedPromise(dependent_list);
#endif
// Propagate rejection to dependents. We always propagate rejection
// immediately.
DependentList::Node* next;
......@@ -429,7 +412,8 @@ void AbstractPromise::OnCanceled() {
void AbstractPromise::OnResolved() {
#if DCHECK_IS_ON()
DCHECK(executor_can_resolve_) << from_here_.ToString();
DCHECK(executor_can_resolve_ || IsResolvedWithPromise())
<< from_here_.ToString();
#endif
if (IsResolvedWithPromise()) {
scoped_refptr<AbstractPromise> curried_promise =
......
......@@ -46,7 +46,7 @@ enum class RejectPolicy {
// resolved. This lets us disambiguate promises with the same resolve and reject
// type.
template <typename T>
struct BASE_EXPORT Resolved {
struct Resolved {
using Type = T;
static_assert(!std::is_same<T, NoReject>::value,
......@@ -64,7 +64,7 @@ struct BASE_EXPORT Resolved {
};
template <>
struct BASE_EXPORT Resolved<void> {
struct Resolved<void> {
using Type = void;
Void value;
};
......@@ -73,7 +73,7 @@ struct BASE_EXPORT Resolved<void> {
// rejected. This lets us disambiguate promises with the same resolve and reject
// type.
template <typename T>
struct BASE_EXPORT Rejected {
struct Rejected {
using Type = T;
T value;
......@@ -93,7 +93,7 @@ struct BASE_EXPORT Rejected {
};
template <>
struct BASE_EXPORT Rejected<void> {
struct Rejected<void> {
using Type = void;
Void value;
};
......@@ -399,10 +399,6 @@ class BASE_EXPORT AbstractPromise
void MaybeInheritChecks(AbstractPromise* source)
EXCLUSIVE_LOCKS_REQUIRED(GetCheckedLock());
// Does nothing if this promise wasn't resolved by a promise.
void PassCatchResponsibilityOntoDependentsForCurriedPromise(
DependentList::Node* dependent_list);
// Controls how we deal with unhandled rejection.
const RejectPolicy reject_policy_;
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// Copyright 2019 The Chromium 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 BASE_TASK_PROMISE_PROMISE_RESULT_H_
#define BASE_TASK_PROMISE_PROMISE_RESULT_H_
#include <type_traits>
#include "base/containers/unique_any.h"
namespace base {
// An optional return type from a promise callback, which allows the callback to
// decide whether or not it should reject. E.g.
//
// enum class Error { kReason };
//
// PromiseResult<int, Error> MyFn() {
// int result;
// ...
// if (error)
// return Error::kReason;
// return result;
// }
//
// If ResolveType and RejectType are distinct PromiseResult's constructor will
// accept them. E.g.
//
// PromiseResult<int, std::string> pr(123); // Resolve
// PromiseResult<int, std::string> pr("whoops"); // Reject
//
// If ResolveType and RejectType are the same you need to use Resolved<> and
// Rejected<> to disambiguate. E.g.
//
// PromiseResult<int, int> pr(Resolved{123}); // Resolve
// PromiseResult<int, int> pr(Rejected{123}); // Reject
//
// PromiseResult<ResolveType, RejectType> has a constructor that accepts
// Promise<ResolveType, RejectType>.
template <typename ResolveType, typename RejectType = NoReject>
class PromiseResult {
public:
template <typename ResolveT = ResolveType,
typename RejectT = RejectType,
class Enable = std::enable_if<std::is_void<ResolveT>::value ^
std::is_void<RejectT>::value>>
PromiseResult() : PromiseResult(typename Analyze<void>::TagType()) {}
template <typename T>
PromiseResult(T&& t)
: PromiseResult(typename Analyze<std::decay_t<T>>::TagType(),
std::forward<T>(t)) {}
PromiseResult(PromiseResult&& other) noexcept = default;
PromiseResult(const PromiseResult&) = delete;
PromiseResult& operator=(const PromiseResult&) = delete;
unique_any& value() { return value_; }
private:
struct IsWrapped {};
struct IsResolved {};
struct IsRejected {};
struct IsPromise {};
// Helper that assigns one of the above tags for |T|.
template <typename T>
struct Analyze {
using DecayedT = std::decay_t<T>;
static constexpr bool is_resolve =
std::is_convertible<DecayedT, std::decay_t<ResolveType>>::value;
static constexpr bool is_reject =
std::is_convertible<DecayedT, std::decay_t<RejectType>>::value;
static_assert(!is_reject || !std::is_same<DecayedT, NoReject>::value,
"A NoReject promise can't reject");
static_assert(!std::is_same<std::decay_t<ResolveType>,
std::decay_t<RejectType>>::value,
"Ambiguous because ResolveType and RejectType are the same");
static_assert(is_resolve || is_reject,
"Argument matches neither resolve nor reject type.");
static_assert(
is_resolve != is_reject && (is_resolve || is_reject),
"Ambiguous because argument matches both ResolveType and RejectType");
using TagType = std::conditional_t<is_resolve, IsResolved, IsRejected>;
};
template <typename T>
struct Analyze<Resolved<T>> {
using TagType = IsWrapped;
static_assert(std::is_same<ResolveType, T>::value,
"T in Resolved<T> is not ResolveType");
};
template <typename T>
struct Analyze<Rejected<T>> {
static_assert(std::is_same<RejectType, T>::value,
"T in Rejected<T> is not RejectType");
static_assert(!std::is_same<RejectType, NoReject>::value,
"A NoReject promise can't reject");
using TagType = IsWrapped;
};
template <typename ResolveT, typename RejectT>
struct Analyze<Promise<ResolveT, RejectT>> {
using TagType = IsPromise;
static_assert(std::is_same<ResolveT, ResolveType>::value,
"Promise resolve types don't match");
static_assert(std::is_same<RejectT, RejectType>::value,
"Promise reject types don't match");
};
template <typename... Args>
PromiseResult(IsResolved, Args&&... args)
: value_(in_place_type_t<Resolved<ResolveType>>(),
std::forward<Args>(args)...) {}
template <typename... Args>
PromiseResult(IsRejected, Args&&... args)
: value_(in_place_type_t<Rejected<RejectType>>(),
std::forward<Args>(args)...) {}
PromiseResult(IsPromise, const Promise<ResolveType, RejectType>& promise)
: value_(promise.abstract_promise_) {}
template <typename T>
PromiseResult(IsWrapped, T&& t) : value_(std::forward<T>(t)) {}
unique_any value_;
};
} // namespace base
#endif // BASE_TASK_PROMISE_PROMISE_RESULT_H_
This diff is collapsed.
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This is a "No Compile Test" suite.
// http://dev.chromium.org/developers/testing/no-compile-tests
#include "base/task/promise/promise.h"
#include "base/task/promise/promise_result.h"
namespace base {
#if defined(NCTEST_METHOD_CANT_CATCH_NOREJECT_PROMISE) // [r"fatal error: static_assert failed .*\"Can't catch a NoReject promise\."]
void WontCompile() {
Promise<int> p;
p.CatchOnCurrent(FROM_HERE, BindOnce([]() {}));
}
#elif defined(NCTEST_METHOD_CANT_CATCH_NOREJECT_PROMISE_TYPE_TWO) // [r"fatal error: static_assert failed .*\"Can't catch a NoReject promise\."]
void WontCompile() {
Promise<int> p;
p.ThenOnCurrent(FROM_HERE, BindOnce([](int) {}), BindOnce([]() {}));
}
#elif defined(NCTEST_METHOD_RESOLVE_CALLBACK_TYPE_MISSMATCH) // [r"fatal error: static_assert failed .*\"|on_resolve| callback must accept Promise::ResolveType or void\."]
void WontCompile() {
Promise<int, void> p;
p.ThenOnCurrent(FROM_HERE, BindOnce([](bool) { }));
}
#elif defined(NCTEST_METHOD_REJECT_CALLBACK_TYPE_MISSMATCH) // [r"fatal error: static_assert failed .*\"|on_reject| callback must accept Promise::ResolveType or void\."]
void WontCompile() {
Promise<int, void> p;
p.CatchOnCurrent(FROM_HERE, BindOnce([](bool) { }));
}
#elif defined(NCTEST_METHOD_REJECT_CALLBACK_TYPE_MISSMATCH_TYPE_TWO) // [r"fatal error: static_assert failed .*\"|on_reject| callback must accept Promise::ResolveType or void\."]
void WontCompile() {
Promise<int, void> p;
p.ThenOnCurrent(FROM_HERE, BindOnce([](int) { }), BindOnce([](bool) { }));
}
#elif defined(NCTEST_METHOD_INCOMPATIBLE_RETURN_TYPES) // [r"fatal error: static_assert failed .*\"|on_resolve| callback and |on_resolve| callback must return compatible types\."]
void WontCompile() {
Promise<void> p;
p.ThenOnCurrent(
FROM_HERE,
BindOnce([]() -> PromiseResult<int, std::string> { return 123; }),
BindOnce([](int err) -> Rejected<bool> { return "123"; }));
}
#elif defined(NCTEST_METHOD_INCOMPATIBLE_RETURN_TYPES2) // [r"fatal error: static_assert failed .*\"|on_resolve| callback and |on_resolve| callback must return compatible types\."]
void WontCompile() {
Promise<void> p;
p.ThenOnCurrent(
FROM_HERE,
BindOnce([]() -> PromiseResult<int, std::string> { return 123; }),
BindOnce([](int err) -> Resolved<std::string> { return "123"; }));
}
#elif defined(NCTEST_METHOD_INCOMPATIBLE_RETURN_TYPES3) // [r"fatal error: static_assert failed .*\"|on_resolve| callback and |on_resolve| callback must return compatible types\."]
void WontCompile() {
Promise<int, void> p;
p.ThenOnCurrent(FROM_HERE, BindOnce([](int) { return true; }),
BindOnce([](int) { return 123.0; }));
}
#elif defined(NCTEST_METHOD_AMBIGUOUS_CONSTRUCTOR) // [r"fatal error: static_assert failed .*\"Ambiguous because ResolveType and RejectType are the same"]
void WontCompile() {
PromiseResult<void, void> pr;
}
#elif defined(NCTEST_METHOD_AMBIGUOUS_CONSTRUCTOR2) // [r"fatal error: static_assert failed .*\"Ambiguous because ResolveType and RejectType are the same"]
void WontCompile() {
PromiseResult<int, int> pr(123);
}
#elif defined(NCTEST_METHOD_REJECTED_NOREJECT) // [r"fatal error: static_assert failed .*\"Can't have Rejected<NoReject>"]
void WontCompile() {
Rejected<NoReject>();
}
#elif defined(NCTEST_METHOD_ARGUMENT_DOESNT_MATCH) // [r"fatal error: static_assert failed .*\"Argument matches neither resolve nor reject type\."]
void WontCompile() {
PromiseResult<int, float> pr("invalid");
}
#elif defined(NCTEST_METHOD_ARGUMENT_DOESNT_MATCH2) // [r"fatal error: static_assert failed .*\"Promise resolve types don't match"]
void WontCompile() {
Promise<void> p;
PromiseResult<int, float> pr(p);
}
#elif defined(NCTEST_METHOD_UNRELATED_RESOLVE) // [r"fatal error: static_assert failed .*\"T in Resolved<T> is not ResolveType"]
void WontCompile() {
struct Unrelated{};
PromiseResult<int, void> pr(Resolved<Unrelated>{});
}
#elif defined(NCTEST_METHOD_UNRELATED_REJECT) // [r"fatal error: static_assert failed .*\"T in Rejected<T> is not RejectType"]
void WontCompile() {
struct Unrelated{};
PromiseResult<int, void> pr(Rejected<Unrelated>{});
}
#elif defined(NCTEST_METHOD_AMBIGUOUS_REJECT_TYPE) // [r"fatal error: static_assert failed .*\"Ambiguous promise reject type"]
void WontCompile() {
Promise<int, void> p1;
// If supported |p2| would have type Promise<NoReject, variant<void, bool>>.
auto p2 = p1.ThenOnCurrent(FROM_HERE, BindOnce([]() { return Rejected<bool>(true); }));
}
#elif defined(NCTEST_METHOD_AMBIGUOUS_RESOLVE_TYPE) // [r"fatal error: static_assert failed .*\"Ambiguous promise resolve type"]
void WontCompile() {
Promise<int, void> p1;
// If supported |p2| would have type Promise<variant<int, bool>, NoReject>.
auto p2 = p1.CatchOnCurrent(FROM_HERE, BindOnce([](int) { return Resolved<bool>(true); }));
}
#endif
} // namespace base
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/task/promise/then_and_catch_executor.h"
namespace base {
namespace internal {
BaseThenAndCatchExecutor::BaseThenAndCatchExecutor(
internal::CallbackBase&& resolve_executor,
internal::CallbackBase&& reject_executor)
: resolve_callback_(std::move(resolve_executor)),
reject_callback_(std::move(reject_executor)) {}
BaseThenAndCatchExecutor::~BaseThenAndCatchExecutor() = default;
bool BaseThenAndCatchExecutor::IsCancelled() const {
if (!resolve_callback_.is_null()) {
// If there is both a resolve and a reject executor they must be canceled
// at the same time.
DCHECK(reject_callback_.is_null() ||
reject_callback_.IsCancelled() == resolve_callback_.IsCancelled());
return resolve_callback_.IsCancelled();
}
return reject_callback_.IsCancelled();
}
AbstractPromise::Executor::PrerequisitePolicy
BaseThenAndCatchExecutor::GetPrerequisitePolicy() {
return PrerequisitePolicy::kAll;
}
void BaseThenAndCatchExecutor::Execute(AbstractPromise* promise) {
AbstractPromise* prerequisite =
promise->GetOnlyPrerequisite()->FindNonCurriedAncestor();
if (prerequisite->IsResolved()) {
if (ProcessNullCallback(resolve_callback_, prerequisite, promise)) {
promise->OnResolved();
return;
}
ExecuteThen(prerequisite, promise);
} else {
DCHECK(prerequisite->IsRejected());
if (ProcessNullCallback(reject_callback_, prerequisite, promise)) {
promise->OnResolved();
return;
}
ExecuteCatch(prerequisite, promise);
}
}
// static
bool BaseThenAndCatchExecutor::ProcessNullCallback(const CallbackBase& callback,
AbstractPromise* arg,
AbstractPromise* result) {
if (callback.is_null()) {
// A curried promise is used to forward the result through null callbacks.
result->emplace(scoped_refptr<AbstractPromise>(arg));
DCHECK(result->IsResolvedWithPromise());
return true;
}
return false;
}
} // namespace internal
} // namespace base
// Copyright 2019 The Chromium 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 BASE_TASK_PROMISE_THEN_AND_CATCH_EXECUTOR_H_
#define BASE_TASK_PROMISE_THEN_AND_CATCH_EXECUTOR_H_
#include <type_traits>
#include "base/callback.h"
#include "base/task/promise/abstract_promise.h"
#include "base/task/promise/helpers.h"
namespace base {
namespace internal {
// Exists to reduce template bloat.
class BASE_EXPORT BaseThenAndCatchExecutor : public AbstractPromise::Executor {
public:
BaseThenAndCatchExecutor(CallbackBase&& resolve_callback,
CallbackBase&& reject_callback);
~BaseThenAndCatchExecutor() override;
// AbstractPromise::Executor:
bool IsCancelled() const override;
PrerequisitePolicy GetPrerequisitePolicy() override;
void Execute(AbstractPromise* promise) override;
protected:
virtual void ExecuteThen(AbstractPromise* prerequisite,
AbstractPromise* promise) = 0;
virtual void ExecuteCatch(AbstractPromise* prerequisite,
AbstractPromise* promise) = 0;
// If |executor| is null then the value of |arg| is moved or copied into
// |result| and true is returned. Otherwise false is returned.
static bool ProcessNullCallback(const CallbackBase& executor,
AbstractPromise* arg,
AbstractPromise* result);
CallbackBase resolve_callback_;
CallbackBase reject_callback_;
};
// Tag signals no callback which is used to eliminate dead code.
struct NoCallback {};
struct CouldResolveOrReject {};
struct CanOnlyResolve {};
struct CanOnlyReject {};
template <bool can_resolve, bool can_reject>
struct CheckResultHelper;
template <>
struct CheckResultHelper<true, false> {
using TagType = CanOnlyResolve;
};
template <>
struct CheckResultHelper<true, true> {
using TagType = CouldResolveOrReject;
};
template <>
struct CheckResultHelper<false, true> {
using TagType = CanOnlyReject;
};
template <typename ResolveOnceCallback,
typename RejectOnceCallback,
typename ArgResolve,
typename ArgReject,
typename ResolveStorage,
typename RejectStorage>
class ThenAndCatchExecutor : public BaseThenAndCatchExecutor {
public:
using ResolveReturnT =
typename CallbackTraits<ResolveOnceCallback>::ReturnType;
using RejectReturnT = typename CallbackTraits<RejectOnceCallback>::ReturnType;
using PrerequisiteCouldResolve =
std::integral_constant<bool,
!std::is_same<ArgResolve, NoCallback>::value>;
using PrerequisiteCouldReject =
std::integral_constant<bool, !std::is_same<ArgReject, NoCallback>::value>;
ThenAndCatchExecutor(ResolveOnceCallback&& resolve_callback,
RejectOnceCallback&& reject_callback)
: BaseThenAndCatchExecutor(std::move(resolve_callback),
std::move(reject_callback)) {
static_assert(sizeof(CallbackBase) == sizeof(ResolveOnceCallback),
"We assume it's possible to cast from CallbackBase to "
"ResolveOnceCallback");
static_assert(sizeof(CallbackBase) == sizeof(RejectOnceCallback),
"We assume it's possible to cast from CallbackBase to "
"RejectOnceCallback");
}
~ThenAndCatchExecutor() override = default;
void ExecuteThen(AbstractPromise* prerequisite,
AbstractPromise* promise) override {
ExecuteThenInternal(prerequisite, promise, PrerequisiteCouldResolve());
}
void ExecuteCatch(AbstractPromise* prerequisite,
AbstractPromise* promise) override {
ExecuteCatchInternal(prerequisite, promise, PrerequisiteCouldReject());
}
#if DCHECK_IS_ON()
ArgumentPassingType ResolveArgumentPassingType() const override {
return resolve_callback_.is_null()
? AbstractPromise::Executor::ArgumentPassingType::kNoCallback
: CallbackTraits<ResolveOnceCallback>::argument_passing_type;
}
ArgumentPassingType RejectArgumentPassingType() const override {
return reject_callback_.is_null()
? AbstractPromise::Executor::ArgumentPassingType::kNoCallback
: CallbackTraits<RejectOnceCallback>::argument_passing_type;
}
bool CanResolve() const override {
return (!resolve_callback_.is_null() &&
PromiseCallbackTraits<ResolveReturnT>::could_resolve) ||
(!reject_callback_.is_null() &&
PromiseCallbackTraits<RejectReturnT>::could_resolve);
}
bool CanReject() const override {
return (!resolve_callback_.is_null() &&
PromiseCallbackTraits<ResolveReturnT>::could_reject) ||
(!reject_callback_.is_null() &&
PromiseCallbackTraits<RejectReturnT>::could_reject);
}
#endif
private:
void ExecuteThenInternal(AbstractPromise* prerequisite,
AbstractPromise* promise,
std::true_type can_resolve) {
ResolveOnceCallback* resolve_callback =
static_cast<ResolveOnceCallback*>(&resolve_callback_);
RunHelper<ResolveOnceCallback, Resolved<ArgResolve>, ResolveStorage,
RejectStorage>::Run(std::move(*resolve_callback), prerequisite,
promise);
using CheckResultTagType = typename CheckResultHelper<
PromiseCallbackTraits<ResolveReturnT>::could_resolve,
PromiseCallbackTraits<ResolveReturnT>::could_reject>::TagType;
CheckResultType(promise, CheckResultTagType());
}
void ExecuteThenInternal(AbstractPromise* prerequisite,
AbstractPromise* promise,
std::false_type can_resolve) {
// |prerequisite| can't resolve so don't generate dead code.
}
void ExecuteCatchInternal(AbstractPromise* prerequisite,
AbstractPromise* promise,
std::true_type can_reject) {
RejectOnceCallback* reject_callback =
static_cast<RejectOnceCallback*>(&reject_callback_);
RunHelper<RejectOnceCallback, Rejected<ArgReject>, ResolveStorage,
RejectStorage>::Run(std::move(*reject_callback), prerequisite,
promise);
using CheckResultTagType = typename CheckResultHelper<
PromiseCallbackTraits<RejectReturnT>::could_resolve,
PromiseCallbackTraits<RejectReturnT>::could_reject>::TagType;
CheckResultType(promise, CheckResultTagType());
}
void ExecuteCatchInternal(AbstractPromise* prerequisite,
AbstractPromise* promise,
std::false_type can_reject) {
// |prerequisite| can't reject so don't generate dead code.
}
void CheckResultType(AbstractPromise* promise, CouldResolveOrReject) {
if (promise->IsResolvedWithPromise() ||
promise->value().type() == TypeId::From<ResolveStorage>()) {
promise->OnResolved();
} else {
DCHECK_EQ(promise->value().type(), TypeId::From<RejectStorage>())
<< " See " << promise->from_here().ToString();
promise->OnRejected();
}
}
void CheckResultType(AbstractPromise* promise, CanOnlyResolve) {
promise->OnResolved();
}
void CheckResultType(AbstractPromise* promise, CanOnlyReject) {
promise->OnRejected();
}
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_PROMISE_THEN_AND_CATCH_EXECUTOR_H_
......@@ -40,8 +40,40 @@ bool PostTaskAndReplyTaskRunner::PostTask(const Location& from_here,
return destination_->PostTask(from_here, std::move(task));
}
// TODO(alexclarke): Remove this when TaskRunner::PostPromiseInternal becomes
// pure virtual.
class PromiseHolder {
public:
explicit PromiseHolder(scoped_refptr<internal::AbstractPromise> promise)
: promise_(std::move(promise)) {}
~PromiseHolder() {
// Detect if the promise was not executed and if so cancel to ensure memory
// is released.
if (promise_)
promise_->OnCanceled();
}
PromiseHolder(PromiseHolder&& other) : promise_(std::move(other.promise_)) {}
scoped_refptr<internal::AbstractPromise> Unwrap() const {
return std::move(promise_);
}
private:
mutable scoped_refptr<internal::AbstractPromise> promise_;
};
} // namespace
template <>
struct BindUnwrapTraits<PromiseHolder> {
static scoped_refptr<internal::AbstractPromise> Unwrap(
const PromiseHolder& o) {
return o.Unwrap();
}
};
bool TaskRunner::PostTask(const Location& from_here, OnceClosure task) {
return PostDelayedTask(from_here, std::move(task), base::TimeDelta());
}
......@@ -58,7 +90,8 @@ bool TaskRunner::PostPromiseInternal(
base::TimeDelta delay) {
return PostDelayedTask(
promise->from_here(),
BindOnce(&internal::AbstractPromise::Execute, std::move(promise)), delay);
BindOnce(&internal::AbstractPromise::Execute, PromiseHolder(promise)),
delay);
}
TaskRunner::TaskRunner() = default;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment