blob: b8d5af79ad3805875e33c7dddcb7af5bb311fd93 [file] [log] [blame]
Austin Schuh36244a12019-09-21 17:52:38 -07001// 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
38namespace absl {
39class Condition;
40
41namespace synchronization_internal {
42
43class 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.
50class 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.
75class 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.
205template <typename T>
206class 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