blob: 6cbbf345ef242ecf191ac5d3e1b390c4322c65a6 [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/dependent_list.h"
#include <algorithm>
#include <atomic>
#include <cstdint>
#include "base/logging.h"
#include "base/task/promise/abstract_promise.h"
namespace base {
namespace internal {
// static
DependentList::Node* DependentList::ReverseList(DependentList::Node* list) {
DependentList::Node* prev = nullptr;
while (list) {
DependentList::Node* next = list->next_;
list->next_ = prev;
prev = list;
list = next;
}
return prev;
}
// static
void DependentList::DispatchAll(DependentList::Node* head,
DependentList::Visitor* visitor,
bool retain_prerequsites) {
head = ReverseList(head);
DependentList::Node* next = nullptr;
while (head) {
next = head->next_;
if (retain_prerequsites)
head->RetainSettledPrerequisite();
// |visitor| might delete the node, so no access to node past this
// call!
visitor->Visit(std::move(head->dependent_));
head = next;
}
}
DependentList::Visitor::~Visitor() = default;
DependentList::Node::Node() = default;
DependentList::Node::Node(Node&& other) noexcept {
prerequisite_ = other.prerequisite_.load(std::memory_order_relaxed);
other.prerequisite_ = 0;
dependent_ = std::move(other.dependent_);
DCHECK_EQ(other.next_, nullptr);
}
DependentList::Node::Node(AbstractPromise* prerequisite,
scoped_refptr<AbstractPromise> dependent)
: prerequisite_(reinterpret_cast<intptr_t>(prerequisite)),
dependent_(std::move(dependent)) {}
DependentList::Node::~Node() {
ClearPrerequisite();
}
DependentList::DependentList(State initial_state)
: data_(CreateData(nullptr,
initial_state,
initial_state == State::kUnresolved ? kAllowInserts
: kBlockInserts)) {}
DependentList::DependentList(ConstructUnresolved)
: DependentList(State::kUnresolved) {}
DependentList::DependentList(ConstructResolved)
: DependentList(State::kResolved) {}
DependentList::DependentList(ConstructRejected)
: DependentList(State::kRejected) {}
DependentList::~DependentList() = default;
void DependentList::Node::Reset(AbstractPromise* prerequisite,
scoped_refptr<AbstractPromise> dependent) {
SetPrerequisite(prerequisite);
dependent_ = std::move(dependent);
next_ = nullptr;
}
void DependentList::Node::SetPrerequisite(AbstractPromise* prerequisite) {
DCHECK(prerequisite);
intptr_t prev_value = prerequisite_.exchange(
reinterpret_cast<intptr_t>(prerequisite), std::memory_order_acq_rel);
if (prev_value & kIsRetained)
reinterpret_cast<AbstractPromise*>(prev_value & ~kIsRetained)->Release();
}
AbstractPromise* DependentList::Node::prerequisite() const {
return reinterpret_cast<AbstractPromise*>(
prerequisite_.load(std::memory_order_acquire) & ~kIsRetained);
}
void DependentList::Node::RetainSettledPrerequisite() {
intptr_t prerequisite = prerequisite_.load(std::memory_order_acquire);
DCHECK((prerequisite & kIsRetained) == 0) << "May only be called once";
if (!prerequisite)
return;
// Mark as retained, note we could have another thread trying to call
// ClearPrerequisite.
if (prerequisite_.compare_exchange_strong(
prerequisite, prerequisite | kIsRetained, std::memory_order_release,
std::memory_order_acquire)) {
reinterpret_cast<AbstractPromise*>(prerequisite)->AddRef();
}
}
void DependentList::Node::ClearPrerequisite() {
intptr_t prerequisite = prerequisite_.exchange(0, std::memory_order_acq_rel);
if (prerequisite & kIsRetained)
reinterpret_cast<AbstractPromise*>(prerequisite & ~kIsRetained)->Release();
}
DependentList::InsertResult DependentList::Insert(Node* node) {
DCHECK(!node->next_);
// std::memory_order_acquire for hapens-after relation with
// SettleAndDispatchAllDependents completing and thus this this call returning
// an error.
uintptr_t prev_data = data_.load(std::memory_order_acquire);
bool did_insert = false;
while (IsAllowingInserts(prev_data) && !did_insert) {
node->next_ = ExtractHead(prev_data);
// On success std::memory_order_release so that all memory operations become
// visible in SettleAndDispatchAllDependents when iterating the list.
// On failure std::memory_order_acquire for happens-after relation with
// SettleAndDispatchAllDependents completing and thus this this call
// returning an error.
//
// Note: ABA is not an issue here as we do not care that head_ might now be
// pointing to a different node (but with same address) we only need to
// guarantee that node->next points to the current head (which is now the
// new node but with the same address so node->next is still valid).
if (data_.compare_exchange_weak(
prev_data, CreateData(node, ExtractState(prev_data), kAllowInserts),
std::memory_order_seq_cst, std::memory_order_seq_cst)) {
did_insert = true;
} else {
// Cleanup in case the loop terminates
node->next_ = nullptr;
}
}
if (did_insert) {
return InsertResult::SUCCESS;
}
switch (ExtractState(prev_data)) {
case State::kResolved:
return InsertResult::FAIL_PROMISE_RESOLVED;
case State::kRejected:
return InsertResult::FAIL_PROMISE_REJECTED;
case State::kCanceled:
return InsertResult::FAIL_PROMISE_CANCELED;
case State::kUnresolved:
// We must have inserted, as inserts must be allowed if in state
// kUnresolved
NOTREACHED();
return InsertResult::SUCCESS;
}
}
bool DependentList::SettleAndDispatchAllDependents(const State settled_state,
Visitor* visitor) {
DCHECK_NE(settled_state, State::kUnresolved);
// Whether this invocation won the settlement race. If so, it will now keep
// dispatching all nodes in as many attempts as it takes to win the race
// against Insert()'s.
bool did_set_state = false;
// This load, and the ones in for compare_exchange_weak failures can be
// std::memory_order_relaxed as we do not make any ordering guarantee when
// this method returns false.
uintptr_t prev_data = data_.load(std::memory_order_seq_cst);
while (true) {
if (!did_set_state && ExtractState(prev_data) != State::kUnresolved) {
// Somebody else set the state.
return false;
}
if (!IsListEmpty(prev_data)) {
// List is not empty and we might have set the state previously
// We need to:
// * Settle the state (or leave as is if we already did).
// * allow_inserts. As we are going to dispatch the nodes we need to
// make sure that other threads can still insert to the list
// (instead of just resolving themselves) to preserve dispatch order.
// * Take ownership of all nodes currently in the queue, i.e. set the
// head to nullptr, marking it empty
DCHECK_EQ(ExtractState(prev_data),
did_set_state ? settled_state : State::kUnresolved);
DCHECK(IsAllowingInserts(prev_data));
uintptr_t new_data = CreateData(nullptr, settled_state, kAllowInserts);
// On success std::memory_order_acquire for happens-after relation with
// with the last successful Insert().
if (!data_.compare_exchange_weak(prev_data, new_data,
std::memory_order_seq_cst,
std::memory_order_relaxed)) {
continue;
}
did_set_state = true;
// We don't want to retain prerequisites when cancelling.
DispatchAll(ExtractHead(prev_data), visitor,
settled_state != State::kCanceled);
prev_data = new_data;
}
// List is empty and we might have set the state previously, so we
// can settle the state (or leave as is if we already did) and freeze
// the list.
DCHECK(IsListEmpty(prev_data));
DCHECK_EQ(ExtractState(prev_data),
did_set_state ? settled_state : State::kUnresolved);
// On success std::memory_order_release for happens-before relation with
// Insert returning an error.
if (data_.compare_exchange_weak(
prev_data, CreateData(nullptr, settled_state, kBlockInserts),
std::memory_order_seq_cst, std::memory_order_relaxed)) {
// Inserts no longer allowed, state settled and list is empty. We are
// done!
return true;
}
}
}
// The following methods do not make any ordering guarantees, false return
// values are always stale. A true return value just means that
// SettleAndDispatchAllDependents was called but it gives no guarantees as
// whether any of the nodes was dispatched or the call finished. Thus
// std::memory_order_relaxed.
bool DependentList::IsSettled() const {
return ExtractState(data_.load(std::memory_order_seq_cst)) !=
State::kUnresolved;
}
bool DependentList::IsResolved() const {
DCHECK(IsSettled()) << "This check is racy";
return ExtractState(data_.load(std::memory_order_seq_cst)) ==
State::kResolved;
}
bool DependentList::IsRejected() const {
DCHECK(IsSettled()) << "This check is racy";
return ExtractState(data_.load(std::memory_order_seq_cst)) ==
State::kRejected;
}
bool DependentList::IsCanceled() const {
return ExtractState(data_.load(std::memory_order_seq_cst)) ==
State::kCanceled;
}
bool DependentList::IsResolvedForTesting() const {
return ExtractState(data_.load(std::memory_order_seq_cst)) ==
State::kResolved;
}
bool DependentList::IsRejectedForTesting() const {
return ExtractState(data_.load(std::memory_order_seq_cst)) ==
State::kRejected;
}
} // namespace internal
} // namespace base