You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by bn...@apache.org on 2026/08/26 18:52:23 UTC
(trafficserver) branch master updated: Test AtomicSharedPtr concurrency (#13566)
This is an automated email from the ASF dual-hosted git repository.
bneradt 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 246cddb3c0 Test AtomicSharedPtr concurrency (#13566)
246cddb3c0 is described below
commit 246cddb3c0061db0f088e36db3f417b40dec29d3
Author: Brian Neradt <br...@gmail.com>
AuthorDate: Wed Aug 26 13:52:17 2026 -0500
Test AtomicSharedPtr concurrency (#13566)
Add Catch2 coverage for AtomicSharedPtr load/store/exchange
operations and concurrent readers during writer swaps. Also
clarify the fallback implementation comment so it no longer
relies on stale library-version shorthand.
---
include/tsutil/AtomicSharedPtr.h | 22 ++--
src/tsutil/CMakeLists.txt | 1 +
src/tsutil/unit_tests/test_AtomicSharedPtr.cc | 162 ++++++++++++++++++++++++++
3 files changed, 173 insertions(+), 12 deletions(-)
diff --git a/include/tsutil/AtomicSharedPtr.h b/include/tsutil/AtomicSharedPtr.h
index c7138a8e57..c6838a7d74 100644
--- a/include/tsutil/AtomicSharedPtr.h
+++ b/include/tsutil/AtomicSharedPtr.h
@@ -27,24 +27,22 @@
#include <atomic>
#include <memory>
-// Use the C++20 std::atomic<std::shared_ptr<T>> specialization when the
-// standard library provides it, otherwise fall back to the pre-C++20
-// std::atomic_*_explicit free-function overloads on shared_ptr. The
-// fallback exists for libstdc++ < 12 and libc++ < 14, which predate the
-// specialization. When those toolchains are no longer supported, delete
-// the #else branch and the surrounding #if; call sites do not change.
+// Use the C++20 std::atomic<std::shared_ptr<T>> specialization when its
+// feature-test macro reports support, otherwise fall back to the pre-C++20
+// std::atomic_*_explicit free-function overloads on shared_ptr. When all
+// supported toolchains provide the specialization, delete the #else branch
+// and the surrounding #if; call sites do not change.
#if defined(__cpp_lib_atomic_shared_ptr) && __cpp_lib_atomic_shared_ptr >= 201711L
template <class T> using AtomicSharedPtr = std::atomic<std::shared_ptr<T>>;
#else
-// Belt-and-suspenders: on the toolchains that take this branch (libstdc++
-// < 12, libc++ < 16) the free-function overloads are not yet marked
-// [[deprecated]], so the suppression below is usually a no-op. It
-// matters only if someone forces the fallback on a modern library (e.g.
-// -D__cpp_lib_atomic_shared_ptr=0) or compiles against a library that
-// ships the deprecation markers ahead of the specialization.
+// Belt-and-suspenders: on toolchains that take this branch, the free-function
+// overloads are normally not marked [[deprecated]], so the suppression below is
+// usually a no-op. It matters only if someone forces the fallback on a modern
+// library (e.g. -D__cpp_lib_atomic_shared_ptr=0) or compiles against a library
+// that ships the deprecation markers ahead of the specialization.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
diff --git a/src/tsutil/CMakeLists.txt b/src/tsutil/CMakeLists.txt
index 7d0e9a651c..0526007351 100644
--- a/src/tsutil/CMakeLists.txt
+++ b/src/tsutil/CMakeLists.txt
@@ -74,6 +74,7 @@ endif()
if(BUILD_TESTING)
add_executable(
test_tsutil
+ unit_tests/test_AtomicSharedPtr.cc
unit_tests/test_Bravo.cc
unit_tests/test_LocalBuffer.cc
unit_tests/test_Metrics.cc
diff --git a/src/tsutil/unit_tests/test_AtomicSharedPtr.cc b/src/tsutil/unit_tests/test_AtomicSharedPtr.cc
new file mode 100644
index 0000000000..4954221c44
--- /dev/null
+++ b/src/tsutil/unit_tests/test_AtomicSharedPtr.cc
@@ -0,0 +1,162 @@
+/** @file
+
+ Unit tests for AtomicSharedPtr
+
+ @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 <catch2/catch_test_macros.hpp>
+
+#include "tsutil/AtomicSharedPtr.h"
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <thread>
+#include <vector>
+
+namespace
+{
+struct Payload {
+ static constexpr size_t VALUE_COUNT = 8;
+
+ explicit Payload(int generation) : generation_(generation) { std::fill(std::begin(values_), std::end(values_), generation); }
+
+ bool
+ is_valid() const
+ {
+ return std::all_of(std::begin(values_), std::end(values_), [this](int value) { return value == generation_; });
+ }
+
+ int generation_ = 0;
+ int values_[VALUE_COUNT];
+};
+
+struct ReaderState {
+ std::atomic<bool> should_start{false};
+ std::atomic<bool> should_stop{false};
+ std::atomic<int> invalid_reads{0};
+ std::atomic<int> read_count{0};
+};
+
+void
+run_reader(AtomicSharedPtr<Payload> &ptr, ReaderState &state)
+{
+ while (!state.should_start.load(std::memory_order_acquire)) {
+ std::this_thread::yield();
+ }
+
+ while (!state.should_stop.load(std::memory_order_acquire)) {
+ auto current = ptr.load(std::memory_order_acquire);
+ if (current == nullptr || !current->is_valid()) {
+ state.invalid_reads.fetch_add(1, std::memory_order_relaxed);
+ }
+ auto const reads = state.read_count.fetch_add(1, std::memory_order_release) + 1;
+ if (reads % 64 == 0) {
+ std::this_thread::yield();
+ }
+ }
+}
+
+std::vector<std::thread>
+make_readers(int reader_count, AtomicSharedPtr<Payload> &ptr, ReaderState &state)
+{
+ std::vector<std::thread> readers;
+
+ readers.reserve(reader_count);
+ for (int i = 0; i < reader_count; ++i) {
+ readers.emplace_back([&ptr, &state] { run_reader(ptr, state); });
+ }
+ return readers;
+}
+
+bool
+wait_for_reader(const ReaderState &state, std::chrono::steady_clock::duration timeout)
+{
+ auto const deadline = std::chrono::steady_clock::now() + timeout;
+
+ while (std::chrono::steady_clock::now() < deadline) {
+ if (state.read_count.load(std::memory_order_acquire) > 0) {
+ return true;
+ }
+ std::this_thread::yield();
+ }
+ return false;
+}
+
+void
+stop_readers(ReaderState &state, std::vector<std::thread> &readers)
+{
+ state.should_stop.store(true, std::memory_order_release);
+ for (auto &reader : readers) {
+ if (reader.joinable()) {
+ reader.join();
+ }
+ }
+}
+} // end anonymous namespace
+
+TEST_CASE("AtomicSharedPtr load store exchange", "[libts][AtomicSharedPtr]")
+{
+ AtomicSharedPtr<int> ptr;
+
+ CHECK(ptr.load() == nullptr);
+
+ auto first = std::make_shared<int>(1);
+ ptr.store(first);
+ CHECK(ptr.load() == first);
+ CHECK(*ptr.load() == 1);
+
+ auto second = std::make_shared<int>(2);
+ auto previous = ptr.exchange(second);
+ CHECK(previous == first);
+ CHECK(ptr.load() == second);
+ CHECK(*ptr.load() == 2);
+}
+
+TEST_CASE("AtomicSharedPtr supports concurrent readers during writer swaps", "[libts][AtomicSharedPtr]")
+{
+ static constexpr int READER_COUNT = 8;
+ static constexpr int WRITE_COUNT = 5000;
+
+ AtomicSharedPtr<Payload> ptr{std::make_shared<Payload>(0)};
+ ReaderState state;
+ auto readers = make_readers(READER_COUNT, ptr, state);
+
+ state.should_start.store(true, std::memory_order_release);
+ auto const reader_started = wait_for_reader(state, std::chrono::seconds(5));
+ if (!reader_started) {
+ stop_readers(state, readers);
+ }
+ REQUIRE(reader_started);
+
+ for (int generation = 1; generation <= WRITE_COUNT; ++generation) {
+ ptr.store(std::make_shared<Payload>(generation), std::memory_order_release);
+ if (generation % 64 == 0) {
+ std::this_thread::yield();
+ }
+ }
+ stop_readers(state, readers);
+
+ CHECK(state.invalid_reads.load() == 0);
+ CHECK(state.read_count.load() > 0);
+ REQUIRE(ptr.load() != nullptr);
+ CHECK(ptr.load()->generation_ == WRITE_COUNT);
+}