Austin Schuh | 36244a1 | 2019-09-21 17:52:38 -0700 | [diff] [blame^] | 1 | // Do not include. This is an implementation detail of base/mutex.h. |
| 2 | // |
| 3 | // Declares three classes: |
| 4 | // |
| 5 | // base::internal::MutexImpl - implementation helper for Mutex |
| 6 | // base::internal::CondVarImpl - implementation helper for CondVar |
| 7 | // base::internal::SynchronizationStorage<T> - implementation helper for |
| 8 | // Mutex, CondVar |
| 9 | |
| 10 | #include <type_traits> |
| 11 | |
| 12 | #if defined(_WIN32) |
| 13 | #include <condition_variable> |
| 14 | #include <mutex> |
| 15 | #else |
| 16 | #include <pthread.h> |
| 17 | #endif |
| 18 | |
| 19 | #include "absl/base/call_once.h" |
| 20 | #include "absl/time/time.h" |
| 21 | |
| 22 | // Declare that Mutex::ReaderLock is actually Lock(). Intended primarily |
| 23 | // for tests, and even then as a last resort. |
| 24 | #ifdef ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE |
| 25 | #error ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE cannot be directly set |
| 26 | #else |
| 27 | #define ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE 1 |
| 28 | #endif |
| 29 | |
| 30 | // Declare that Mutex::EnableInvariantDebugging is not implemented. |
| 31 | // Intended primarily for tests, and even then as a last resort. |
| 32 | #ifdef ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED |
| 33 | #error ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED cannot be directly set |
| 34 | #else |
| 35 | #define ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED 1 |
| 36 | #endif |
| 37 | |
| 38 | namespace absl { |
| 39 | class Condition; |
| 40 | |
| 41 | namespace synchronization_internal { |
| 42 | |
| 43 | class MutexImpl; |
| 44 | |
| 45 | // Do not use this implementation detail of CondVar. Provides most of the |
| 46 | // implementation, but should not be placed directly in static storage |
| 47 | // because it will not linker initialize properly. See |
| 48 | // SynchronizationStorage<T> below for what we mean by linker |
| 49 | // initialization. |
| 50 | class CondVarImpl { |
| 51 | public: |
| 52 | CondVarImpl(); |
| 53 | CondVarImpl(const CondVarImpl&) = delete; |
| 54 | CondVarImpl& operator=(const CondVarImpl&) = delete; |
| 55 | ~CondVarImpl(); |
| 56 | |
| 57 | void Signal(); |
| 58 | void SignalAll(); |
| 59 | void Wait(MutexImpl* mutex); |
| 60 | bool WaitWithDeadline(MutexImpl* mutex, absl::Time deadline); |
| 61 | |
| 62 | private: |
| 63 | #if defined(_WIN32) |
| 64 | std::condition_variable_any std_cv_; |
| 65 | #else |
| 66 | pthread_cond_t pthread_cv_; |
| 67 | #endif |
| 68 | }; |
| 69 | |
| 70 | // Do not use this implementation detail of Mutex. Provides most of the |
| 71 | // implementation, but should not be placed directly in static storage |
| 72 | // because it will not linker initialize properly. See |
| 73 | // SynchronizationStorage<T> below for what we mean by linker |
| 74 | // initialization. |
| 75 | class MutexImpl { |
| 76 | public: |
| 77 | MutexImpl(); |
| 78 | MutexImpl(const MutexImpl&) = delete; |
| 79 | MutexImpl& operator=(const MutexImpl&) = delete; |
| 80 | ~MutexImpl(); |
| 81 | |
| 82 | void Lock(); |
| 83 | bool TryLock(); |
| 84 | void Unlock(); |
| 85 | void Await(const Condition& cond); |
| 86 | bool AwaitWithDeadline(const Condition& cond, absl::Time deadline); |
| 87 | |
| 88 | private: |
| 89 | friend class CondVarImpl; |
| 90 | |
| 91 | #if defined(_WIN32) |
| 92 | std::mutex std_mutex_; |
| 93 | #else |
| 94 | pthread_mutex_t pthread_mutex_; |
| 95 | #endif |
| 96 | |
| 97 | // True if the underlying mutex is locked. If the destructor is entered |
| 98 | // while locked_, the underlying mutex is unlocked. Mutex supports |
| 99 | // destruction while locked, but the same is undefined behavior for both |
| 100 | // pthread_mutex_t and std::mutex. |
| 101 | bool locked_ = false; |
| 102 | |
| 103 | // Signaled before releasing the lock, in support of Await. |
| 104 | CondVarImpl released_; |
| 105 | }; |
| 106 | |
| 107 | // Do not use this implementation detail of CondVar and Mutex. A storage |
| 108 | // space for T that supports a LinkerInitialized constructor. T must |
| 109 | // have a default constructor, which is called by the first call to |
| 110 | // get(). T's destructor is never called if the LinkerInitialized |
| 111 | // constructor is called. |
| 112 | // |
| 113 | // Objects constructed with the default constructor are constructed and |
| 114 | // destructed like any other object, and should never be allocated in |
| 115 | // static storage. |
| 116 | // |
| 117 | // Objects constructed with the LinkerInitialized constructor should |
| 118 | // always be in static storage. For such objects, calls to get() are always |
| 119 | // valid, except from signal handlers. |
| 120 | // |
| 121 | // Note that this implementation relies on undefined language behavior that |
| 122 | // are known to hold for the set of supported compilers. An analysis |
| 123 | // follows. |
| 124 | // |
| 125 | // From the C++11 standard: |
| 126 | // |
| 127 | // [basic.life] says an object has non-trivial initialization if it is of |
| 128 | // class type and it is initialized by a constructor other than a trivial |
| 129 | // default constructor. (the LinkerInitialized constructor is |
| 130 | // non-trivial) |
| 131 | // |
| 132 | // [basic.life] says the lifetime of an object with a non-trivial |
| 133 | // constructor begins when the call to the constructor is complete. |
| 134 | // |
| 135 | // [basic.life] says the lifetime of an object with non-trivial destructor |
| 136 | // ends when the call to the destructor begins. |
| 137 | // |
| 138 | // [basic.life] p5 specifies undefined behavior when accessing non-static |
| 139 | // members of an instance outside its |
| 140 | // lifetime. (SynchronizationStorage::get() access non-static members) |
| 141 | // |
| 142 | // So, LinkerInitialized object of SynchronizationStorage uses a |
| 143 | // non-trivial constructor, which is called at some point during dynamic |
| 144 | // initialization, and is therefore subject to order of dynamic |
| 145 | // initialization bugs, where get() is called before the object's |
| 146 | // constructor is, resulting in undefined behavior. |
| 147 | // |
| 148 | // Similarly, a LinkerInitialized SynchronizationStorage object has a |
| 149 | // non-trivial destructor, and so its lifetime ends at some point during |
| 150 | // destruction of objects with static storage duration [basic.start.term] |
| 151 | // p4. There is a window where other exit code could call get() after this |
| 152 | // occurs, resulting in undefined behavior. |
| 153 | // |
| 154 | // Combined, these statements imply that LinkerInitialized instances |
| 155 | // of SynchronizationStorage<T> rely on undefined behavior. |
| 156 | // |
| 157 | // However, in practice, the implementation works on all supported |
| 158 | // compilers. Specifically, we rely on: |
| 159 | // |
| 160 | // a) zero-initialization being sufficient to initialize |
| 161 | // LinkerInitialized instances for the purposes of calling |
| 162 | // get(), regardless of when the constructor is called. This is |
| 163 | // because the is_dynamic_ boolean is correctly zero-initialized to |
| 164 | // false. |
| 165 | // |
| 166 | // b) the LinkerInitialized constructor is a NOP, and immaterial to |
| 167 | // even to concurrent calls to get(). |
| 168 | // |
| 169 | // c) the destructor being a NOP for LinkerInitialized objects |
| 170 | // (guaranteed by a check for !is_dynamic_), and so any concurrent and |
| 171 | // subsequent calls to get() functioning as if the destructor were not |
| 172 | // called, by virtue of the instances' storage remaining valid after the |
| 173 | // destructor runs. |
| 174 | // |
| 175 | // d) That a-c apply transitively when SynchronizationStorage<T> is the |
| 176 | // only member of a class allocated in static storage. |
| 177 | // |
| 178 | // Nothing in the language standard guarantees that a-d hold. In practice, |
| 179 | // these hold in all supported compilers. |
| 180 | // |
| 181 | // Future direction: |
| 182 | // |
| 183 | // Ideally, we would simply use std::mutex or a similar class, which when |
| 184 | // allocated statically would support use immediately after static |
| 185 | // initialization up until static storage is reclaimed (i.e. the properties |
| 186 | // we require of all "linker initialized" instances). |
| 187 | // |
| 188 | // Regarding construction in static storage, std::mutex is required to |
| 189 | // provide a constexpr default constructor [thread.mutex.class], which |
| 190 | // ensures the instance's lifetime begins with static initialization |
| 191 | // [basic.start.init], and so is immune to any problems caused by the order |
| 192 | // of dynamic initialization. However, as of this writing Microsoft's |
| 193 | // Visual Studio does not provide a constexpr constructor for std::mutex. |
| 194 | // See |
| 195 | // https://blogs.msdn.microsoft.com/vcblog/2015/06/02/constexpr-complete-for-vs-2015-rtm-c11-compiler-c17-stl/ |
| 196 | // |
| 197 | // Regarding destruction of instances in static storage, [basic.life] does |
| 198 | // say an object ends when storage in which the occupies is released, in |
| 199 | // the case of non-trivial destructor. However, std::mutex is not specified |
| 200 | // to have a trivial destructor. |
| 201 | // |
| 202 | // So, we would need a class with a constexpr default constructor and a |
| 203 | // trivial destructor. Today, we can achieve neither desired property using |
| 204 | // std::mutex directly. |
| 205 | template <typename T> |
| 206 | class SynchronizationStorage { |
| 207 | public: |
| 208 | // Instances allocated on the heap or on the stack should use the default |
| 209 | // constructor. |
| 210 | SynchronizationStorage() |
| 211 | : is_dynamic_(true), once_() {} |
| 212 | |
| 213 | // Instances allocated in static storage (not on the heap, not on the |
| 214 | // stack) should use this constructor. |
| 215 | explicit SynchronizationStorage(base_internal::LinkerInitialized) {} |
| 216 | |
| 217 | constexpr explicit SynchronizationStorage(absl::ConstInitType) |
| 218 | : is_dynamic_(false), once_(), space_{{0}} {} |
| 219 | |
| 220 | SynchronizationStorage(SynchronizationStorage&) = delete; |
| 221 | SynchronizationStorage& operator=(SynchronizationStorage&) = delete; |
| 222 | |
| 223 | ~SynchronizationStorage() { |
| 224 | if (is_dynamic_) { |
| 225 | get()->~T(); |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | // Retrieve the object in storage. This is fast and thread safe, but does |
| 230 | // incur the cost of absl::call_once(). |
| 231 | // |
| 232 | // For instances in static storage constructed with the |
| 233 | // LinkerInitialized constructor, may be called at any time without |
| 234 | // regard for order of dynamic initialization or destruction of objects |
| 235 | // in static storage. See the class comment for caveats. |
| 236 | T* get() { |
| 237 | absl::call_once(once_, SynchronizationStorage::Construct, this); |
| 238 | return reinterpret_cast<T*>(&space_); |
| 239 | } |
| 240 | |
| 241 | private: |
| 242 | static void Construct(SynchronizationStorage<T>* self) { |
| 243 | new (&self->space_) T(); |
| 244 | } |
| 245 | |
| 246 | // When true, T's destructor is run when this is destructed. |
| 247 | // |
| 248 | // The LinkerInitialized constructor assumes this value will be set |
| 249 | // false by static initialization. |
| 250 | bool is_dynamic_; |
| 251 | |
| 252 | absl::once_flag once_; |
| 253 | |
| 254 | // An aligned space for T. |
| 255 | typename std::aligned_storage<sizeof(T), alignof(T)>::type space_; |
| 256 | }; |
| 257 | |
| 258 | } // namespace synchronization_internal |
| 259 | } // namespace absl |