summaryrefslogtreecommitdiff
path: root/absl/synchronization/internal/mutex_nonprod.inc
blob: b8d5af79ad3805875e33c7dddcb7af5bb311fd93 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Do not include.  This is an implementation detail of base/mutex.h.
//
// Declares three classes:
//
// base::internal::MutexImpl - implementation helper for Mutex
// base::internal::CondVarImpl - implementation helper for CondVar
// base::internal::SynchronizationStorage<T> - implementation helper for
//                                             Mutex, CondVar

#include <type_traits>

#if defined(_WIN32)
#include <condition_variable>
#include <mutex>
#else
#include <pthread.h>
#endif

#include "absl/base/call_once.h"
#include "absl/time/time.h"

// Declare that Mutex::ReaderLock is actually Lock().  Intended primarily
// for tests, and even then as a last resort.
#ifdef ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE
#error ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE cannot be directly set
#else
#define ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE 1
#endif

// Declare that Mutex::EnableInvariantDebugging is not implemented.
// Intended primarily for tests, and even then as a last resort.
#ifdef ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED
#error ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED cannot be directly set
#else
#define ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED 1
#endif

namespace absl {
class Condition;

namespace synchronization_internal {

class MutexImpl;

// Do not use this implementation detail of CondVar. Provides most of the
// implementation, but should not be placed directly in static storage
// because it will not linker initialize properly. See
// SynchronizationStorage<T> below for what we mean by linker
// initialization.
class CondVarImpl {
 public:
  CondVarImpl();
  CondVarImpl(const CondVarImpl&) = delete;
  CondVarImpl& operator=(const CondVarImpl&) = delete;
  ~CondVarImpl();

  void Signal();
  void SignalAll();
  void Wait(MutexImpl* mutex);
  bool WaitWithDeadline(MutexImpl* mutex, absl::Time deadline);

 private:
#if defined(_WIN32)
  std::condition_variable_any std_cv_;
#else
  pthread_cond_t pthread_cv_;
#endif
};

// Do not use this implementation detail of Mutex. Provides most of the
// implementation, but should not be placed directly in static storage
// because it will not linker initialize properly. See
// SynchronizationStorage<T> below for what we mean by linker
// initialization.
class MutexImpl {
 public:
  MutexImpl();
  MutexImpl(const MutexImpl&) = delete;
  MutexImpl& operator=(const MutexImpl&) = delete;
  ~MutexImpl();

  void Lock();
  bool TryLock();
  void Unlock();
  void Await(const Condition& cond);
  bool AwaitWithDeadline(const Condition& cond, absl::Time deadline);

 private:
  friend class CondVarImpl;

#if defined(_WIN32)
  std::mutex std_mutex_;
#else
  pthread_mutex_t pthread_mutex_;
#endif

  // True if the underlying mutex is locked.  If the destructor is entered
  // while locked_, the underlying mutex is unlocked.  Mutex supports
  // destruction while locked, but the same is undefined behavior for both
  // pthread_mutex_t and std::mutex.
  bool locked_ = false;

