diff --git a/include/tsutil/Bravo.h b/include/tsutil/Bravo.h index 4660f69aca6..1dc1043d680 100644 --- a/include/tsutil/Bravo.h +++ b/include/tsutil/Bravo.h @@ -39,6 +39,7 @@ #include "tsutil/DenseThreadId.h" #include "tsutil/Assert.h" +#include "tsutil/ts_thread_safety.h" #include #include @@ -68,129 +69,54 @@ using Token = size_t; /** ts::bravo::shared_lock + + Reader guard for shared_mutex_impl, carrying the BRAVO Token. A rigid scoped + capability: it acquires the shared lock in its constructor and releases it in + its destructor, with no copy, move, defer or release. That rigidity is what + lets the analysis track it -- the deferred and movable forms of + std::shared_lock let the held state escape a single scope and cannot be + modelled -- and BRAVO readers need only this scoped form. */ -template class shared_lock +template class TS_SCOPED_CAPABILITY shared_lock { public: using mutex_type = Mutex; - shared_lock() noexcept = default; - shared_lock(Mutex &m) : _mutex(&m) { lock(); } - shared_lock(Mutex &m, std::try_to_lock_t) : _mutex(&m) { try_lock(); } - shared_lock(Mutex &m, std::defer_lock_t) noexcept : _mutex(&m) {} - - ~shared_lock() - { - if (_owns) { - _mutex->unlock_shared(_token); - } - }; + explicit shared_lock(Mutex &m) TS_ACQUIRE_SHARED(m) : _mutex(&m) { _mutex->lock_shared(_token); } + ~shared_lock() TS_RELEASE() { _mutex->unlock_shared(_token); } //// - // Not Copyable + // Neither copyable nor movable: the held shared lock must not escape this scope. // shared_lock(shared_lock const &) = delete; shared_lock &operator=(shared_lock const &) = delete; - - //// - // Moveable - // - shared_lock(shared_lock &&s) : _mutex(s._mutex), _token(s._token), _owns(s._owns) - { - s._mutex = nullptr; - s._token = 0; - s._owns = false; - }; - - shared_lock & - operator=(shared_lock &&s) - { - if (_owns) { - _mutex->unlock_shared(_token); - } - _mutex = s._mutex; - _token = s._token; - _owns = s._owns; - - s._mutex = nullptr; - s._token = 0; - s._owns = false; - }; - - //// - // Shared locking - // - void - lock() - { - _mutex->lock_shared(_token); - _owns = true; - } - - bool - try_lock() - { - _owns = _mutex->try_lock_shared(_token); - return _owns; - } - - // not implemented yet - bool try_lock_for() = delete; - bool try_lock_until() = delete; - - void - unlock() - { - _mutex->unlock_shared(_token); - _owns = false; - } - - //// - // Modifiers - // - void - swap(shared_lock &s) - { - std::swap(_mutex, s._mutex); - std::swap(_token, s._token); - std::swap(_owns, s._owns); - } - - mutex_type * - release() - { - mutex_type *m = _mutex; - _mutex = nullptr; - _token = 0; - _owns = false; - return m; - } + shared_lock(shared_lock &&) = delete; + shared_lock &operator=(shared_lock &&) = delete; //// // Observers // mutex_type * - mutex() + mutex() const { return _mutex; } Token - token() + token() const { return _token; } bool - owns_lock() + owns_lock() const { - return _owns; + return _mutex != nullptr; } private: mutex_type *_mutex = nullptr; Token _token = 0; - bool _owns = false; }; /** @@ -201,7 +127,8 @@ template class shared_lock Set the SLOT_SIZE larger than DenseThreadId::num_possible_values to go fast-path. */ -template class shared_mutex_impl +template +class TS_CAPABILITY("shared_mutex") shared_mutex_impl { public: shared_mutex_impl() = default; @@ -219,15 +146,22 @@ template = DenseThreadId::num_possible_values()); + // Clear up front so a slow-path acquisition never leaves a stale fast-path slot for unlock_shared(). + token = 0; + // Fast path if (_mutex.read_bias.load(std::memory_order_acquire)) { size_t index = DenseThreadId::self() % SLOT_SIZE; @@ -277,10 +214,13 @@ template = DenseThreadId::num_possible_values()); + // Clear up front so a slow-path acquisition never leaves a stale fast-path slot for unlock_shared(). + token = 0; + // Fast path if (_mutex.read_bias.load(std::memory_order_acquire)) { size_t index = DenseThreadId::self() % SLOT_SIZE; @@ -313,7 +253,7 @@ template