-
Notifications
You must be signed in to change notification settings - Fork 851
Avoid Stripe Mutex lock contention for RWW #12794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -372,4 +372,195 @@ template <typename T = std::shared_mutex, size_t SLOT_SIZE = 256, int SLOWDOWN_G | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using shared_mutex = shared_mutex_impl<>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ts::bravo::recursive_shared_mutex_impl | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| A recursive version of shared_mutex_impl that allows the same thread | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| to acquire exclusive and shared locks multiple times. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Uses DenseThreadId for efficient per-thread state tracking without map overhead. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Optimized to minimize expensive std::this_thread::get_id() calls by using | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DenseThreadId for ownership tracking. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Mixed lock semantics: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Upgrade prevention: A thread holding a shared lock cannot acquire an exclusive lock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (would cause deadlock). try_lock() returns false, lock() asserts. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Downgrade allowed: A thread holding an exclusive lock can acquire a shared lock. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| template <typename T = shared_mutex_impl<>, size_t SLOT_SIZE = 256> class recursive_shared_mutex_impl | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use a sentinel value for "no owner" - DenseThreadId values are 0 to SLOT_SIZE-1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static constexpr size_t NO_OWNER = SLOT_SIZE; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recursive_shared_mutex_impl() = default; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ~recursive_shared_mutex_impl() = default; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // No copying or moving | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recursive_shared_mutex_impl(recursive_shared_mutex_impl const &) = delete; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recursive_shared_mutex_impl &operator=(recursive_shared_mutex_impl const &) = delete; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recursive_shared_mutex_impl(recursive_shared_mutex_impl &&) = delete; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recursive_shared_mutex_impl &operator=(recursive_shared_mutex_impl &&) = delete; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Exclusive locking (recursive) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t tid = DenseThreadId::self(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fast path: check if we already own the lock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (_exclusive_owner.load(std::memory_order_relaxed) == tid) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ++_exclusive_count; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Upgrade prevention: cannot acquire exclusive lock while holding shared lock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ThreadState &state = _thread_states[tid]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| debug_assert(state.shared_count == 0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _mutex.lock(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _exclusive_owner.store(tid, std::memory_order_relaxed); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _exclusive_count = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try_lock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t tid = DenseThreadId::self(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fast path: check if we already own the lock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (_exclusive_owner.load(std::memory_order_relaxed) == tid) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ++_exclusive_count; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Upgrade prevention: cannot acquire exclusive lock while holding shared lock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ThreadState &state = _thread_states[tid]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (state.shared_count > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (_mutex.try_lock()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _exclusive_owner.store(tid, std::memory_order_relaxed); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _exclusive_count = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (--_exclusive_count == 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _exclusive_owner.store(NO_OWNER, std::memory_order_relaxed); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _mutex.unlock(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Shared locking (recursive) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lock_shared(Token &token) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t tid = DenseThreadId::self(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ThreadState &state = _thread_states[tid]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fast path: already holding shared lock - just increment count (most common case) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t count = state.shared_count; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (count > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state.shared_count = count + 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token = state.cached_token; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check for downgrade: if we hold exclusive lock, allow shared lock without acquiring underlying | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (_exclusive_owner.load(std::memory_order_relaxed) == tid) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state.shared_count = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token = 0; // Special token indicating we're under exclusive lock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Slow path: acquire underlying lock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _mutex.lock_shared(state.cached_token); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state.shared_count = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token = state.cached_token; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try_lock_shared(Token &token) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t tid = DenseThreadId::self(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ThreadState &state = _thread_states[tid]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fast path: already holding shared lock - just increment count (most common case) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t count = state.shared_count; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (count > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state.shared_count = count + 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token = state.cached_token; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check for downgrade: if we hold exclusive lock, allow shared lock without acquiring underlying | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (_exclusive_owner.load(std::memory_order_relaxed) == tid) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state.shared_count = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token = 0; // Special token indicating we're under exclusive lock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Slow path: try to acquire underlying lock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (_mutex.try_lock_shared(state.cached_token)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state.shared_count = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| token = state.cached_token; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unlock_shared(const Token /* token */) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t tid = DenseThreadId::self(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ThreadState &state = _thread_states[tid]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (--state.shared_count == 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Only unlock underlying mutex if we're not holding exclusive lock | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (_exclusive_owner.load(std::memory_order_relaxed) != tid) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _mutex.unlock_shared(state.cached_token); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state.cached_token = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+517
to
+526
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unlock_shared(const Token /* token */) | |
| { | |
| size_t tid = DenseThreadId::self(); | |
| ThreadState &state = _thread_states[tid]; | |
| if (--state.shared_count == 0) { | |
| // Only unlock underlying mutex if we're not holding exclusive lock | |
| if (_exclusive_owner.load(std::memory_order_relaxed) != tid) { | |
| _mutex.unlock_shared(state.cached_token); | |
| } | |
| state.cached_token = 0; | |
| unlock_shared(const Token token) | |
| { | |
| size_t tid = DenseThreadId::self(); | |
| ThreadState &state = _thread_states[tid]; | |
| TS_ASSERT(state.shared_count > 0); | |
| if (--state.shared_count == 0) { | |
| if (token == 0) { | |
| // Downgrade path: shared lock acquired while holding exclusive lock; | |
| // no underlying shared lock to release. | |
| TS_ASSERT(_exclusive_owner.load(std::memory_order_relaxed) == tid); | |
| } else { | |
| // Regular shared lock: must release underlying shared lock. | |
| TS_ASSERT(_exclusive_owner.load(std::memory_order_relaxed) != tid); | |
| _mutex.unlock_shared(token); | |
| state.cached_token = 0; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
recursive_shared_mutex_impl's shared-under-exclusive path does not acquire an underlying shared lock (it just sets
token = 0). If a caller unlocks the exclusive lock whileThreadState::shared_countis still > 0, the thread will no longer hold any lock, and a later unlock_shared may attempt to unlock an underlying shared lock that was never acquired. Add a guard (e.g., debug_assert shared_count==0 before releasing the underlying exclusive lock) or implement a true downgrade mechanism.