|
| 1 | +/* |
| 2 | + * Copyright 2019 Google LLC |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +#ifndef FIREBASE_APP_CLIENT_CPP_SRC_REFERENCE_COUNT_H_ |
| 18 | +#define FIREBASE_APP_CLIENT_CPP_SRC_REFERENCE_COUNT_H_ |
| 19 | + |
| 20 | +#include "app/src/mutex.h" |
| 21 | + |
| 22 | +namespace FIREBASE_NAMESPACE { |
| 23 | +namespace internal { |
| 24 | + |
| 25 | +// Reference counter. |
| 26 | +// To use this in a thread safe fashion, this class should be used in with |
| 27 | +// ReferenceCountLock or ReferenceCountedInitializer. |
| 28 | +class ReferenceCount { |
| 29 | + public: |
| 30 | + // Initialize with no references. |
| 31 | + ReferenceCount() : references_(0) {} |
| 32 | + |
| 33 | + // Increase the reference count, returning the previous number of references. |
| 34 | + int AddReference() { |
| 35 | + MutexLock lock(mutex_); |
| 36 | + int previous_references = references_; |
| 37 | + references_++; |
| 38 | + return previous_references; |
| 39 | + } |
| 40 | + |
| 41 | + // Decrease the reference count, returning the previous number of references. |
| 42 | + // If the object has no references the count is not decreased. |
| 43 | + int RemoveReference() { |
| 44 | + MutexLock lock(mutex_); |
| 45 | + int previous_references = references_; |
| 46 | + if (references_) references_--; |
| 47 | + return previous_references; |
| 48 | + } |
| 49 | + |
| 50 | + // Remove all references to this object. |
| 51 | + // This should only be used to clean up during initialization of an object |
| 52 | + // while holding the mutex. |
| 53 | + int RemoveAllReferences() { |
| 54 | + MutexLock lock(mutex_); |
| 55 | + int previous_references = references_; |
| 56 | + references_ = 0; |
| 57 | + return previous_references; |
| 58 | + } |
| 59 | + |
| 60 | + // Get the current number of references. |
| 61 | + int references() { |
| 62 | + MutexLock lock(mutex_); |
| 63 | + return references_; |
| 64 | + } |
| 65 | + |
| 66 | + // Get the mutex that guards this object. |
| 67 | + Mutex& mutex() { return mutex_; } |
| 68 | + |
| 69 | + private: |
| 70 | + // Number of references to this object. |
| 71 | + int references_; |
| 72 | + Mutex mutex_; // Allows users to guard references_. |
| 73 | +}; |
| 74 | + |
| 75 | +// Increases ReferenceCount (of type T) while the lock is active. |
| 76 | +template <typename T> |
| 77 | +class ReferenceCountLock { |
| 78 | + public: |
| 79 | + // Acquire the reference count lock and hold a reference for the lifetime of |
| 80 | + // this object. |
| 81 | + explicit ReferenceCountLock(T* reference_count) |
| 82 | + : reference_count_(reference_count), lock_(reference_count->mutex()) { |
| 83 | + reference_count->AddReference(); |
| 84 | + } |
| 85 | + |
| 86 | + ~ReferenceCountLock() { reference_count_->RemoveReference(); } |
| 87 | + |
| 88 | + // Increase the number of references returning the previous count excluding |
| 89 | + // the reference added by this lock. |
| 90 | + int AddReference() { |
| 91 | + return GetBaseReferences(reference_count_->AddReference()); |
| 92 | + } |
| 93 | + |
| 94 | + // Decrease the number of references returning the previous count excluding |
| 95 | + // the reference added by this lock. |
| 96 | + // If the object has no references the count is not decreased. |
| 97 | + int RemoveReference() { |
| 98 | + return GetBaseReferences(reference_count_->RemoveReference()); |
| 99 | + } |
| 100 | + |
| 101 | + // Remove all references to this object. |
| 102 | + // This should only be used to clean up during initialization of an object |
| 103 | + // while holding the mutex. |
| 104 | + int RemoveAllReferences() { |
| 105 | + return GetBaseReferences(reference_count_->RemoveAllReferences()); |
| 106 | + } |
| 107 | + |
| 108 | + // Get the current number of references excluding the reference added by this |
| 109 | + // lock. |
| 110 | + int references() const { |
| 111 | + return GetBaseReferences(reference_count_->references()); |
| 112 | + } |
| 113 | + |
| 114 | + private: |
| 115 | + // Remove the lock's reference from the specified reference count. |
| 116 | + static int GetBaseReferences(int count) { return count ? count - 1 : count; } |
| 117 | + |
| 118 | + private: |
| 119 | + T* reference_count_; |
| 120 | + MutexLock lock_; |
| 121 | +}; |
| 122 | + |
| 123 | +// Object which calls the registered Initialize method when the reference |
| 124 | +// count is increased to 1 and the registered Terminate method when the |
| 125 | +// reference count is decreased to 0. |
| 126 | +// |
| 127 | +// For example, in a module: |
| 128 | +// |
| 129 | +// static bool InitializeInternal(void* context) { |
| 130 | +// // Allocate resources for the module. |
| 131 | +// } |
| 132 | +// |
| 133 | +// static void TerminateInternal(void* context) { |
| 134 | +// // Free resources for the module. |
| 135 | +// } |
| 136 | +// |
| 137 | +// static ReferenceCountedInitializer g_initializer( |
| 138 | +// InitializeInternal, TerminateInternal, nullptr); // NOLINT |
| 139 | +// |
| 140 | +// bool Initialize() { |
| 141 | +// return g_initializer.AddReference() > 0; |
| 142 | +// } |
| 143 | +// |
| 144 | +// void DoSomethingWithModuleState() { |
| 145 | +// ReferenceCountLock<ReferenceCountedInitializer> lock(&g_initializer); |
| 146 | +// assert(g_initializer.references() > 0); // Initialized? |
| 147 | +// // Use global state. |
| 148 | +// // When lock is destroyed, if another thread decremented the reference |
| 149 | +// // count to 1 the module will be cleaned up. |
| 150 | +// } |
| 151 | +// |
| 152 | +// void Terminate() { |
| 153 | +// g_initializer.RemoveReference(); |
| 154 | +// } |
| 155 | +template <typename T> |
| 156 | +class ReferenceCountedInitializer { |
| 157 | + public: |
| 158 | + // Called when the reference count is increased from 0 to 1. |
| 159 | + typedef bool (*Initialize)(T* context); |
| 160 | + // Called when the reference count is decreased from 1 to 0. |
| 161 | + typedef void (*Terminate)(T* context); |
| 162 | + |
| 163 | + public: |
| 164 | + // Construct the object with no initialize or terminate methods. |
| 165 | + ReferenceCountedInitializer() |
| 166 | + : initialize_(nullptr), terminate_(nullptr), context_(nullptr) {} |
| 167 | + |
| 168 | + // Construct the object with just a terminate method. |
| 169 | + ReferenceCountedInitializer(Terminate terminate, T* context) |
| 170 | + : initialize_(nullptr), terminate_(terminate), context_(context) {} |
| 171 | + |
| 172 | + // Construct the object, both initialize and terminate are optional. |
| 173 | + ReferenceCountedInitializer(Initialize initialize, Terminate terminate, |
| 174 | + T* context) |
| 175 | + : initialize_(initialize), terminate_(terminate), context_(context) {} |
| 176 | + |
| 177 | + // Increase the reference count calling the specified initialization method |
| 178 | + // with context if increasing the reference count from 0 to 1, returning the |
| 179 | + // previous reference count. |
| 180 | + // A reference count of -1 is returned if initialization fails. |
| 181 | + int AddReference(Initialize initialize, T* context) { |
| 182 | + ReferenceCountLock<ReferenceCount> lock(&count_); |
| 183 | + int previous_references = lock.AddReference(); |
| 184 | + if (previous_references == 0 && initialize && !initialize(context)) { |
| 185 | + lock.RemoveReference(); |
| 186 | + return -1; |
| 187 | + } |
| 188 | + return previous_references; |
| 189 | + } |
| 190 | + |
| 191 | + // Increase the reference count calling the initialize method if increasing |
| 192 | + // the reference count from 0 to 1, returning the previous reference count. |
| 193 | + // A reference count of -1 is returned if initialization fails. |
| 194 | + int AddReference() { return AddReference(initialize_, context_); } |
| 195 | + |
| 196 | + // Decrease the reference count, returning the previous number of references. |
| 197 | + int RemoveReference() { |
| 198 | + ReferenceCountLock<ReferenceCount> lock(&count_); |
| 199 | + int previous_references = lock.RemoveReference(); |
| 200 | + if (previous_references == 1) ExecuteTerminate(); |
| 201 | + return previous_references; |
| 202 | + } |
| 203 | + |
| 204 | + // Clear the reference count and run the registered terminate method. |
| 205 | + // This can be used to reset the reference count during initialization. |
| 206 | + int RemoveAllReferences() { |
| 207 | + MutexLock lock(count_.mutex()); |
| 208 | + int previous_references = count_.RemoveAllReferences(); |
| 209 | + if (previous_references) ExecuteTerminate(); |
| 210 | + return previous_references; |
| 211 | + } |
| 212 | + |
| 213 | + // Clear the reference count without running the register terminate method. |
| 214 | + // This can be used to reset the reference count during initialization. |
| 215 | + int RemoveAllReferencesWithoutTerminate() { |
| 216 | + MutexLock lock(count_.mutex()); |
| 217 | + return count_.RemoveAllReferences(); |
| 218 | + } |
| 219 | + |
| 220 | + // Get the current number of references. |
| 221 | + int references() { return count_.references(); } |
| 222 | + |
| 223 | + // Get the mutex that guards this object. |
| 224 | + Mutex& mutex() { return count_.mutex(); } |
| 225 | + |
| 226 | + // Get the initialize method. |
| 227 | + Initialize initialize() const { return initialize_; } |
| 228 | + |
| 229 | + // Get the terminate method. |
| 230 | + Terminate terminate() const { return terminate_; } |
| 231 | + |
| 232 | + // Set the initialization context. |
| 233 | + void set_context(T* new_context) { |
| 234 | + MutexLock lock(mutex()); |
| 235 | + context_ = new_context; |
| 236 | + } |
| 237 | + |
| 238 | + // Get the context for the initializer. |
| 239 | + T* context() const { return context_; } |
| 240 | + |
| 241 | + private: |
| 242 | + // Execute the terminate method. |
| 243 | + void ExecuteTerminate() { if (terminate_) terminate_(context_); } |
| 244 | + |
| 245 | + private: |
| 246 | + ReferenceCount count_; |
| 247 | + Initialize initialize_; |
| 248 | + Terminate terminate_; |
| 249 | + T* context_; |
| 250 | +}; |
| 251 | + |
| 252 | +} // namespace internal |
| 253 | +} // namespace FIREBASE_NAMESPACE |
| 254 | + |
| 255 | +#endif // FIREBASE_APP_CLIENT_CPP_SRC_REFERENCE_COUNT_H_ |
0 commit comments