  // Signaled before releasing the lock, in support of Await.
  CondVarImpl released_;
};

// Do not use this implementation detail of CondVar and Mutex.  A storage
// space for T that supports a LinkerInitialized constructor. T must
// have a default constructor, which is called by the first call to
// get(). T's destructor is never called if the LinkerInitialized
// constructor is called.
//
// Objects constructed with the default constructor are constructed and
// destructed like any other object, and should never be allocated in
// static storage.
//
// Objects constructed with the LinkerInitialized constructor should
// always be in static storage. For such objects, calls to get() are always
// valid, except from signal handlers.
//
// Note that this implementation relies on undefined language behavior that
// are known to hold for the set of supported compilers. An analysis
// follows.
//
// From the C++11 standard:
//
// [basic.life] says an object has non-trivial initialization if it is of
// class type and it is initialized by a constructor other than a trivial
// default constructor.  (the LinkerInitialized constructor is
// non-trivial)
//
// [basic.life] says the lifetime of an object with a non-trivial
// constructor begins when the call to the constructor is complete.
//
// [basic.life] says the lifetime of an object with non-trivial destructor
// ends when the call to the destructor begins.
//
// [basic.life] p5 specifies undefined behavior when accessing non-static
// members of an instance outside its
// lifetime. (SynchronizationStorage::get() access non-static members)
//
// So, LinkerInitialized object of SynchronizationStorage uses a
// non-trivial constructor, which is called at some point during dynamic
// initialization, and is therefore subject to order of dynamic
// initialization bugs, where get() is called before the object's
// constructor is, resulting in undefined behavior.
//
// Similarly, a LinkerInitialized SynchronizationStorage object has a
// non-trivial destructor, and so its lifetime ends at some point during
// destruction of objects with static storage duration [basic.start.term]
// p4. There is a window where other exit code could call get() after this
// occurs, resulting in undefined behavior.
//
// Combined, these statements imply that LinkerInitialized instances
// of SynchronizationStorage<T> rely on undefined behavior.
//
// However, in practice, the implementation works on all supported
// compilers. Specifically, we rely on:
//
// a) zero-initialization being sufficient to initialize
// LinkerInitialized instances for the purposes of calling
// get(), regardless of when the constructor is called. This is
// because the is_dynamic_ boolean is correctly zero-initialized to
// false.
//
// b) the LinkerInitialized constructor is a NOP, and immaterial to
// even to concurrent calls to get().
//
// c) the destructor being a NOP for LinkerInitialized objects
// (guaranteed by a check for !is_dynamic_), and so any concurrent and
// subsequent calls to get() functioning as if the destructor were not
// called, by virtue of the instances' storage remaining valid after the
// destructor runs.
//
// d) That a-c apply transitively when SynchronizationStorage<T> is the
// only member of a class allocated in static storage.
//
// Nothing in the language standard guarantees that a-d hold.  In practice,
// these hold in all supported compilers.
//
// Future direction:
//
// Ideally, we would simply use std::mutex or a similar class, which when
// allocated statically would support use immediately after static
// initialization up until static storage is reclaimed (i.e. the properties
// we require of all "linker initialized" instances).
//
// Regarding construction in static storage, std::mutex is required to
// provide a constexpr default constructor [thread.mutex.class], which
// ensures the instance's lifetime begins with static initialization
// [basic.start.init], and so is immune to any problems caused by the order
// of dynamic initialization. However, as of this writing Microsoft's
// Visual Studio does not provide a constexpr constructor for std::mutex.
// See
// https://blogs.msdn.microsoft.com/vcblog/2015/06/02/constexpr-complete-for-vs-2015-rtm-c11-compiler-c17-stl/
//
// Regarding destruction of instances in static storage, [basic.life] does
// say an object ends when storage in which the occupies is released, in
// the case of non-trivial destructor. However, std::mutex is not specified
// to have a trivial destructor.
//
// So, we would need a class with a constexpr default constructor and a
// trivial destructor. Today, we can achieve neither desired property using
// std::mutex directly.
template <typename T>
class SynchronizationStorage {
 public:
  // Instances allocated on the heap or on the stack should use the default
  // constructor.
  SynchronizationStorage()
      : is_dynamic_(true), once_() {}

  // Instances allocated in static storage (not on the heap, not on the
  // stack) should use this constructor.
  explicit SynchronizationStorage(base_internal::LinkerInitialized) {}

  constexpr explicit SynchronizationStorage(absl::ConstInitType)
      : is_dynamic_(false), once_(), space_{{0}} {}

  SynchronizationStorage(SynchronizationStorage&) = delete;
  SynchronizationStorage& operator=(SynchronizationStorage&) = delete;

  ~SynchronizationStorage() {
    if (is_dynamic_) {
      get()->~T();
    }
  }

  // Retrieve the object in storage. This is fast and thread safe, but does
  // incur the cost of absl::call_once().
  //
  // For instances in static storage constructed with the
  // LinkerInitialized constructor, may be called at any time without
  // regard for order of dynamic initialization or destruction of objects
  // in static storage. See the class comment for caveats.
  T* get() {
    absl::call_once(once_, SynchronizationStorage::Construct, this);
    return reinterpret_cast<T*>(&space_);
  }

 private:
  static void Construct(SynchronizationStorage<T>* self) {
    new (&self->space_) T();
  }

  // When true, T's destructor is run when this is destructed.
  //
  // The LinkerInitialized constructor assumes this value will be set
  // false by static initialization.
  bool is_dynamic_;

  absl::once_flag once_;

  // An aligned space for T.
  typename std::aligned_storage<sizeof(T), alignof(T)>::type space_;
};

}  // namespace synchronization_internal
}  // namespace absl