blob: 020bdbfc77f579e7d099366c911c31c0be73fde5 [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.
#ifndef BASE_TASK_PROMISE_DEPENDENT_LIST_H_
#define BASE_TASK_PROMISE_DEPENDENT_LIST_H_
#include <atomic>
#include <cstdint>
#include <type_traits>
#include "base/base_export.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
namespace base {
namespace internal {
// Returns 2^N where N is the smallest N possible so that 2^N > value.
constexpr uintptr_t NextPowerOfTwo(uintptr_t value) {
// Keep setting 1's to the right of the first one until there are only 1's. In
// each iteration we double the number of 1's that we set. At last add 1 and
// we have the next power of 2.
for (size_t i = 1; i < sizeof(uintptr_t) * 8; i <<= 1) {
value |= value >> i;
}
return value + 1;
}
class AbstractPromise;
// AbstractPromise needs to know which promises depend upon it. This lock free
// class stores the list of dependents. This is not a general purpose list
// because the data can only be consumed once.
//
// This class is thread safe.
class BASE_EXPORT DependentList {
public:
struct ConstructUnresolved {};
struct ConstructResolved {};
struct ConstructRejected {};
explicit DependentList(ConstructUnresolved);
explicit DependentList(ConstructResolved);
explicit DependentList(ConstructRejected);
~DependentList();
DependentList(const DependentList&) = delete;
DependentList& operator=(const DependentList&) = delete;
enum class InsertResult {
SUCCESS,
FAIL_PROMISE_RESOLVED,
FAIL_PROMISE_REJECTED,
FAIL_PROMISE_CANCELED,
};
// Align Node on an 8-byte boundary to ensure the first 3 bits are 0 and can
// be used to store additional state (see static_asserts below).
class BASE_EXPORT alignas(8) Node {
public:
Node();
explicit Node(Node&& other) noexcept;
// Constructs a Node, |prerequisite| will not be retained unless
// RetainSettledPrerequisite is called.
Node(AbstractPromise* prerequisite,
scoped_refptr<AbstractPromise> dependent);
~Node();
// Caution this is not thread safe.
void Reset(AbstractPromise* prerequisite,
scoped_refptr<AbstractPromise> dependent);
// Expected prerequisite usage:
// 1. prerequisite = null on creation (or is constructed with a value)
// 2. (optional, once only) SetPrerequisite(value)
// 3. (maybe, once only) RetainSettledPrerequisite();
// 4. (maybe) ClearPrerequisite()
// 5. Destructor called
// Can be called on any thread.
void SetPrerequisite(AbstractPromise* prerequisite);
// Can be called on any thread.
AbstractPromise* prerequisite() const;
scoped_refptr<AbstractPromise>& dependent() { return dependent_; }
const scoped_refptr<AbstractPromise>& dependent() const {
return dependent_;
}
Node* next() const { return next_; }
// Calls AddRef on |prerequisite()| and marks the prerequisite as being
// retained. The |prerequisite()| will be released by Node's destructor or
// a call to ClearPrerequisite. Does nothing if called more than once.
// Can be called on any thread at any time. Can be called once only.
void RetainSettledPrerequisite();
// Calls Release() if the rerequsite was retained and then sets
// |prerequisite_| to zero. Can be called on any thread at any time. Can be
// called more than once.
void ClearPrerequisite();
private:
friend class DependentList;
void MarkAsRetained() { prerequisite_ |= kIsRetained; }
// An AbstractPromise* where the LSB is a flag which specified if it's
// retained or not.
// A reference for |prerequisite_| is acquired with an explicit call to
// AddRef() if it's resolved or rejected.
std::atomic<intptr_t> prerequisite_{0};
scoped_refptr<AbstractPromise> dependent_;
Node* next_ = nullptr;
static constexpr intptr_t kIsRetained = 1;
};
// Insert will only succeed if neither ResolveAndConsumeAllDependents nor
// RejectAndConsumeAllDependents nor CancelAndConsumeAllDependents have been
// called yet. If the call succeeds, |node| must remain valid pointer until it
// is consumed by one of the *AndConsumeAllDependents methods. If none of
// those methods is called |node| must only be valid for the duration of this
// call. Nodes will be consumed in the same order as they are inserted.
InsertResult Insert(Node* node);
// Callback for *AndConsumeAllDependents methods.
// TODO(carlscab): Consider using a callable object instead.
class BASE_EXPORT Visitor {
public:
virtual ~Visitor();
// Called from the *AndConsumeAllDependents methods for each node.
// |dependent| is the consumed (i.e. moved) from the one associated with the
// node. It is fine if the pointer to the node becomes invalid inside this
// call (i.e it is fine to delete the node).
virtual void Visit(scoped_refptr<AbstractPromise> dependent) = 0;
};
// The following *AndConsumeAllDependents methods will settle the list and
// consume all previously inserted nodes. It is guaranteed that Insert()
// failures will happen-after all nodes have been consumed. In particular that
// means that if an Insert happens while we are still consuming nodes the
// Insert will succeed and the node will be appended to the list of nodes to
// consume and eventually be consumed.
//
// ATTENTION: Calls to any of this methods will fail if itself or a different
// consume method has been previously called. ResolveAndConsumeAllDependents
// and RejectAndConsumeAllDependents will DCHECK on failures and
// CancelAndConsumeAllDependents will return false if it fails.
void ResolveAndConsumeAllDependents(Visitor* visitor) {
const bool success =
SettleAndDispatchAllDependents(State::kResolved, visitor);
DCHECK(success) << "Was already settled";
}
void RejectAndConsumeAllDependents(Visitor* visitor) {
const bool success =
SettleAndDispatchAllDependents(State::kRejected, visitor);
DCHECK(success) << "Was already settled";
}
// TODO(alexclarke): Consider DCHECK for failures which would also allow us to
// greatly simplify SettleAndDispatchAllDependents
bool CancelAndConsumeAllDependents(Visitor* visitor) {
return SettleAndDispatchAllDependents(State::kCanceled, visitor);
}
// Returns true if any of IsResolved, IsRejected, or IsCanceled would return
// true
bool IsSettled() const;
// Returns true if (Resolve/Reject/Cancel)AndConsumeAllDependents
// has resolved/rejected/canceled the promise, respectively.
//
// ATTENTION: No guarantees are made as of whether the
// (Resolve/Reject/Cancel)AndConsumeAllDependents method is still executing.
bool IsCanceled() const;
// DCHECKs if not settled.
bool IsResolved() const;
// DCHECKs if not settled.
bool IsRejected() const;
// Like the above but doesn't DCHECK if unsettled.
bool IsResolvedForTesting() const;
bool IsRejectedForTesting() const;
private:
// The data for this class is:
// * head: Pointer to the head of the list of Node instances
// * allow_inserts: flag indicating whether further inserts are allowed
// * state: State value
//
// We store all this information in a uintptr_t to support atomic operations
// as follows:
// PP...PPPFSS
// * P: Pointer to the head of the list of Node instances (head)
// * F: Flag inidicating whether further inserts are allowed (allow_inserts)
// * S: State value (state)
//
// The various *Mask constants contain the bit masks for the various fields.
//
// Inserts can be allowed in any of the states, but they MUST be allowed in
// State::kUnresolved. Inserts are allowed while in one of the settled states
// while the SettleAndDispatchAllDependents is dispatching nodes. This is done
// so to preserve dispatch order. Once all nodes have been dispatched (i.e.
// the list is empty), the allow_inserts is atomically (making sure list is
// still empty) set to false. From that point on Inserts will fail.
//
// All valid state transitions start from State::kUnresolved i.e. only the
// first call to SettleAndDispatchAllDependents will be able to settle the
// state and succeed, all others will fail.
//
// The Is(Resolved|Rejected|Canceled) methods must return true while we are
// dispatching nodes. That is we need to access the settled state while we are
// still dispatching nodes. Thus we need and extra bit (allow_inserts) so that
// Insert can determine whether to insert or fail when there is a settled
// state.
enum class InsertPolicy {
kAllow,
kBlock,
};
static constexpr auto kAllowInserts = InsertPolicy::kAllow;
static constexpr auto kBlockInserts = InsertPolicy::kBlock;
enum class State {
kUnresolved = 0,
kResolved,
kRejected,
kCanceled,
kLastValue = kCanceled
};
static constexpr uintptr_t kStateMask =
NextPowerOfTwo(static_cast<uintptr_t>(State::kLastValue)) - 1;
static constexpr uintptr_t kAllowInsertsBitMask = kStateMask + 1;
static constexpr uintptr_t kHeadMask = ~(kAllowInsertsBitMask | kStateMask);
static_assert(
std::alignment_of<Node>() > kAllowInsertsBitMask,
"Will not be able to hold the Node* and all the state in a uintptr_t");
static State ExtractState(uintptr_t data) {
return static_cast<State>(data & kStateMask);
}
static DependentList::Node* ExtractHead(uintptr_t data) {
return reinterpret_cast<DependentList::Node*>(data & kHeadMask);
}
static bool IsListEmpty(uintptr_t data) {
return ExtractHead(data) == nullptr;
}
static bool IsAllowingInserts(uintptr_t data) {
return data & kAllowInsertsBitMask;
}
static uintptr_t CreateData(Node* head,
State state,
InsertPolicy insert_policy) {
DCHECK_EQ(uintptr_t(head), uintptr_t(head) & kHeadMask)
<< "Node doesn't have enough alignment";
DCHECK(insert_policy == kAllowInserts || head == nullptr)
<< "List must be empty if no more inserts are allowed";
DCHECK(insert_policy == kAllowInserts || state != State::kUnresolved)
<< "Can not block inserts and remain in kUnresolved state";
return reinterpret_cast<uintptr_t>(head) |
(insert_policy == kAllowInserts ? kAllowInsertsBitMask : 0) |
(static_cast<uintptr_t>(state) & kStateMask);
}
explicit DependentList(State initial_state);
// Settles the list and consumes all previously inserted nodes. If the list is
// already settled it does nothing and returns false, true otherwise.
bool SettleAndDispatchAllDependents(State settled_state, Visitor* visitor);
static DependentList::Node* ReverseList(DependentList::Node* list);
// Goes through the list starting at |head| consuming node->dependent and
// passing it to the provided |visitor|.
static void DispatchAll(DependentList::Node* head,
DependentList::Visitor* visitor,
bool retain_prerequsites);
std::atomic<uintptr_t> data_;
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_PROMISE_DEPENDENT_LIST_H_