1 // Copyright 2018 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef BASE_TASK_COMMON_OPERATIONS_CONTROLLER_H_ 6 #define BASE_TASK_COMMON_OPERATIONS_CONTROLLER_H_ 7 8 #include <atomic> 9 #include <cstdint> 10 11 #include "base/base_export.h" 12 #include "base/memory/raw_ptr_exclusion.h" 13 #include "base/synchronization/waitable_event.h" 14 15 namespace base { 16 namespace internal { 17 18 // A lock-free thread-safe controller to manage critical multi-threaded 19 // operations without locks. 20 // 21 // The controller is used to determine if operations are allowed, and to keep 22 // track of how many are currently active. Users will call TryBeginOperation() 23 // before starting such operations. If the call succeeds the user can run the 24 // operation and the controller will keep track of it until the user signals 25 // that the operation is completed. No operations are allowed before 26 // StartAcceptingOperations() is called, or after 27 // ShutdownAndWaitForZeroOperations() is called. 28 // 29 // There is no explicit way of telling the controller when an operation is 30 // completed, instead for convenience TryBeginOperation() will return a RAII 31 // like object that will do so on destruction. 32 // 33 // For example: 34 // 35 // OperationsController controller_; 36 // 37 // void SetUp() { 38 // controller_.StartAcceptingOperations(); 39 // } 40 // 41 // void TearDown() { 42 // controller_.ShutdownAndWaitForZeroOperations(); 43 // } 44 // 45 // void MaybeRunOperation() { 46 // auto operation_token = controller_.TryBeginOperation(); 47 // if (operation_token) { 48 // Process(); 49 // } 50 // } 51 // 52 // This class is thread-safe. 53 // But note that StartAcceptingOperations can never be called after 54 // ShutdownAndWaitForZeroOperations. 55 class BASE_EXPORT OperationsController { 56 public: 57 // The owner of an OperationToken which evaluates to true can safely perform 58 // an operation while being certain it happens-after 59 // StartAcceptingOperations() and happens-before 60 // ShutdownAndWaitForZeroOperations(). Releasing this OperationToken 61 // relinquishes this right. 62 // 63 // This class is thread-safe 64 class OperationToken { 65 public: ~OperationToken()66 ~OperationToken() { 67 if (outer_) 68 outer_->DecrementBy(1); 69 } 70 OperationToken(const OperationToken&) = delete; OperationToken(OperationToken && other)71 OperationToken(OperationToken&& other) { 72 this->outer_ = other.outer_; 73 other.outer_ = nullptr; 74 } 75 76 operator bool() const { return !!outer_; } 77 78 private: 79 friend class OperationsController; OperationToken(OperationsController * outer)80 explicit OperationToken(OperationsController* outer) : outer_(outer) {} 81 82 // `outer_` is not a raw_ptr<...> for performance reasons (based on analysis 83 // of sampling profiler data and tab_search:top100:2020). 84 RAW_PTR_EXCLUSION OperationsController* outer_; 85 }; 86 87 OperationsController(); 88 89 // Users must call ShutdownAndWaitForZeroOperations() before destroying an 90 // instance of this class if StartAcceptingOperations() was called. 91 ~OperationsController(); 92 93 OperationsController(const OperationsController&) = delete; 94 OperationsController& operator=(const OperationsController&) = delete; 95 96 // Starts to accept operations (before this point TryBeginOperation() returns 97 // an invalid token). Returns true if an attempt to perform an operation was 98 // made and denied before StartAcceptingOperations() was called. Can be called 99 // at most once, never after ShutdownAndWaitForZeroOperations(). 100 bool StartAcceptingOperations(); 101 102 // Returns a RAII like object that implicitly converts to true if operations 103 // are allowed i.e. if this call happens-after StartAcceptingOperations() and 104 // happens-before Shutdown(), otherwise the object will convert to false. On 105 // successful return, this OperationsController will keep track of the 106 // operation until the returned object goes out of scope. 107 OperationToken TryBeginOperation(); 108 109 // Prevents further calls to TryBeginOperation() from succeeding and waits for 110 // all the ongoing operations to complete. 111 // 112 // Attention: Can only be called once. 113 void ShutdownAndWaitForZeroOperations(); 114 115 private: 116 // Atomic representation of the state of this class. We use the upper 2 bits 117 // to keep track of flag like values and the remainder bits are used as a 118 // counter. The 2 flags are used to represent 3 different states: 119 // 120 // State | AcceptOperations Bit | ShuttingDown Bit 121 // -------------------------------------------------------------- 122 // kRejectingOperations | 0 | 0 123 // kAcceptingOperations | 1 | 0 124 // kShuttingDown | * | 1 125 // 126 // The counter keeps track of the rejected operations when we are in 127 // the kRejectingOperations state, the number of inflight operations 128 // otherwise. If the count reaches zero and we are in the shutting down state 129 // |shutdown_complete_| will be signaled. 130 static constexpr uint32_t kShuttingDownBitMask = uint32_t{1} << 31; 131 static constexpr uint32_t kAcceptingOperationsBitMask = uint32_t{1} << 30; 132 static constexpr uint32_t kFlagsBitMask = 133 (kShuttingDownBitMask | kAcceptingOperationsBitMask); 134 static constexpr uint32_t kCountBitMask = ~kFlagsBitMask; 135 enum class State { 136 kRejectingOperations, 137 kAcceptingOperations, 138 kShuttingDown, 139 }; 140 141 // Helper methods for the bit fiddling. Pass a |state_and_count_| value to 142 // extract state or count out of it. ExtractCount(uint32_t value)143 static uint32_t ExtractCount(uint32_t value) { return value & kCountBitMask; } 144 static State ExtractState(uint32_t value); 145 146 // Decrements the counter by |n| and signals |shutdown_complete_| if needed. 147 void DecrementBy(uint32_t n); 148 149 std::atomic<uint32_t> state_and_count_{0}; 150 WaitableEvent shutdown_complete_; 151 }; 152 153 } // namespace internal 154 } // namespace base 155 156 #endif // BASE_TASK_COMMON_OPERATIONS_CONTROLLER_H_ 157