This is an automated email from the ASF dual-hosted git repository.
masaori pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new c331c7205c Add BRAVO Reader-Writer Lock (#9394)
c331c7205c is described below
commit c331c7205cf1b0f4a4955211ccbec50d36fb063e
Author: Masaori Koshiba <[email protected]>
AuthorDate: Tue May 23 07:52:57 2023 +0900
Add BRAVO Reader-Writer Lock (#9394)
* Add BRAVO Reader-Writer Lock
* Add DenseThreadId
Signed-off-by: Walt Karas <[email protected]>
---
.gitignore | 1 +
NOTICE | 5 +
include/tscpp/util/Bravo.h | 377 ++++++++++++++++++++++++
include/tscpp/util/DenseThreadId.h | 116 ++++++++
src/tscore/Makefile.am | 7 +-
src/tscore/unit_tests/benchmark_shared_mutex.cc | 134 +++++++++
src/tscore/unit_tests/test_Bravo.cc | 229 ++++++++++++++
src/tscpp/util/Makefile.am | 4 +-
8 files changed, 871 insertions(+), 2 deletions(-)
diff --git a/.gitignore b/.gitignore
index 30a42adcc6..426124c8ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -86,6 +86,7 @@ src/tscore/ink_autoconf.h
src/tscore/ink_autoconf.h.in
include/tscore/ink_config.h
include/ts/apidefs.h
+src/tscore/benchmark_shared_mutex
src/tscore/CompileParseRules
src/tscore/CompileParseRules.dSYM
src/tscore/ParseRulesCType
diff --git a/NOTICE b/NOTICE
index 949f852011..50552702ca 100644
--- a/NOTICE
+++ b/NOTICE
@@ -96,3 +96,8 @@ https://github.com/jbeder/yaml-cpp
fastlz: an ANSI C/C90 implementation of Lempel-Ziv 77 algorithm (LZ77) of
lossless data compression.
https://github.com/ariya/FastLZ
+
+~~
+
+include/tscpp/util/Bravo.h is C++ version of puzpuzpuz/xsync's RBMutex
+Copyright (c) 2021 Andrey Pechkurov (MIT License)
diff --git a/include/tscpp/util/Bravo.h b/include/tscpp/util/Bravo.h
new file mode 100644
index 0000000000..a9155d3492
--- /dev/null
+++ b/include/tscpp/util/Bravo.h
@@ -0,0 +1,377 @@
+/** @file
+
+ Implementation of BRAVO - Biased Locking for Reader-Writer Locks
+
+ Dave Dice and Alex Kogan. 2019. BRAVO: Biased Locking for Reader-Writer
Locks.
+ In Proceedings of the 2019 USENIX Annual Technical Conference (ATC). USENIX
Association, Renton, WA, 315–328.
+
+ https://www.usenix.org/conference/atc19/presentation/dice
+
+ > Section 3.
+ > BRAVO acts as an accelerator layer, as readers can always fall back to
the traditional underlying lock to gain read access.
+ > ...
+ > Write performance and the scalability of read-vs-write and
write-vs-write behavior depends solely on the underlying lock.
+
+ This code is C++ version of puzpuzpuz/xsync's RBMutex
+ https://github.com/puzpuzpuz/xsync/blob/main/rbmutex.go
+ Copyright (c) 2021 Andrey Pechkurov
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#pragma once
+
+#include "DenseThreadId.h"
+
+#include "tscore/Diags.h"
+#include "tscore/ink_assert.h"
+
+#include <array>
+#include <atomic>
+#include <cassert>
+#include <chrono>
+#include <shared_mutex>
+#include <thread>
+
+namespace ts::bravo
+{
+using time_point = std::chrono::time_point<std::chrono::system_clock>;
+
+#ifdef __cpp_lib_hardware_interference_size
+using std::hardware_constructive_interference_size;
+#else
+// 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned
│ ...
+constexpr std::size_t hardware_constructive_interference_size = 64;
+#endif
+
+/**
+ ts::bravo::Token
+
+ Token for readers.
+ 0 is special value that represents inital/invalid value.
+ */
+using Token = size_t;
+
+/**
+ ts::bravo::shared_lock
+ */
+template <class Mutex> class 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);
+ }
+ };
+
+ ////
+ // Not Copyable
+ //
+ 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;
+ }
+
+ ////
+ // Observers
+ //
+ mutex_type *
+ mutex()
+ {
+ return _mutex;
+ }
+
+ Token
+ token()
+ {
+ return _token;
+ }
+
+ bool
+ owns_lock()
+ {
+ return _owns;
+ }
+
+private:
+ mutex_type *_mutex = nullptr;
+ Token _token = 0;
+ bool _owns = false;
+};
+
+/**
+ ts::bravo::shared_mutex
+
+ You can use std::lock_guard for writers but, you can't use std::shared_lock
for readers to handle ts::bravo::Token.
+ Use ts::bravo::shared_lock for readers.
+
+ Set the SLOT_SIZE larger than DenseThreadId::num_possible_values to go
fast-path.
+ */
+template <typename T = std::shared_mutex, size_t SLOT_SIZE = 256, int
SLOWDOWN_GUARD = 7> class shared_mutex_impl
+{
+public:
+ shared_mutex_impl() = default;
+ ~shared_mutex_impl() = default;
+
+ ////
+ // No copying or moving.
+ //
+ shared_mutex_impl(shared_mutex_impl const &) = delete;
+ shared_mutex_impl &operator=(shared_mutex_impl const &) = delete;
+
+ shared_mutex_impl(shared_mutex_impl &&) = delete;
+ shared_mutex_impl &operator=(shared_mutex_impl &&) = delete;
+
+ ////
+ // Exclusive locking
+ //
+ void
+ lock()
+ {
+ _mutex.underlying.lock();
+ _revoke();
+ }
+
+ bool
+ try_lock()
+ {
+ bool r = _mutex.underlying.try_lock();
+ if (!r) {
+ return false;
+ }
+
+ _revoke();
+
+ return true;
+ }
+
+ void
+ unlock()
+ {
+ _mutex.underlying.unlock();
+ }
+
+ ////
+ // Shared locking
+ //
+ void
+ lock_shared(Token &token)
+ {
+ ink_assert(SLOT_SIZE >= DenseThreadId::num_possible_values());
+
+ // Fast path
+ if (_mutex.read_bias.load(std::memory_order_acquire)) {
+ size_t index = DenseThreadId::self() % SLOT_SIZE;
+ Slot &slot = _mutex.readers[index];
+ bool expect = false;
+ if (slot.mu.compare_exchange_strong(expect, true,
std::memory_order_relaxed)) {
+ // recheck
+ if (_mutex.read_bias.load(std::memory_order_acquire)) {
+ token = index + 1;
+ return;
+ } else {
+ slot.mu.store(false, std::memory_order_relaxed);
+ }
+ }
+ }
+
+ // Slow path
+ _mutex.underlying.lock_shared();
+ if (_mutex.read_bias.load(std::memory_order_acquire) == false && _now() >=
_mutex.inhibit_until) {
+ _mutex.read_bias.store(true, std::memory_order_release);
+ }
+ }
+
+ bool
+ try_lock_shared(Token &token)
+ {
+ ink_assert(SLOT_SIZE >= DenseThreadId::num_possible_values());
+
+ // Fast path
+ if (_mutex.read_bias.load(std::memory_order_acquire)) {
+ size_t index = DenseThreadId::self() % SLOT_SIZE;
+ Slot &slot = _mutex.readers[index];
+ bool expect = false;
+
+ if (slot.mu.compare_exchange_weak(expect, true,
std::memory_order_release, std::memory_order_relaxed)) {
+ // recheck
+ if (_mutex.read_bias.load(std::memory_order_acquire)) {
+ token = index + 1;
+ return true;
+ } else {
+ slot.mu.store(false, std::memory_order_relaxed);
+ }
+ }
+ }
+
+ // Slow path
+ bool r = _mutex.underlying.try_lock_shared();
+ if (r) {
+ // Set RBias if the BRAVO policy allows that
+ if (_mutex.read_bias.load(std::memory_order_acquire) == false && _now()
>= _mutex.inhibit_until) {
+ _mutex.read_bias.store(true, std::memory_order_release);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ void
+ unlock_shared(const Token token)
+ {
+ if (token == 0) {
+ _mutex.underlying.unlock_shared();
+ return;
+ }
+
+ Slot &slot = _mutex.readers[token - 1];
+ slot.mu.store(false, std::memory_order_relaxed);
+ }
+
+private:
+ struct alignas(hardware_constructive_interference_size) Slot {
+ std::atomic<bool> mu = false;
+ };
+
+ struct Mutex {
+ std::atomic<bool> read_bias = false;
+ std::array<Slot, SLOT_SIZE> readers = {};
+ time_point inhibit_until{};
+ T underlying;
+ };
+
+ time_point
+ _now()
+ {
+ return std::chrono::system_clock::now();
+ }
+
+ /**
+ Disable read bias and do revocation
+ */
+ void
+ _revoke()
+ {
+ if (!_mutex.read_bias.load(std::memory_order_acquire)) {
+ // do nothing
+ return;
+ }
+
+ _mutex.read_bias.store(false, std::memory_order_release);
+ time_point start = _now();
+ for (size_t i = 0; i < SLOT_SIZE; ++i) {
+ for (int j = 0; _mutex.readers[i].mu.load(std::memory_order_relaxed);
++j) {
+ std::this_thread::sleep_for(std::chrono::nanoseconds(1 << j));
+ }
+ }
+ time_point n = _now();
+ _mutex.inhibit_until = n + ((n - start) * SLOWDOWN_GUARD);
+ }
+
+ ////
+ // Variables
+ //
+ Mutex _mutex;
+};
+
+using shared_mutex = shared_mutex_impl<>;
+
+} // namespace ts::bravo
diff --git a/include/tscpp/util/DenseThreadId.h
b/include/tscpp/util/DenseThreadId.h
new file mode 100644
index 0000000000..9189cf7428
--- /dev/null
+++ b/include/tscpp/util/DenseThreadId.h
@@ -0,0 +1,116 @@
+/** @file
+
+ A replacement for std::shared_mutex with guarantees against writer
starvation.
+ Cache contention between CPU cores is avoided except when a write lock is
taken.
+ Assumes no thread will exit while holding mutex.
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#pragma once
+
+#if __has_include(<tscore/ink_assert.h>)
+// Included in core.
+#include <tscore/ink_assert.h>
+#define L_Assert ink_assert
+#include <tscore/Diags.h>
+#define L_Fatal Fatal
+#else
+// Should be plugin code.
+#include <ts/ts.h>
+#define L_Assert TSAssert
+#define L_Fatal TSFatal
+#endif
+
+#include <cstddef>
+#include <mutex>
+#include <vector>
+
+// Provide an alternate thread id, suitible for use as an array index.
+//
+class DenseThreadId
+{
+public:
+ // This can onlhy be called during single-threaded initialization.
+ //
+ static void
+ set_num_possible_values(std::size_t num_possible_values)
+ {
+ _num_possible_values = num_possible_values;
+ }
+
+ static std::size_t
+ self()
+ {
+ return _id.val;
+ }
+
+ static std::size_t
+ num_possible_values()
+ {
+ return _num_possible_values;
+ }
+
+private:
+ inline static std::mutex _mtx;
+ inline static std::vector<std::size_t> _id_stack;
+ inline static std::size_t _stack_top_idx;
+ inline static std::size_t _num_possible_values{256};
+
+ static void
+ _init()
+ {
+ _id_stack.resize(_num_possible_values);
+
+ _stack_top_idx = 0;
+ for (std::size_t i{0}; i < _num_possible_values; ++i) {
+ _id_stack[i] = i + 1;
+ }
+ }
+
+ struct _Id {
+ _Id()
+ {
+ std::unique_lock<std::mutex> ul{_mtx};
+
+ if (!_inited) {
+ _init();
+ _inited = true;
+ }
+ if (_id_stack.size() == _stack_top_idx) {
+ L_Fatal("DenseThreadId: number of threads exceeded maximum (%u)",
unsigned(_id_stack.size()));
+ }
+ val = _stack_top_idx;
+ _stack_top_idx = _id_stack[_stack_top_idx];
+ }
+
+ ~_Id()
+ {
+ std::unique_lock<std::mutex> ul{_mtx};
+
+ _id_stack[val] = _stack_top_idx;
+ _stack_top_idx = val;
+ }
+
+ std::size_t val;
+ };
+
+ inline static thread_local _Id _id;
+ inline static bool _inited{false};
+};
diff --git a/src/tscore/Makefile.am b/src/tscore/Makefile.am
index 142d6f63d0..5a5f0ef1ae 100644
--- a/src/tscore/Makefile.am
+++ b/src/tscore/Makefile.am
@@ -18,7 +18,7 @@
include $(top_srcdir)/build/tidy.mk
-noinst_PROGRAMS = CompileParseRules freelist_benchmark
+noinst_PROGRAMS = CompileParseRules freelist_benchmark benchmark_shared_mutex
check_PROGRAMS = test_geometry test_X509HostnameValidator test_tscore
if EXPENSIVE_TESTS
@@ -172,6 +172,7 @@ test_tscore_SOURCES = \
unit_tests/test_ArgParser.cc \
unit_tests/test_BufferWriter.cc \
unit_tests/test_BufferWriterFormat.cc \
+ unit_tests/test_Bravo.cc \
unit_tests/test_CryptoHash.cc \
unit_tests/test_Extendible.cc \
unit_tests/test_Histogram.cc \
@@ -205,6 +206,10 @@ freelist_benchmark_CXXFLAGS = -Wno-array-bounds
$(AM_CXXFLAGS) -I$(abs_top_srcdi
freelist_benchmark_LDADD = libtscore.la @HWLOC_LIBS@
freelist_benchmark_SOURCES = unit_tests/freelist_benchmark.cc
+benchmark_shared_mutex_CXXFLAGS = -Wno-array-bounds $(AM_CXXFLAGS)
-I$(abs_top_srcdir)/tests/include
+benchmark_shared_mutex_LDADD = libtscore.la
+benchmark_shared_mutex_SOURCES = unit_tests/benchmark_shared_mutex.cc
+
CompileParseRules_SOURCES = CompileParseRules.cc
CompileParseRules$(BUILD_EXEEXT): $(CompileParseRules_OBJECTS)
diff --git a/src/tscore/unit_tests/benchmark_shared_mutex.cc
b/src/tscore/unit_tests/benchmark_shared_mutex.cc
new file mode 100644
index 0000000000..fe1a499d77
--- /dev/null
+++ b/src/tscore/unit_tests/benchmark_shared_mutex.cc
@@ -0,0 +1,134 @@
+/** @file
+
+ Micro Benchmark tool for shared_mutex - requires Catch2 v2.9.0+
+
+ - e.g. example of running 64 threads with read/write rate is 100:1
+ ```
+ $ taskset -c 0-63 ./benchmark_shared_mutex --ts-nthreads 64 --ts-nloop 1000
--ts-nread 100 --ts-nwrite 1
+ ```
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#define CATCH_CONFIG_ENABLE_BENCHMARKING
+#define CATCH_CONFIG_RUNNER
+
+#include "catch.hpp"
+
+#include "tscpp/util/Bravo.h"
+
+#include <mutex>
+#include <shared_mutex>
+#include <thread>
+
+namespace
+{
+// Args
+struct Conf {
+ int nloop = 1;
+ int nthreads = 1;
+ int nread = 1;
+ int nwrite = 1;
+};
+
+Conf conf;
+template <typename T, typename S>
+int
+run(T &mutex)
+{
+ std::thread list[conf.nthreads];
+ int counter = 0;
+
+ for (int i = 0; i < conf.nthreads; i++) {
+ new (&list[i]) std::thread{[&counter](T &mutex) {
+ int c = 0;
+ for (int j = 0; j < conf.nloop; ++j) {
+ // reader
+ for (int i = 0; i < conf.nread; ++i) {
+ S lock(mutex);
+ // Do not optimize
+ c = counter;
+ }
+
+ // writer
+ for (int i = 0; i < conf.nwrite; ++i) {
+ std::lock_guard lock(mutex);
+ // Do not optimize
+ ++c;
+ counter = c;
+ }
+ }
+ },
+ std::ref(mutex)};
+ }
+
+ for (int i = 0; i < conf.nthreads; i++) {
+ list[i].join();
+ }
+
+ return counter;
+}
+
+} // namespace
+
+TEST_CASE("Micro benchmark of shared_mutex", "")
+{
+ SECTION("std::shared_mutex")
+ {
+ BENCHMARK("std::shared_mutex", )
+ {
+ std::shared_mutex mutex;
+
+ return run<std::shared_mutex,
std::shared_lock<std::shared_mutex>>(mutex);
+ };
+ }
+
+ SECTION("ts::bravo::shared_mutex")
+ {
+ BENCHMARK("ts::bravo::shared_mutex")
+ {
+ ts::bravo::shared_mutex mutex;
+
+ return run<ts::bravo::shared_mutex,
ts::bravo::shared_lock<ts::bravo::shared_mutex>>(mutex);
+ };
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ Catch::Session session;
+
+ using namespace Catch::clara;
+
+ // clang-format off
+ auto cli = session.cli() |
+ Opt(conf.nthreads, "")["--ts-nthreads"]("number of threads (default: 1)") |
+ Opt(conf.nread, "")["--ts-nread"]("number of read op (default: 1)") |
+ Opt(conf.nwrite, "")["--ts-nwrite"]("number of write op (default: 1)") |
+ Opt(conf.nloop, "")["--ts-nloop"]("number of read-write loop (default:
1)");
+ // clang-format on
+
+ session.cli(cli);
+
+ int returnCode = session.applyCommandLine(argc, argv);
+ if (returnCode != 0) {
+ return returnCode;
+ }
+
+ return session.run();
+}
diff --git a/src/tscore/unit_tests/test_Bravo.cc
b/src/tscore/unit_tests/test_Bravo.cc
new file mode 100644
index 0000000000..e64e7514f3
--- /dev/null
+++ b/src/tscore/unit_tests/test_Bravo.cc
@@ -0,0 +1,229 @@
+/** @file
+
+ Unit tests for BRAVO
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+#include "catch.hpp"
+#include "tscpp/util/Bravo.h"
+
+#include <chrono>
+#include <mutex>
+#include <shared_mutex>
+#include <thread>
+
+using namespace std::chrono_literals;
+
+TEST_CASE("BRAVO - simple check", "[libts][BRAVO]")
+{
+ SECTION("reader-reader")
+ {
+ ts::bravo::shared_mutex mutex;
+ ts::bravo::shared_lock<ts::bravo::shared_mutex> lock(mutex);
+ CHECK(lock.owns_lock() == true);
+
+ std::thread t{[](ts::bravo::shared_mutex &mutex) {
+ ts::bravo::Token token{0};
+ CHECK(mutex.try_lock_shared(token) == true);
+ mutex.unlock_shared(token);
+ },
+ std::ref(mutex)};
+
+ t.join();
+ }
+
+ SECTION("reader-writer")
+ {
+ ts::bravo::shared_mutex mutex;
+ ts::bravo::shared_lock<ts::bravo::shared_mutex> lock(mutex);
+ CHECK(lock.owns_lock() == true);
+
+ std::thread t{[](ts::bravo::shared_mutex &mutex) { CHECK(mutex.try_lock()
== false); }, std::ref(mutex)};
+
+ t.join();
+ }
+
+ SECTION("writer-reader")
+ {
+ ts::bravo::shared_mutex mutex;
+ std::lock_guard<ts::bravo::shared_mutex> lock(mutex);
+
+ std::thread t{[](ts::bravo::shared_mutex &mutex) {
+ ts::bravo::Token token{0};
+ CHECK(mutex.try_lock_shared(token) == false);
+ CHECK(token == 0);
+ },
+ std::ref(mutex)};
+
+ t.join();
+ }
+
+ SECTION("writer-writer")
+ {
+ ts::bravo::shared_mutex mutex;
+ std::lock_guard<ts::bravo::shared_mutex> lock(mutex);
+
+ std::thread t{[](ts::bravo::shared_mutex &mutex) { CHECK(mutex.try_lock()
== false); }, std::ref(mutex)};
+
+ t.join();
+ }
+}
+
+TEST_CASE("BRAVO - multiple try-lock", "[libts][BRAVO]")
+{
+ SECTION("rwrw")
+ {
+ ts::bravo::shared_mutex mutex;
+ int i = 0;
+
+ {
+ ts::bravo::Token token{0};
+ CHECK(mutex.try_lock_shared(token));
+ CHECK(i == 0);
+ mutex.unlock_shared(token);
+ }
+
+ {
+ CHECK(mutex.try_lock());
+ CHECK(++i == 1);
+ mutex.unlock();
+ }
+
+ {
+ ts::bravo::Token token{0};
+ CHECK(mutex.try_lock_shared(token));
+ CHECK(i == 1);
+ mutex.unlock_shared(token);
+ }
+
+ {
+ CHECK(mutex.try_lock());
+ CHECK(++i == 2);
+ mutex.unlock();
+ }
+
+ CHECK(i == 2);
+ }
+}
+
+TEST_CASE("BRAVO - check with race", "[libts][BRAVO]")
+{
+ SECTION("reader-reader")
+ {
+ ts::bravo::shared_mutex mutex;
+ int i = 0;
+
+ std::thread t1{[&](ts::bravo::shared_mutex &mutex) {
+ ts::bravo::shared_lock<ts::bravo::shared_mutex>
lock(mutex);
+ CHECK(lock.owns_lock() == true);
+ CHECK(i == 0);
+ },
+ std::ref(mutex)};
+
+ std::thread t2{[&](ts::bravo::shared_mutex &mutex) {
+ ts::bravo::shared_lock<ts::bravo::shared_mutex>
lock(mutex);
+ CHECK(lock.owns_lock() == true);
+ CHECK(i == 0);
+ },
+ std::ref(mutex)};
+
+ t1.join();
+ t2.join();
+
+ CHECK(i == 0);
+ }
+
+ SECTION("reader-writer")
+ {
+ ts::bravo::shared_mutex mutex;
+ int i = 0;
+
+ std::thread t1{[&](ts::bravo::shared_mutex &mutex) {
+ ts::bravo::shared_lock<ts::bravo::shared_mutex>
lock(mutex);
+ CHECK(lock.owns_lock() == true);
+ CHECK(i == 0);
+ std::this_thread::sleep_for(100ms);
+ },
+ std::ref(mutex)};
+
+ std::thread t2{[&](ts::bravo::shared_mutex &mutex) {
+ std::this_thread::sleep_for(50ms);
+ std::lock_guard<ts::bravo::shared_mutex> lock(mutex);
+ CHECK(++i == 1);
+ },
+ std::ref(mutex)};
+
+ t1.join();
+ t2.join();
+
+ CHECK(i == 1);
+ }
+
+ SECTION("writer-reader")
+ {
+ ts::bravo::shared_mutex mutex;
+ int i = 0;
+
+ std::thread t1{[&](ts::bravo::shared_mutex &mutex) {
+ std::lock_guard<ts::bravo::shared_mutex> lock(mutex);
+ std::this_thread::sleep_for(100ms);
+ CHECK(++i == 1);
+ },
+ std::ref(mutex)};
+
+ std::thread t2{[&](ts::bravo::shared_mutex &mutex) {
+ std::this_thread::sleep_for(50ms);
+ ts::bravo::shared_lock<ts::bravo::shared_mutex>
lock(mutex);
+ CHECK(lock.owns_lock() == true);
+ CHECK(i == 1);
+ },
+ std::ref(mutex)};
+
+ t1.join();
+ t2.join();
+
+ CHECK(i == 1);
+ }
+
+ SECTION("writer-writer")
+ {
+ ts::bravo::shared_mutex mutex;
+ int i = 0;
+
+ std::thread t1{[&](ts::bravo::shared_mutex &mutex) {
+ std::lock_guard<ts::bravo::shared_mutex> lock(mutex);
+ std::this_thread::sleep_for(100ms);
+ CHECK(++i == 1);
+ },
+ std::ref(mutex)};
+
+ std::thread t2{[&](ts::bravo::shared_mutex &mutex) {
+ std::this_thread::sleep_for(50ms);
+ std::lock_guard<ts::bravo::shared_mutex> lock(mutex);
+ CHECK(++i == 2);
+ },
+ std::ref(mutex)};
+
+ t1.join();
+ t2.join();
+
+ CHECK(i == 2);
+ }
+}
diff --git a/src/tscpp/util/Makefile.am b/src/tscpp/util/Makefile.am
index fa51549fbc..5845a2e41c 100644
--- a/src/tscpp/util/Makefile.am
+++ b/src/tscpp/util/Makefile.am
@@ -36,7 +36,9 @@ test_tscpputil_CPPFLAGS = $(AM_CPPFLAGS)\
-I$(abs_top_srcdir)/tests/include @SWOC_INCLUDES@
test_tscpputil_CXXFLAGS = -Wno-array-bounds $(AM_CXXFLAGS)
-test_tscpputil_LDADD = libtscpputil.la @SWOC_LIBS@
+test_tscpputil_LDADD = \
+ libtscpputil.la \
+ @SWOC_LIBS@
test_tscpputil_SOURCES = \
unit_tests/unit_test_main.cc \
unit_tests/test_LocalBuffer.cc \