docs
Loading...
Searching...
No Matches
Lock.h
Go to the documentation of this file.
1///
2/// Copyright (C) 2026 Ultralight, Inc. All rights reserved.
3/// A license is required for commercial use. https://ultralig.ht
4///
5#pragma once
7#include <atomic>
8#include <mutex> // for std::lock_guard<>
9
10namespace ultralight {
11
12///
13/// Lightweight mutex optimized for short locking periods.
14///
15/// Uncontended lock and unlock are a single inline atomic operation. Contended waiters spin
16/// briefly, then block in the OS until the lock is released (no CPU is consumed while waiting).
17///
18/// Can be used in place of std::mutex since it implements the STL's Lockable interface.
19///
20/// @note This lock is not recursive: locking it again from the thread that already holds it
21/// will deadlock. Debug builds assert when this is attempted.
22///
23class Lock {
24 public:
25 constexpr Lock() = default;
26
27 UL_ALWAYS_INLINE void lock() noexcept {
28 // CAS rather than exchange: an exchange would overwrite kLockedContended and lose the
29 // pending-waiter state, so a blocked waiter would never be woken.
30 uint32_t expected = kUnlocked;
31 if (!state_.compare_exchange_strong(expected, kLocked, std::memory_order_acquire,
32 std::memory_order_relaxed))
34#ifndef NDEBUG
35 owner_.store(current_thread_id(), std::memory_order_relaxed);
36#endif
37 }
38
39 UL_ALWAYS_INLINE bool try_lock() noexcept {
40 // First do a relaxed load to check if lock is free in order to prevent
41 // unnecessary cache misses if someone does while(!try_lock())
42 if (state_.load(std::memory_order_relaxed) != kUnlocked)
43 return false;
44 uint32_t expected = kUnlocked;
45 if (!state_.compare_exchange_strong(expected, kLocked, std::memory_order_acquire,
46 std::memory_order_relaxed))
47 return false;
48#ifndef NDEBUG
49 owner_.store(current_thread_id(), std::memory_order_relaxed);
50#endif
51 return true;
52 }
53
54 UL_ALWAYS_INLINE void unlock() noexcept {
55#ifndef NDEBUG
56 // Clear before releasing the lock word so the next holder never observes a stale owner.
57 owner_.store(0, std::memory_order_relaxed);
58#endif
59 if (state_.exchange(kUnlocked, std::memory_order_release) == kLockedContended)
61 }
62
63 protected:
64 Lock(const Lock&) = delete;
65 Lock& operator=(const Lock&) = delete;
66
67 // Lock word states. kLockedContended means a waiter may be blocked in the OS, so unlock()
68 // must issue a wake.
69 static constexpr uint32_t kUnlocked = 0;
70 static constexpr uint32_t kLocked = 1;
71 static constexpr uint32_t kLockedContended = 2;
72
73 UExport void contended_lock() noexcept;
74 UExport void contended_unlock() noexcept;
75 UExport static uint32_t current_thread_id() noexcept;
76
77 std::atomic<uint32_t> state_ = { kUnlocked };
78 // Diagnostic only: thread id of the current holder, checked to assert on recursive locking.
79 // Written only by debug builds, but present in all builds so the layout of Lock (and of
80 // public types that embed it) does not vary with the consumer's build configuration.
81 std::atomic<uint32_t> owner_ = { 0 };
82};
83
84using LockHolder = std::lock_guard<Lock>;
85
86} // namespace ultralight
#define UExport
Definition Exports.h:22
#define UL_ALWAYS_INLINE
Definition Defines.h:70
std::atomic< uint32_t > state_
Definition Lock.h:77
constexpr Lock()=default
void contended_unlock() noexcept
UL_ALWAYS_INLINE void unlock() noexcept
Definition Lock.h:54
UL_ALWAYS_INLINE void lock() noexcept
Definition Lock.h:27
void contended_lock() noexcept
UL_ALWAYS_INLINE bool try_lock() noexcept
Definition Lock.h:39
Lock(const Lock &)=delete
static constexpr uint32_t kUnlocked
Definition Lock.h:69
static uint32_t current_thread_id() noexcept
std::atomic< uint32_t > owner_
Definition Lock.h:81
Lock & operator=(const Lock &)=delete
static constexpr uint32_t kLocked
Definition Lock.h:70
static constexpr uint32_t kLockedContended
Definition Lock.h:71
Definition StringSTL.h:166
Root namespace for every public Ultralight type, function, and enumeration.
std::lock_guard< Lock > LockHolder
Definition Lock.h:84