summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/RWLock.h
blob: e03d008631f258ea1e25366795bd2fc772a75ce1 (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
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// An interface for read-write locks.

#ifndef mozilla_RWLock_h
#define mozilla_RWLock_h

#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/BlockingResourceBase.h"
#include "mozilla/PlatformRWLock.h"
#include "mozilla/ThreadSafety.h"

namespace mozilla {

// A RWLock is similar to a Mutex, but whereas a Mutex permits only a single
// reader thread or a single writer thread to access a piece of data, a
// RWLock distinguishes between readers and writers: you may have multiple
// reader threads concurrently accessing a piece of data or a single writer
// thread.  This difference should guide your usage of RWLock: if you are not
// reading the data from multiple threads simultaneously or you are writing
// to the data roughly as often as read from it, then Mutex will suit your
// purposes just fine.
//
// You should be using the AutoReadLock and AutoWriteLock classes, below,
// for RAII read locking and write locking, respectively.  If you really must
// take a read lock manually, call the ReadLock method; to relinquish that
// read lock, call the ReadUnlock method.  Similarly, WriteLock and WriteUnlock
// perform the same operations, but for write locks.
//
// It is unspecified what happens when a given thread attempts to acquire the
// same lock in multiple ways; some underlying implementations of RWLock do
// support acquiring a read lock multiple times on a given thread, but you
// should not rely on this behavior.
//
// It is unspecified whether RWLock gives priority to waiting readers or
// a waiting writer when unlocking.
class MOZ_CAPABILITY("rwlock") RWLock : public detail::RWLockImpl,
                                        public BlockingResourceBase {
 public:
  explicit RWLock(const char* aName);

#ifdef DEBUG
  bool LockedForWritingByCurrentThread();
  [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true);
  void ReadLock() MOZ_ACQUIRE_SHARED();
  void ReadUnlock() MOZ_RELEASE_SHARED();
  [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true);
  void WriteLock() MOZ_CAPABILITY_ACQUIRE();
  void WriteUnlock() MOZ_EXCLUSIVE_RELEASE();
#else
  [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true) {
    return detail::RWLockImpl::tryReadLock();
  }
  void ReadLock() MOZ_ACQUIRE_SHARED() { detail::RWLockImpl::readLock(); }
  void ReadUnlock() MOZ_RELEASE_SHARED() { detail::RWLockImpl::readUnlock(); }
  [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true) {
    return detail::RWLockImpl::tryWriteLock();
  }
  void WriteLock() MOZ_CAPABILITY_ACQUIRE() { detail::RWLockImpl::writeLock(); }
  void WriteUnlock() MOZ_EXCLUSIVE_RELEASE() {
    detail::RWLockImpl::writeUnlock();
  }
#endif

 private:
  RWLock() = delete;
  RWLock(const RWLock&) = delete;
  RWLock& operator=(const RWLock&) = delete;

#ifdef DEBUG
  // We record the owning thread for write locks only.
  PRThread* mOwningThread;
#endif
};

// We only use this once; not sure we can add thread safety attributions here
template <typename T>
class MOZ_RAII BaseAutoTryReadLock {
 public:
  explicit BaseAutoTryReadLock(T& aLock)
      : mLock(aLock.TryReadLock() ? &aLock : nullptr) {}

  ~BaseAutoTryReadLock() {
    if (mLock) {
      mLock->ReadUnlock();
    }
  }

  explicit operator bool() const { return mLock; }

 private:
  BaseAutoTryReadLock() = delete;
  BaseAutoTryReadLock(const BaseAutoTryReadLock&) = delete;
  BaseAutoTryReadLock& operator=(const BaseAutoTryReadLock&) = delete;

  T* mLock;
};

template <typename T>
class MOZ_SCOPED_CAPABILITY MOZ_RAII BaseAutoReadLock {
 public:
  explicit BaseAutoReadLock(T& aLock) MOZ_ACQUIRE_SHARED(aLock)
      : mLock(&aLock) {
    MOZ_ASSERT(mLock, "null lock");
    mLock->ReadLock();
  }

  // Not MOZ_RELEASE_SHARED(), which would make sense - apparently this trips
  // over a bug in clang's static analyzer and it says it expected an
  // exclusive unlock.
  ~BaseAutoReadLock() MOZ_RELEASE_GENERIC() { mLock->ReadUnlock(); }

 private:
  BaseAutoReadLock() = delete;
  BaseAutoReadLock(const BaseAutoReadLock&) = delete;
  BaseAutoReadLock& operator=(const BaseAutoReadLock&) = delete;

  T* mLock;
};

// XXX Mutex attributions?
template <typename T>
class MOZ_RAII BaseAutoTryWriteLock {
 public:
  explicit BaseAutoTryWriteLock(T& aLock)
      : mLock(aLock.TryWriteLock() ? &aLock : nullptr) {}

  ~BaseAutoTryWriteLock() {
    if (mLock) {
      mLock->WriteUnlock();
    }
  }

  explicit operator bool() const { return mLock; }

 private:
  BaseAutoTryWriteLock() = delete;
  BaseAutoTryWriteLock(const BaseAutoTryWriteLock&) = delete;
  BaseAutoTryWriteLock& operator=(const BaseAutoTryWriteLock&) = delete;

  T* mLock;
};

template <typename T>
class MOZ_SCOPED_CAPABILITY MOZ_RAII BaseAutoWriteLock final {
 public:
  explicit BaseAutoWriteLock(T& aLock) MOZ_CAPABILITY_ACQUIRE(aLock)
      : mLock(&aLock) {
    MOZ_ASSERT(mLock, "null lock");
    mLock->WriteLock();
  }

  ~BaseAutoWriteLock() MOZ_CAPABILITY_RELEASE() { mLock->WriteUnlock(); }

 private:
  BaseAutoWriteLock() = delete;
  BaseAutoWriteLock(const BaseAutoWriteLock&) = delete;
  BaseAutoWriteLock& operator=(const BaseAutoWriteLock&) = delete;

  T* mLock;
};

// Read try-lock and unlock a RWLock with RAII semantics.  Much preferred to
// bare calls to TryReadLock() and ReadUnlock().
typedef BaseAutoTryReadLock<RWLock> AutoTryReadLock;

// Read lock and unlock a RWLock with RAII semantics.  Much preferred to bare
// calls to ReadLock() and ReadUnlock().
typedef BaseAutoReadLock<RWLock> AutoReadLock;

// Write try-lock and unlock a RWLock with RAII semantics.  Much preferred to
// bare calls to TryWriteLock() and WriteUnlock().
typedef BaseAutoTryWriteLock<RWLock> AutoTryWriteLock;

// Write lock and unlock a RWLock with RAII semantics.  Much preferred to bare
// calls to WriteLock() and WriteUnlock().
typedef BaseAutoWriteLock<RWLock> AutoWriteLock;

class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS MOZ_CAPABILITY("rwlock")
    StaticRWLock {
 public:
  // In debug builds, check that mLock is initialized for us as we expect by
  // the compiler.  In non-debug builds, don't declare a constructor so that
  // the compiler can see that the constructor is trivial.
#ifdef DEBUG
  StaticRWLock() { MOZ_ASSERT(!mLock); }
#endif

  [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true) {
    return Lock()->TryReadLock();
  }
  void ReadLock() MOZ_ACQUIRE_SHARED() { Lock()->ReadLock(); }
  void ReadUnlock() MOZ_RELEASE_SHARED() { Lock()->ReadUnlock(); }
  [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true) {
    return Lock()->TryWriteLock();
  }
  void WriteLock() MOZ_CAPABILITY_ACQUIRE() { Lock()->WriteLock(); }
  void WriteUnlock() MOZ_EXCLUSIVE_RELEASE() { Lock()->WriteUnlock(); }

 private:
  [[nodiscard]] RWLock* Lock() MOZ_RETURN_CAPABILITY(*mLock) {
    if (mLock) {
      return mLock;
    }

    RWLock* lock = new RWLock("StaticRWLock");
    if (!mLock.compareExchange(nullptr, lock)) {
      delete lock;
    }

    return mLock;
  }

  Atomic<RWLock*> mLock;

  // Disallow copy constructor, but only in debug mode.  We only define
  // a default constructor in debug mode (see above); if we declared
  // this constructor always, the compiler wouldn't generate a trivial
  // default constructor for us in non-debug mode.
#ifdef DEBUG
  StaticRWLock(const StaticRWLock& aOther);
#endif

  // Disallow these operators.
  StaticRWLock& operator=(StaticRWLock* aRhs) = delete;
  static void* operator new(size_t) noexcept(true) = delete;
  static void operator delete(void*) = delete;
};

typedef BaseAutoTryReadLock<StaticRWLock> StaticAutoTryReadLock;
typedef BaseAutoReadLock<StaticRWLock> StaticAutoReadLock;
typedef BaseAutoTryWriteLock<StaticRWLock> StaticAutoTryWriteLock;
typedef BaseAutoWriteLock<StaticRWLock> StaticAutoWriteLock;

}  // namespace mozilla

#endif  // mozilla_RWLock_h