| // 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_ |