From 3351cab398bf071bd23dbd1be91eca05ab6715be Mon Sep 17 00:00:00 2001 From: "torlando-agent[bot]" <281092095+torlando-agent[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 04:15:29 +0000 Subject: [PATCH] test: strengthen call admission contention coverage --- lib/tdeck_ui/UI/LXMF/CallGenerationGuard.h | 5 ++- tests/README.md | 2 +- tests/native/test_call_generation_guard.cpp | 46 +++++++++++++-------- tests/native/test_call_generation_guard.py | 1 + 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/lib/tdeck_ui/UI/LXMF/CallGenerationGuard.h b/lib/tdeck_ui/UI/LXMF/CallGenerationGuard.h index 42ca67a8..a5ab5db9 100644 --- a/lib/tdeck_ui/UI/LXMF/CallGenerationGuard.h +++ b/lib/tdeck_ui/UI/LXMF/CallGenerationGuard.h @@ -10,9 +10,10 @@ namespace UI { namespace LXMF { -// Lock-free admission guard for call setup. Each successful reservation owns a +// Atomic admission guard for call setup. Each successful reservation owns a // generation until that exact generation releases it; generation zero always -// means "unowned". +// means "unowned". Generations eventually repeat after the finite token space +// wraps, so a token retained for a full cycle can exhibit ABA. class CallGenerationGuard { public: static constexpr uint32_t MAX_GENERATION = 0x7fffffffu; diff --git a/tests/README.md b/tests/README.md index b5145059..ba814c46 100644 --- a/tests/README.md +++ b/tests/README.md @@ -21,7 +21,7 @@ System Python 3.9 has pytest pre-installed; Homebrew Python does not. - `native/test_ring_buffers.{cpp,py}` — PCM + encoded SPSC ring buffers, including 100k-frame multithreaded producer/consumer stress - `native/test_audio_filters.{cpp,py}` — VoiceFilterChain frequency response, peak limiting, multichannel - `native/test_call_command_mailbox.{cpp,py}` — generation-scoped LXST hangup/mute command handoff and producer/consumer stress -- `native/test_call_generation_guard.{cpp,py}` — lock-free, generation-scoped call admission, stale-owner protection, and two-thread reservation races +- `native/test_call_generation_guard.{cpp,py}` — atomic, generation-scoped call admission, stale-owner protection, and repeated two-thread reservation races ### Adding a new native C++ test diff --git a/tests/native/test_call_generation_guard.cpp b/tests/native/test_call_generation_guard.cpp index a397298d..25231538 100644 --- a/tests/native/test_call_generation_guard.cpp +++ b/tests/native/test_call_generation_guard.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -89,23 +90,34 @@ static void next_reservation_has_distinct_generation() { } static void exactly_one_thread_wins_reservation_race() { - CallGenerationGuard guard; - std::atomic start{false}; - uint32_t first = 0; - uint32_t second = 0; - std::thread a([&] { - while (!start.load(std::memory_order_acquire)) std::this_thread::yield(); - first = guard.tryReserve(); - }); - std::thread b([&] { - while (!start.load(std::memory_order_acquire)) std::this_thread::yield(); - second = guard.tryReserve(); - }); - start.store(true, std::memory_order_release); - a.join(); - b.join(); - EXPECT_TRUE((first == 0) != (second == 0)); - EXPECT_EQ(guard.current(), first != 0 ? first : second); + constexpr int kRaceIterations = 1000; + for (int iteration = 0; iteration < kRaceIterations; ++iteration) { + CallGenerationGuard guard; + std::atomic ready{0}; + std::atomic start{false}; + uint32_t first = 0; + uint32_t second = 0; + + auto reserve = [&](uint32_t& result) { + ready.fetch_add(1, std::memory_order_release); + while (!start.load(std::memory_order_acquire)) { + std::this_thread::yield(); + } + result = guard.tryReserve(); + }; + + std::thread a(reserve, std::ref(first)); + std::thread b(reserve, std::ref(second)); + while (ready.load(std::memory_order_acquire) != 2) { + std::this_thread::yield(); + } + start.store(true, std::memory_order_release); + a.join(); + b.join(); + + EXPECT_TRUE((first == 0) != (second == 0)); + EXPECT_EQ(guard.current(), first != 0 ? first : second); + } } static void stale_token_cannot_release_new_winner() { diff --git a/tests/native/test_call_generation_guard.py b/tests/native/test_call_generation_guard.py index 9852bf91..175faa76 100644 --- a/tests/native/test_call_generation_guard.py +++ b/tests/native/test_call_generation_guard.py @@ -20,6 +20,7 @@ def test_call_generation_guard(tmp_path): "-std=c++17", "-Wall", "-Wextra", + "-Werror", "-pthread", str(TEST_SOURCE), "-o",