1 //===--- Implementation of a Linux RwLock class ---------------*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
8 #ifndef LLVM_LIBC_SRC_SUPPORT_THREADS_LINUX_RWLOCK_H
9 #define LLVM_LIBC_SRC_SUPPORT_THREADS_LINUX_RWLOCK_H
11 #include "hdr/errno_macros.h"
12 #include "hdr/types/pid_t.h"
13 #include "src/__support/CPP/atomic.h"
14 #include "src/__support/CPP/limits.h"
15 #include "src/__support/CPP/optional.h"
16 #include "src/__support/OSUtil/syscall.h"
17 #include "src/__support/common.h"
18 #include "src/__support/libc_assert.h"
19 #include "src/__support/macros/attributes.h"
20 #include "src/__support/macros/config.h"
21 #include "src/__support/macros/optimization.h"
22 #include "src/__support/threads/identifier.h"
23 #include "src/__support/threads/linux/futex_utils.h"
24 #include "src/__support/threads/linux/futex_word.h"
25 #include "src/__support/threads/linux/raw_mutex.h"
26 #include "src/__support/threads/sleep.h"
28 #ifndef LIBC_COPT_RWLOCK_DEFAULT_SPIN_COUNT
29 #define LIBC_COPT_RWLOCK_DEFAULT_SPIN_COUNT 100
32 #ifndef LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY
33 #define LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY 1
34 #warning "LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY is not defined, defaulting to 1"
37 #if LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY
38 #include "src/__support/time/linux/monotonicity.h"
41 namespace LIBC_NAMESPACE_DECL
{
42 // Forward declaration of the RwLock class.
44 // A namespace to rwlock specific utilities.
46 // The role of the thread in the RwLock.
47 enum class Role
{ Reader
= 0, Writer
= 1 };
49 // A waiting queue to keep track of the pending readers and writers.
50 class WaitingQueue final
: private RawMutex
{
51 /* FutexWordType raw_mutex; (from base class) */
53 // Pending reader count (protected by the mutex)
54 FutexWordType pending_readers
;
55 // Pending writer count (protected by the mutex)
56 FutexWordType pending_writers
;
57 // Reader serialization (increases on each reader-waking operation)
58 Futex reader_serialization
;
59 // Writer serialization (increases on each writer-waking operation)
60 Futex writer_serialization
;
63 // RAII guard to lock and unlock the waiting queue.
68 LIBC_INLINE
Guard(WaitingQueue
&queue
, bool is_pshared
)
69 : queue(queue
), is_pshared(is_pshared
) {
70 queue
.lock(cpp::nullopt
, is_pshared
);
74 LIBC_INLINE
~Guard() { queue
.unlock(is_pshared
); }
75 template <Role role
> LIBC_INLINE FutexWordType
&pending_count() {
76 if constexpr (role
== Role::Reader
)
77 return queue
.pending_readers
;
79 return queue
.pending_writers
;
81 template <Role role
> LIBC_INLINE FutexWordType
&serialization() {
82 if constexpr (role
== Role::Reader
)
83 return queue
.reader_serialization
.val
;
85 return queue
.writer_serialization
.val
;
91 LIBC_INLINE
constexpr WaitingQueue()
92 : RawMutex(), pending_readers(0), pending_writers(0),
93 reader_serialization(0), writer_serialization(0) {}
95 LIBC_INLINE Guard
acquire(bool is_pshared
) {
96 return Guard(*this, is_pshared
);
100 LIBC_INLINE
long wait(FutexWordType expected
,
101 cpp::optional
<Futex::Timeout
> timeout
,
103 if constexpr (role
== Role::Reader
)
104 return reader_serialization
.wait(expected
, timeout
, is_pshared
);
106 return writer_serialization
.wait(expected
, timeout
, is_pshared
);
109 template <Role role
> LIBC_INLINE
long notify(bool is_pshared
) {
110 if constexpr (role
== Role::Reader
)
111 return reader_serialization
.notify_all(is_pshared
);
113 return writer_serialization
.notify_one(is_pshared
);
117 // The RwState of the RwLock is stored in an integer word, consisting of the
118 // following components:
119 // -----------------------------------------------
120 // | Range | Description |
121 // ===============================================
122 // | 0 | Pending Reader Bit |
123 // -----------------------------------------------
124 // | 1 | Pending Writer Bit |
125 // -----------------------------------------------
126 // | [2, MSB) | Active Reader Count |
127 // -----------------------------------------------
128 // | MSB | Active Writer Bit |
129 // -----------------------------------------------
131 // Shift amounts to access the components of the state.
132 LIBC_INLINE_VAR
static constexpr int PENDING_READER_SHIFT
= 0;
133 LIBC_INLINE_VAR
static constexpr int PENDING_WRITER_SHIFT
= 1;
134 LIBC_INLINE_VAR
static constexpr int ACTIVE_READER_SHIFT
= 2;
135 LIBC_INLINE_VAR
static constexpr int ACTIVE_WRITER_SHIFT
=
136 cpp::numeric_limits
<int>::digits
;
138 // Bitmasks to access the components of the state.
139 LIBC_INLINE_VAR
static constexpr int PENDING_READER_BIT
=
140 1 << PENDING_READER_SHIFT
;
141 LIBC_INLINE_VAR
static constexpr int PENDING_WRITER_BIT
=
142 1 << PENDING_WRITER_SHIFT
;
143 LIBC_INLINE_VAR
static constexpr int ACTIVE_READER_COUNT_UNIT
=
144 1 << ACTIVE_READER_SHIFT
;
145 LIBC_INLINE_VAR
static constexpr int ACTIVE_WRITER_BIT
=
146 1 << ACTIVE_WRITER_SHIFT
;
147 LIBC_INLINE_VAR
static constexpr int PENDING_MASK
=
148 PENDING_READER_BIT
| PENDING_WRITER_BIT
;
151 // We use the signed integer as the state type. It is easier
152 // to reason about the state transitions using signness.
156 // Construction and conversion functions.
157 LIBC_INLINE
constexpr RwState(int state
= 0) : state(state
) {}
158 LIBC_INLINE
constexpr operator int() const { return state
; }
160 // Utilities to check the state of the RwLock.
161 LIBC_INLINE
constexpr bool has_active_writer() const { return state
< 0; }
162 LIBC_INLINE
constexpr bool has_active_reader() const {
163 return state
>= ACTIVE_READER_COUNT_UNIT
;
165 LIBC_INLINE
constexpr bool has_acitve_owner() const {
166 return has_active_reader() || has_active_writer();
168 LIBC_INLINE
constexpr bool has_last_reader() const {
169 return (state
>> ACTIVE_READER_SHIFT
) == 1;
171 LIBC_INLINE
constexpr bool has_pending_writer() const {
172 return state
& PENDING_WRITER_BIT
;
174 LIBC_INLINE
constexpr bool has_pending() const {
175 return state
& PENDING_MASK
;
178 LIBC_INLINE
constexpr RwState
set_writer_bit() const {
179 return RwState(state
| ACTIVE_WRITER_BIT
);
182 // The preference parameter changes the behavior of the lock acquisition
183 // if there are both readers and writers waiting for the lock. If writers
184 // are preferred, reader acquisition will be blocked until all pending
185 // writers are served.
186 template <Role role
> LIBC_INLINE
bool can_acquire(Role preference
) const {
187 if constexpr (role
== Role::Reader
) {
188 switch (preference
) {
190 return !has_active_writer();
192 return !has_active_writer() && !has_pending_writer();
194 __builtin_unreachable();
196 return !has_acitve_owner();
199 // This function check if it is possible to grow the reader count without
200 // overflowing the state.
201 LIBC_INLINE
cpp::optional
<RwState
> try_increase_reader_count() const {
202 LIBC_ASSERT(!has_active_writer() &&
203 "try_increase_reader_count shall only be called when there "
204 "is no active writer.");
206 if (LIBC_UNLIKELY(__builtin_sadd_overflow(state
, ACTIVE_READER_COUNT_UNIT
,
212 // Utilities to do atomic operations on the state.
213 LIBC_INLINE
static RwState
fetch_sub_reader_count(cpp::Atomic
<int> &target
,
214 cpp::MemoryOrder order
) {
215 return RwState(target
.fetch_sub(ACTIVE_READER_COUNT_UNIT
, order
));
218 LIBC_INLINE
static RwState
load(cpp::Atomic
<int> &target
,
219 cpp::MemoryOrder order
) {
220 return RwState(target
.load(order
));
224 LIBC_INLINE
static RwState
fetch_set_pending_bit(cpp::Atomic
<int> &target
,
225 cpp::MemoryOrder order
) {
226 if constexpr (role
== Role::Reader
)
227 return RwState(target
.fetch_or(PENDING_READER_BIT
, order
));
229 return RwState(target
.fetch_or(PENDING_WRITER_BIT
, order
));
232 LIBC_INLINE
static RwState
fetch_clear_pending_bit(cpp::Atomic
<int> &target
,
233 cpp::MemoryOrder order
) {
234 if constexpr (role
== Role::Reader
)
235 return RwState(target
.fetch_and(~PENDING_READER_BIT
, order
));
237 return RwState(target
.fetch_and(~PENDING_WRITER_BIT
, order
));
240 LIBC_INLINE
static RwState
fetch_clear_active_writer(cpp::Atomic
<int> &target
,
241 cpp::MemoryOrder order
) {
242 return RwState(target
.fetch_and(~ACTIVE_WRITER_BIT
, order
));
245 LIBC_INLINE
bool compare_exchange_weak_with(cpp::Atomic
<int> &target
,
247 cpp::MemoryOrder success_order
,
248 cpp::MemoryOrder failure_order
) {
249 return target
.compare_exchange_weak(state
, desired
, success_order
,
253 // Utilities to spin and reload the state.
256 LIBC_INLINE
static RwState
spin_reload_until(cpp::Atomic
<int> &target
,
257 F
&&func
, unsigned spin_count
) {
259 auto state
= RwState::load(target
, cpp::MemoryOrder::RELAXED
);
260 if (func(state
) || spin_count
== 0)
269 LIBC_INLINE
static RwState
spin_reload(cpp::Atomic
<int> &target
,
270 Role preference
, unsigned spin_count
) {
271 if constexpr (role
== Role::Reader
) {
272 // Return the reader state if either the lock is available or there is
273 // any ongoing contention.
274 return spin_reload_until(
277 return state
.can_acquire
<Role::Reader
>(preference
) ||
282 // Return the writer state if either the lock is available or there is
283 // any contention *between writers*. Since writers can be way less than
284 // readers, we allow them to spin more to improve the fairness.
285 return spin_reload_until(
288 return state
.can_acquire
<Role::Writer
>(preference
) ||
289 state
.has_pending_writer();
295 friend class RwLockTester
;
297 } // namespace rwlock
300 using RwState
= rwlock::RwState
;
301 using Role
= rwlock::Role
;
302 using WaitingQueue
= rwlock::WaitingQueue
;
305 // Return types for the lock functions.
306 // All the locking routines returning this type are marked as [[nodiscard]]
307 // because it is a common error to assume the lock success without checking
308 // the return value, which can lead to undefined behaviors or other subtle
309 // bugs that are hard to reason about.
310 enum class LockResult
: int {
312 TimedOut
= ETIMEDOUT
,
313 Overflow
= EAGAIN
, /* EAGAIN is specified in the standard for overflow. */
315 Deadlock
= EDEADLOCK
,
316 PermissionDenied
= EPERM
,
320 // Whether the RwLock is shared between processes.
321 LIBC_PREFERED_TYPE(bool)
322 unsigned is_pshared
: 1;
323 // Reader/Writer preference.
324 LIBC_PREFERED_TYPE(Role
)
325 unsigned preference
: 1;
326 // RwState to keep track of the RwLock.
327 cpp::Atomic
<int> state
;
328 // writer_tid is used to keep track of the thread id of the writer. Notice
329 // that TLS address is not a good idea here since it may remains the same
330 // across forked processes.
331 cpp::Atomic
<pid_t
> writer_tid
;
332 // Waiting queue to keep track of the readers and writers.
336 // Load the bitfield preference.
337 LIBC_INLINE Role
get_preference() const {
338 return static_cast<Role
>(preference
);
341 template <Role role
> LIBC_INLINE LockResult
try_lock(RwState
&old
) {
342 if constexpr (role
== Role::Reader
) {
343 while (LIBC_LIKELY(old
.can_acquire
<Role::Reader
>(get_preference()))) {
344 cpp::optional
<RwState
> next
= old
.try_increase_reader_count();
346 return LockResult::Overflow
;
347 if (LIBC_LIKELY(old
.compare_exchange_weak_with(
348 state
, *next
, cpp::MemoryOrder::ACQUIRE
,
349 cpp::MemoryOrder::RELAXED
)))
350 return LockResult::Success
;
351 // Notice that old is updated by the compare_exchange_weak_with
354 return LockResult::Busy
;
356 // This while loop should terminate quickly
357 while (LIBC_LIKELY(old
.can_acquire
<Role::Writer
>(get_preference()))) {
358 if (LIBC_LIKELY(old
.compare_exchange_weak_with(
359 state
, old
.set_writer_bit(), cpp::MemoryOrder::ACQUIRE
,
360 cpp::MemoryOrder::RELAXED
))) {
361 writer_tid
.store(internal::gettid(), cpp::MemoryOrder::RELAXED
);
362 return LockResult::Success
;
364 // Notice that old is updated by the compare_exchange_weak_with
367 return LockResult::Busy
;
372 LIBC_INLINE
constexpr RwLock(Role preference
= Role::Reader
,
373 bool is_pshared
= false)
374 : is_pshared(is_pshared
),
375 preference(static_cast<unsigned>(preference
) & 1u), state(0),
376 writer_tid(0), queue() {}
379 LIBC_INLINE LockResult
try_read_lock() {
380 RwState old
= RwState::load(state
, cpp::MemoryOrder::RELAXED
);
381 return try_lock
<Role::Reader
>(old
);
384 LIBC_INLINE LockResult
try_write_lock() {
385 RwState old
= RwState::load(state
, cpp::MemoryOrder::RELAXED
);
386 return try_lock
<Role::Writer
>(old
);
391 LIBC_INLINE LockResult
392 lock_slow(cpp::optional
<Futex::Timeout
> timeout
= cpp::nullopt
,
393 unsigned spin_count
= LIBC_COPT_RWLOCK_DEFAULT_SPIN_COUNT
) {
394 // Phase 1: deadlock detection.
395 // A deadlock happens if this is a RAW/WAW lock in the same thread.
396 if (writer_tid
.load(cpp::MemoryOrder::RELAXED
) == internal::gettid())
397 return LockResult::Deadlock
;
399 #if LIBC_COPT_TIMEOUT_ENSURE_MONOTONICITY
400 // Phase 2: convert the timeout if necessary.
402 ensure_monotonicity(*timeout
);
405 // Phase 3: spin to get the initial state. We ignore the timing due to
406 // spin since it should end quickly.
408 RwState::spin_reload
<role
>(state
, get_preference(), spin_count
);
410 // Enter the main acquisition loop.
412 // Phase 4: if the lock can be acquired, try to acquire it.
413 LockResult result
= try_lock
<role
>(old
);
414 if (result
!= LockResult::Busy
)
417 // Phase 5: register ourselves as a reader.
420 // The queue need to be protected by a mutex since the operations in
421 // this block must be executed as a whole transaction. It is possible
422 // that this lock will make the timeout imprecise, but this is the
423 // best we can do. The transaction is small and everyone should make
424 // progress rather quickly.
425 WaitingQueue::Guard guard
= queue
.acquire(is_pshared
);
426 guard
.template pending_count
<role
>()++;
428 // Use atomic operation to guarantee the total order of the operations
429 // on the state. The pending flag update should be visible to any
430 // succeeding unlock events. Or, if a unlock does happen before we
431 // sleep on the futex, we can avoid such waiting.
432 old
= RwState::fetch_set_pending_bit
<role
>(state
,
433 cpp::MemoryOrder::RELAXED
);
434 // no need to use atomic since it is already protected by the mutex.
435 serial_number
= guard
.serialization
<role
>();
438 // Phase 6: do futex wait until the lock is available or timeout is
440 bool timeout_flag
= false;
441 if (!old
.can_acquire
<role
>(get_preference()))
442 timeout_flag
= (queue
.wait
<role
>(serial_number
, timeout
, is_pshared
) ==
445 // Phase 7: unregister ourselves as a pending reader/writer.
447 // Similarly, the unregister operation should also be an atomic
449 WaitingQueue::Guard guard
= queue
.acquire(is_pshared
);
450 guard
.pending_count
<role
>()--;
451 // Clear the flag if we are the last reader. The flag must be
452 // cleared otherwise operations like trylock may fail even though
453 // there is no competitors.
454 if (guard
.pending_count
<role
>() == 0)
455 RwState::fetch_clear_pending_bit
<role
>(state
,
456 cpp::MemoryOrder::RELAXED
);
459 // Phase 8: exit the loop is timeout is reached.
461 return LockResult::TimedOut
;
463 // Phase 9: reload the state and retry the acquisition.
464 old
= RwState::spin_reload
<role
>(state
, get_preference(), spin_count
);
470 LIBC_INLINE LockResult
471 read_lock(cpp::optional
<Futex::Timeout
> timeout
= cpp::nullopt
,
472 unsigned spin_count
= LIBC_COPT_RWLOCK_DEFAULT_SPIN_COUNT
) {
473 LockResult result
= try_read_lock();
474 if (LIBC_LIKELY(result
!= LockResult::Busy
))
476 return lock_slow
<Role::Reader
>(timeout
, spin_count
);
479 LIBC_INLINE LockResult
480 write_lock(cpp::optional
<Futex::Timeout
> timeout
= cpp::nullopt
,
481 unsigned spin_count
= LIBC_COPT_RWLOCK_DEFAULT_SPIN_COUNT
) {
482 LockResult result
= try_write_lock();
483 if (LIBC_LIKELY(result
!= LockResult::Busy
))
485 return lock_slow
<Role::Writer
>(timeout
, spin_count
);
489 // Compiler (clang 19.0) somehow decides that this function may be inlined,
490 // which leads to a larger unlock function that is infeasible to be inlined.
491 // Since notifcation routine is colder we mark it as noinline explicitly.
493 LIBC_INLINE
void notify_pending_threads() {
494 enum class WakeTarget
{ Readers
, Writers
, None
};
498 WaitingQueue::Guard guard
= queue
.acquire(is_pshared
);
499 if (guard
.pending_count
<Role::Writer
>() != 0) {
500 guard
.serialization
<Role::Writer
>()++;
501 status
= WakeTarget::Writers
;
502 } else if (guard
.pending_count
<Role::Reader
>() != 0) {
503 guard
.serialization
<Role::Reader
>()++;
504 status
= WakeTarget::Readers
;
506 status
= WakeTarget::None
;
509 if (status
== WakeTarget::Readers
)
510 queue
.notify
<Role::Reader
>(is_pshared
);
511 else if (status
== WakeTarget::Writers
)
512 queue
.notify
<Role::Writer
>(is_pshared
);
517 LIBC_INLINE LockResult
unlock() {
518 RwState old
= RwState::load(state
, cpp::MemoryOrder::RELAXED
);
519 if (old
.has_active_writer()) {
520 // The lock is held by a writer.
521 // Check if we are the owner of the lock.
522 if (writer_tid
.load(cpp::MemoryOrder::RELAXED
) != internal::gettid())
523 return LockResult::PermissionDenied
;
525 writer_tid
.store(0, cpp::MemoryOrder::RELAXED
);
526 // clear the writer bit.
528 RwState::fetch_clear_active_writer(state
, cpp::MemoryOrder::RELEASE
);
529 // If there is no pending readers or writers, we are done.
530 if (!old
.has_pending())
531 return LockResult::Success
;
532 } else if (old
.has_active_reader()) {
533 // The lock is held by readers.
534 // Decrease the reader count.
535 old
= RwState::fetch_sub_reader_count(state
, cpp::MemoryOrder::RELEASE
);
536 // If there is no pending readers or writers, we are done.
537 if (!old
.has_last_reader() || !old
.has_pending())
538 return LockResult::Success
;
540 return LockResult::PermissionDenied
;
542 notify_pending_threads();
543 return LockResult::Success
;
546 // We do not allocate any special resources for the RwLock, so this function
547 // will only check if the lock is currently held by any thread.
549 LIBC_INLINE LockResult
check_for_destroy() {
550 RwState old
= RwState::load(state
, cpp::MemoryOrder::RELAXED
);
551 if (old
.has_acitve_owner())
552 return LockResult::Busy
;
553 return LockResult::Success
;
556 } // namespace LIBC_NAMESPACE_DECL
558 #endif // LLVM_LIBC_SRC_SUPPORT_THREADS_LINUX_RWLOCK_H