blob: 0f80c8a95e353f55bc4c26246f0190915957c21f [file] [log] [blame]
// 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/abstract_promise.h"
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/sequenced_task_runner.h"
#include "base/task/promise/dependent_list.h"
#include "base/task/promise/post_task_executor.h"
#include "base/threading/sequenced_task_runner_handle.h"
namespace base {
namespace internal {
#if DCHECK_IS_ON()
namespace {
// static
RepeatingClosure& GetPromiseApiErrorCallback() {
static NoDestructor<RepeatingClosure> on_api_error_callback;
return *on_api_error_callback;
}
} // namespace
// static
void AbstractPromise::SetApiErrorObserverForTesting(
RepeatingClosure on_api_error_callback) {
CheckedAutoLock lock(GetCheckedLock());
GetPromiseApiErrorCallback() = std::move(on_api_error_callback);
}
// Like DCHECK except observable via
// AbstractPromise::SetApiErrorObserverForTesting. Exists to avoid DEATH_TESTs
// which are flaky with promises.
#define PROMISE_API_DCHECK(condition) \
if (!(condition) && GetPromiseApiErrorCallback()) { \
GetPromiseApiErrorCallback().Run(); \
return; \
} \
DCHECK(condition)
#endif // DCHECK_IS_ON()
AbstractPromise::~AbstractPromise() {
#if DCHECK_IS_ON()
{
CheckedAutoLock lock(GetCheckedLock());
PROMISE_API_DCHECK(!must_catch_ancestor_that_could_reject_ ||
passed_catch_responsibility_)
<< "Promise chain ending at " << from_here_.ToString()
<< " didn't have a catch for potentially rejecting promise here "
<< must_catch_ancestor_that_could_reject_->from_here().ToString();
PROMISE_API_DCHECK(!this_must_catch_ || passed_catch_responsibility_)
<< "Potentially rejecting promise at " << from_here_.ToString()
<< " doesn't have a catch.";
}
#endif
// If we're not settled we might be retaining some promises which need to be
// released to prevent memory leaks. If we are settled this does nothing.
OnCanceled();
}
bool AbstractPromise::IsCanceled() const {
if (dependents_.IsCanceled())
return true;
const PromiseExecutor* executor = GetExecutor();
return executor && executor->IsCancelled();
}
void AbstractPromise::AddAsDependentForAllPrerequisites() {
if (!prerequisites_)
return;
// 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 (DependentList::Node& node : *prerequisites_->prerequisite_list()) {
node.dependent() = this;
// If |node.prerequisite()| was canceled then early out because
// |prerequisites_->prerequisite_list| will have been cleared.
DCHECK(node.prerequisite());
if (!node.prerequisite()->InsertDependentOnAnyThread(&node))
break;
}
}
bool AbstractPromise::InsertDependentOnAnyThread(DependentList::Node* node) {
scoped_refptr<AbstractPromise>& dependent = node->dependent();
// Used to ensure no reference to the dependent is kept in case the Promise is
// already settled.
scoped_refptr<AbstractPromise> dependent_to_release;
#if DCHECK_IS_ON()
{
CheckedAutoLock lock(GetCheckedLock());
DCHECK(node->dependent()) << from_here_.ToString();
node->dependent()->MaybeInheritChecks(this);
}
#endif
// If |dependents_| has been consumed (i.e. this promise has been resolved
// or rejected) then |node| may be ready to run now.
switch (dependents_.Insert(node)) {
case DependentList::InsertResult::SUCCESS:
break;
case DependentList::InsertResult::FAIL_PROMISE_RESOLVED: {
AbstractPromise* curried_promise = GetCurriedPromise();
if (curried_promise) {
// Try and reinsert |node| in the curried ancestor.
node->SetPrerequisite(curried_promise);
return curried_promise->InsertDependentOnAnyThread(node);
} else {
dependent_to_release = std::move(dependent);
node->RetainSettledPrerequisite();
dependent_to_release->OnPrerequisiteResolved(this);
}
break;
}
case DependentList::InsertResult::FAIL_PROMISE_REJECTED: {
AbstractPromise* curried_promise = GetCurriedPromise();
if (curried_promise) {
// Try and reinsert |node| in the curried ancestor.
node->SetPrerequisite(curried_promise);
return curried_promise->InsertDependentOnAnyThread(node);
} else {
dependent_to_release = std::move(dependent);
node->RetainSettledPrerequisite();
dependent_to_release->OnPrerequisiteRejected(this);
}
break;
}
case DependentList::InsertResult::FAIL_PROMISE_CANCELED:
dependent_to_release = std::move(dependent);
return dependent_to_release->OnPrerequisiteCancelled(this);
}
return true;
}
void AbstractPromise::IgnoreUncaughtCatchForTesting() {
#if DCHECK_IS_ON()
CheckedAutoLock lock(GetCheckedLock());
passed_catch_responsibility_ = true;
#endif
}
#if DCHECK_IS_ON()
// static
CheckedLock& AbstractPromise::GetCheckedLock() {
static base::NoDestructor<CheckedLock> instance;
return *instance;
}
void AbstractPromise::DoubleMoveDetector::CheckForDoubleMoveErrors(
const base::Location& new_dependent_location,
PromiseExecutor::ArgumentPassingType new_dependent_executor_type) {
switch (new_dependent_executor_type) {
case PromiseExecutor::ArgumentPassingType::kNoCallback:
return;
case PromiseExecutor::ArgumentPassingType::kNormal:
PROMISE_API_DCHECK(!dependent_move_only_promise_)
<< "Can't mix move only and non-move only " << callback_type_
<< "callback arguments for the same " << callback_type_
<< " prerequisite. See " << new_dependent_location.ToString()
<< " and " << dependent_move_only_promise_->ToString()
<< " with common ancestor " << from_here_.ToString();
dependent_normal_promise_ =
std::make_unique<Location>(new_dependent_location);
return;
case PromiseExecutor::ArgumentPassingType::kMove:
PROMISE_API_DCHECK(!dependent_move_only_promise_ ||
*dependent_move_only_promise_ ==
new_dependent_location)
<< "Can't have multiple move only " << callback_type_
<< " callbacks for same " << callback_type_ << " prerequisite. See "
<< new_dependent_location.ToString() << " and "
<< dependent_move_only_promise_->ToString() << " with common "
<< callback_type_ << " prerequisite " << from_here_.ToString();
PROMISE_API_DCHECK(!dependent_normal_promise_)
<< "Can't mix move only and non-move only " << callback_type_
<< " callback arguments for the same " << callback_type_
<< " prerequisite. See " << new_dependent_location.ToString()
<< " and " << dependent_normal_promise_->ToString() << " with common "
<< callback_type_ << " prerequisite " << from_here_.ToString();
dependent_move_only_promise_ =
std::make_unique<Location>(new_dependent_location);
return;
}
}
void AbstractPromise::MaybeInheritChecks(AbstractPromise* prerequisite) {
if (!ancestor_that_could_resolve_) {
// Inherit |prerequisite|'s resolve ancestor if it doesn't have a resolve
// callback.
if (prerequisite->resolve_argument_passing_type_ ==
PromiseExecutor::ArgumentPassingType::kNoCallback) {
ancestor_that_could_resolve_ = prerequisite->ancestor_that_could_resolve_;
}
// If |prerequisite| didn't have a resolve callback (but it's reject
// callback could resolve) or if
// |prerequisite->ancestor_that_could_resolve_| is null then assign
// |prerequisite->this_resolve_|.
if (!ancestor_that_could_resolve_ && prerequisite->executor_can_resolve_)
ancestor_that_could_resolve_ = prerequisite->this_resolve_;
}
if (!ancestor_that_could_reject_) {
// Inherit |prerequisite|'s reject ancestor if it doesn't have a Catch.
if (prerequisite->reject_argument_passing_type_ ==
PromiseExecutor::ArgumentPassingType::kNoCallback) {
ancestor_that_could_reject_ = prerequisite->ancestor_that_could_reject_;
}
// If |prerequisite| didn't have a reject callback (but it's resolve
// callback could reject) or if
// |prerequisite->ancestor_that_could_resolve_| is null then assign
// |prerequisite->this_reject_|.
if (!ancestor_that_could_reject_ && prerequisite->executor_can_reject_)
ancestor_that_could_reject_ = prerequisite->this_reject_;
}
if (!must_catch_ancestor_that_could_reject_) {
// Inherit |prerequisite|'s must catch ancestor if it doesn't have a Catch.
if (prerequisite->reject_argument_passing_type_ ==
PromiseExecutor::ArgumentPassingType::kNoCallback) {
must_catch_ancestor_that_could_reject_ =
prerequisite->must_catch_ancestor_that_could_reject_;
}
// If |prerequisite| didn't have a reject callback (but it's resolve
// callback could reject) or if
// |prerequisite->must_catch_ancestor_that_could_reject_| is null then
// assign |prerequisite->this_must_catch_|.
if (!must_catch_ancestor_that_could_reject_ &&
prerequisite->executor_can_reject_) {
must_catch_ancestor_that_could_reject_ = prerequisite->this_must_catch_;
}
}
if (ancestor_that_could_resolve_) {
ancestor_that_could_resolve_->CheckForDoubleMoveErrors(
from_here_, resolve_argument_passing_type_);
}
if (ancestor_that_could_reject_) {
ancestor_that_could_reject_->CheckForDoubleMoveErrors(
from_here_, reject_argument_passing_type_);
}
prerequisite->passed_catch_responsibility_ = true;
}
AbstractPromise::LocationRef::LocationRef(const Location& from_here)
: from_here_(from_here) {}
AbstractPromise::LocationRef::~LocationRef() = default;
AbstractPromise::DoubleMoveDetector::DoubleMoveDetector(
const Location& from_here,
const char* callback_type)
: from_here_(from_here), callback_type_(callback_type) {}
AbstractPromise::DoubleMoveDetector::~DoubleMoveDetector() = default;
#endif
AbstractPromise* AbstractPromise::GetCurriedPromise() {
if (!value_.ContainsCurriedPromise())
return nullptr;
return value_.Get<scoped_refptr<AbstractPromise>>()->get();
}
const PromiseExecutor* AbstractPromise::GetExecutor() const {
if (!value_.ContainsPromiseExecutor())
return nullptr;
return value_.Get<PromiseExecutor>();
}
PromiseExecutor::PrerequisitePolicy AbstractPromise::GetPrerequisitePolicy() {
PromiseExecutor* executor = GetExecutor();
if (!executor) {
// If there's no executor it's because the promise has already run. We
// can't run again however. The only circumstance in which we expect
// GetPrerequisitePolicy() to be called after execution is when it was
// resolved with a promise or we're already settled.
DCHECK(IsSettled());
return PromiseExecutor::PrerequisitePolicy::kNever;
}
return executor->GetPrerequisitePolicy();
}
AbstractPromise* AbstractPromise::GetFirstSettledPrerequisite() const {
DCHECK(prerequisites_);
return prerequisites_->GetFirstSettledPrerequisite();
}
void AbstractPromise::Execute() {
const PromiseExecutor* executor = GetExecutor();
DCHECK(executor || dependents_.IsCanceled()) << from_here_.ToString();
if (!executor || executor->IsCancelled()) {
OnCanceled();
return;
}
#if DCHECK_IS_ON()
// Clear |must_catch_ancestor_that_could_reject_| if we can catch it.
if (reject_argument_passing_type_ !=
PromiseExecutor::ArgumentPassingType::kNoCallback) {
CheckedAutoLock lock(GetCheckedLock());
must_catch_ancestor_that_could_reject_ = nullptr;
}
#endif
DCHECK(!IsResolvedWithPromise());
// To reduce template bloat the Executor machinery deals with AbstractPromise
// raw pointers. This is fine as long as we ensure the reference count doesn't
// go to zero when we run the promise callback (which could do anything
// including releasing what might otherwise be the last reference to the
// AbstractPromise).
scoped_refptr<AbstractPromise> protect(this);
// This is likely to delete the executor.
GetExecutor()->Execute(this);
if (value_.ContainsRejected()) {
OnRejected();
} else if (value_.ContainsResolved() || value_.ContainsCurriedPromise()) {
OnResolved();
}
}
void AbstractPromise::ReplaceCurriedPrerequisite(
AbstractPromise* curried_prerequisite,
AbstractPromise* replacement) {
DCHECK(curried_prerequisite->IsResolved() ||
curried_prerequisite->IsRejected());
DCHECK(curried_prerequisite->IsResolvedWithPromise());
DCHECK(replacement);
for (DependentList::Node& node : *prerequisites_->prerequisite_list()) {
if (node.prerequisite() == curried_prerequisite) {
node.Reset(replacement, this);
replacement->InsertDependentOnAnyThread(&node);
return;
}
}
NOTREACHED();
}
void AbstractPromise::OnPrerequisiteResolved(
AbstractPromise* resolved_prerequisite) {
DCHECK(resolved_prerequisite->IsResolved());
switch (GetPrerequisitePolicy()) {
case PromiseExecutor::PrerequisitePolicy::kAll:
if (prerequisites_->DecrementPrerequisiteCountAndCheckIfZero())
DispatchPromise();
break;
case PromiseExecutor::PrerequisitePolicy::kAny:
// PrerequisitePolicy::kAny should resolve immediately.
if (prerequisites_->MarkPrerequisiteAsSettling(resolved_prerequisite))
DispatchPromise();
break;
case PromiseExecutor::PrerequisitePolicy::kNever:
break;
}
}
void AbstractPromise::OnPrerequisiteRejected(
AbstractPromise* rejected_prerequisite) {
DCHECK(rejected_prerequisite->IsRejected());
// Promises::All (or Race if we add that) can have multiple prerequisites and
// it will reject as soon as any prerequisite rejects. Multiple prerequisites
// can reject, but we wish to record only the first one. Also we can only
// invoke executors once.
if (prerequisites_->MarkPrerequisiteAsSettling(rejected_prerequisite)) {
DispatchPromise();
}
}
bool AbstractPromise::OnPrerequisiteCancelled(
AbstractPromise* canceled_prerequisite) {
switch (GetPrerequisitePolicy()) {
case PromiseExecutor::PrerequisitePolicy::kAll:
// PrerequisitePolicy::kAll should cancel immediately.
OnCanceled();
return false;
case PromiseExecutor::PrerequisitePolicy::kAny:
// PrerequisitePolicy::kAny should only cancel if all if it's
// pre-requisites have been canceled.
if (prerequisites_->DecrementPrerequisiteCountAndCheckIfZero()) {
OnCanceled();
return false;
} else {
prerequisites_->RemoveCanceledPrerequisite(canceled_prerequisite);
}
return true;
case PromiseExecutor::PrerequisitePolicy::kNever:
// If we where resolved with a promise then we can't have had
// PrerequisitePolicy::kAny or PrerequisitePolicy::kNever before the
// executor was replaced with the curried promise, so pass on
// cancellation.
if (IsResolvedWithPromise())
OnCanceled();
return false;
}
}
void AbstractPromise::OnResolveDispatchReadyDependents() {
class Visitor : public DependentList::Visitor {
public:
explicit Visitor(AbstractPromise* resolved_prerequisite)
: resolved_prerequisite_(resolved_prerequisite) {}
private:
void Visit(scoped_refptr<AbstractPromise> dependent) override {
dependent->OnPrerequisiteResolved(resolved_prerequisite_);
}
AbstractPromise* const resolved_prerequisite_;
};
Visitor visitor(this);
dependents_.ResolveAndConsumeAllDependents(&visitor);
}
void AbstractPromise::OnRejectDispatchReadyDependents() {
class Visitor : public DependentList::Visitor {
public:
explicit Visitor(AbstractPromise* rejected_prerequisite)
: rejected_prerequisite_(rejected_prerequisite) {}
private:
void Visit(scoped_refptr<AbstractPromise> dependent) override {
dependent->OnPrerequisiteRejected(rejected_prerequisite_);
}
AbstractPromise* const rejected_prerequisite_;
};
Visitor visitor(this);
dependents_.RejectAndConsumeAllDependents(&visitor);
}
void AbstractPromise::OnResolveMakeDependantsUseCurriedPrerequisite(
AbstractPromise* non_curried_root) {
class Visitor : public DependentList::Visitor {
public:
explicit Visitor(AbstractPromise* resolved_prerequisite,
AbstractPromise* non_curried_root)
: resolved_prerequisite_(resolved_prerequisite),
non_curried_root_(non_curried_root) {}
private:
void Visit(scoped_refptr<AbstractPromise> dependent) override {
dependent->ReplaceCurriedPrerequisite(resolved_prerequisite_,
non_curried_root_);
}
AbstractPromise* const resolved_prerequisite_;
AbstractPromise* const non_curried_root_;
};
Visitor visitor(this, non_curried_root);
dependents_.ResolveAndConsumeAllDependents(&visitor);
}
void AbstractPromise::OnRejectMakeDependantsUseCurriedPrerequisite(
AbstractPromise* non_curried_root) {
class Visitor : public DependentList::Visitor {
public:
explicit Visitor(AbstractPromise* rejected_prerequisite,
AbstractPromise* non_curried_root)
: rejected_prerequisite_(rejected_prerequisite),
non_curried_root_(non_curried_root) {}
private:
void Visit(scoped_refptr<AbstractPromise> dependent) override {
dependent->ReplaceCurriedPrerequisite(rejected_prerequisite_,
non_curried_root_);
}
AbstractPromise* const rejected_prerequisite_;
AbstractPromise* const non_curried_root_;
};
Visitor visitor(this, non_curried_root);
dependents_.RejectAndConsumeAllDependents(&visitor);
}
void AbstractPromise::DispatchPromise() {
if (task_runner_) {
task_runner_->PostPromiseInternal(WrappedPromise(this), TimeDelta());
} else {
Execute();
}
}
void AbstractPromise::OnCanceled() {
class Visitor : public DependentList::Visitor {
public:
explicit Visitor(AbstractPromise* canceled_prerequisite)
: canceled_prerequisite_(canceled_prerequisite) {}
private:
void Visit(scoped_refptr<AbstractPromise> dependent) override {
dependent->OnPrerequisiteCancelled(canceled_prerequisite_);
}
AbstractPromise* const canceled_prerequisite_;
};
Visitor visitor(this);
if (!dependents_.CancelAndConsumeAllDependents(&visitor))
return;
// The executor could be keeping a promise alive, but it's never going to run
// so clear it.
value_.reset();
#if DCHECK_IS_ON()
{
CheckedAutoLock lock(GetCheckedLock());
passed_catch_responsibility_ = true;
}
#endif
if (prerequisites_)
prerequisites_->Clear();
}
AbstractPromise* AbstractPromise::FindCurriedAncestor() {
AbstractPromise* promise = this;
while (promise->IsSettled()) {
if (promise->IsCanceled())
return nullptr;
if (!promise->value_.ContainsCurriedPromise())
break;
promise = promise->value_.Get<scoped_refptr<AbstractPromise>>()->get();
}
return promise;
}
void AbstractPromise::OnResolved() {
#if DCHECK_IS_ON()
PROMISE_API_DCHECK(executor_can_resolve_ || IsResolvedWithPromise())
<< from_here_.ToString();
#endif
if (AbstractPromise* curried_promise = GetCurriedPromise()) {
#if DCHECK_IS_ON()
{
CheckedAutoLock lock(GetCheckedLock());
MaybeInheritChecks(curried_promise);
}
#endif
curried_promise = curried_promise->FindCurriedAncestor();
if (!curried_promise) {
OnCanceled();
return;
}
OnResolveMakeDependantsUseCurriedPrerequisite(curried_promise);
} else {
OnResolveDispatchReadyDependents();
}
if (prerequisites_)
prerequisites_->Clear();
}
void AbstractPromise::OnRejected() {
#if DCHECK_IS_ON()
PROMISE_API_DCHECK(executor_can_reject_) << from_here_.ToString();
#endif
if (AbstractPromise* curried_promise = GetCurriedPromise()) {
#if DCHECK_IS_ON()
{
CheckedAutoLock lock(GetCheckedLock());
MaybeInheritChecks(curried_promise);
}
#endif
curried_promise = curried_promise->FindCurriedAncestor();
// It shouldn't be possible for OnRejected to be called with a canceled
// curried promise because AbstractPromise::Execute regards a curried as a
// resolved promise.
DCHECK(curried_promise);
OnRejectMakeDependantsUseCurriedPrerequisite(curried_promise);
} else {
OnRejectDispatchReadyDependents();
}
if (prerequisites_)
prerequisites_->Clear();
}
AbstractPromise::AdjacencyList::AdjacencyList() = default;
AbstractPromise::AdjacencyList::AdjacencyList(AbstractPromise* prerequisite)
: prerequisite_list_(1), action_prerequisite_count_(1) {
prerequisite_list_[0].SetPrerequisite(prerequisite);
}
AbstractPromise::AdjacencyList::AdjacencyList(
std::vector<DependentList::Node> nodes)
: prerequisite_list_(std::move(nodes)),
action_prerequisite_count_(prerequisite_list_.size()) {}
AbstractPromise::AdjacencyList::~AdjacencyList() = default;
bool AbstractPromise::AdjacencyList::
DecrementPrerequisiteCountAndCheckIfZero() {
return action_prerequisite_count_.fetch_sub(1, std::memory_order_acq_rel) ==
1;
}
// For PrerequisitePolicy::kAll this is called for the first rejected
// prerequisite. For PrerequisitePolicy:kAny this is called for the first
// resolving or rejecting prerequisite.
bool AbstractPromise::AdjacencyList::MarkPrerequisiteAsSettling(
AbstractPromise* settled_prerequisite) {
DCHECK(settled_prerequisite->IsSettled());
uintptr_t expected = 0;
return first_settled_prerequisite_.compare_exchange_strong(
expected, reinterpret_cast<uintptr_t>(settled_prerequisite),
std::memory_order_acq_rel);
}
void AbstractPromise::AdjacencyList::RemoveCanceledPrerequisite(
AbstractPromise* canceled_prerequisite) {
DCHECK(canceled_prerequisite->IsCanceled());
for (DependentList::Node& node : prerequisite_list_) {
if (node.prerequisite() == canceled_prerequisite) {
node.ClearPrerequisite();
return;
}
}
NOTREACHED() << "Couldn't find canceled_prerequisite "
<< canceled_prerequisite->from_here().ToString();
}
void AbstractPromise::AdjacencyList::Clear() {
// If there's only one prerequisite we can just clear |prerequisite_list_|
// which deals with potential refcounting cycles due to curried promises.
if (prerequisite_list_.size() == 1) {
prerequisite_list_.clear();
} else {
// If there's multiple prerequisites we can't do that because the
// DependentList::Nodes may still be in use by some of them.
// Instead we release our prerequisite references and rely on refcounting to
// release the owning AbstractPromise.
for (DependentList::Node& node : prerequisite_list_) {
node.ClearPrerequisite();
}
}
}
BasePromise::BasePromise() = default;
BasePromise::BasePromise(
scoped_refptr<internal::AbstractPromise> abstract_promise)
: abstract_promise_(std::move(abstract_promise)) {}
BasePromise::BasePromise(const BasePromise& other) = default;
BasePromise::BasePromise(BasePromise&& other) = default;
BasePromise& BasePromise::operator=(const BasePromise& other) = default;
BasePromise& BasePromise::operator=(BasePromise&& other) = default;
BasePromise::~BasePromise() = default;
} // namespace internal
WrappedPromise::WrappedPromise() = default;
WrappedPromise::WrappedPromise(scoped_refptr<internal::AbstractPromise> promise)
: promise_(std::move(promise)) {}
WrappedPromise::WrappedPromise(internal::PassedPromise&& passed_promise)
: promise_(passed_promise.Release(), subtle::kAdoptRefTag) {
DCHECK(promise_);
}
WrappedPromise::WrappedPromise(const Location& from_here, OnceClosure task)
: WrappedPromise(internal::AbstractPromise::CreateNoPrerequisitePromise(
from_here,
RejectPolicy::kMustCatchRejection,
internal::DependentList::ConstructUnresolved(),
internal::PromiseExecutor::Data(
base::in_place_type_t<internal::PostTaskExecutor<void>>(),
std::move(task)))) {}
WrappedPromise::WrappedPromise(const WrappedPromise& other) = default;
WrappedPromise::WrappedPromise(WrappedPromise&& other) = default;
WrappedPromise& WrappedPromise::operator=(const WrappedPromise& other) =
default;
WrappedPromise& WrappedPromise::operator=(WrappedPromise&& other) = default;
WrappedPromise::~WrappedPromise() = default;
void WrappedPromise::Execute() {
DCHECK(promise_);
promise_->Execute();
}
void WrappedPromise::Clear() {
promise_ = nullptr;
}
} // namespace base