test: strengthen call admission contention coverage

This commit is contained in:
torlando-agent[bot]
2026-08-01 14:14:56 +00:00
parent 63e0fd0380
commit 3351cab398
4 changed files with 34 additions and 20 deletions
+3 -2
View File
@@ -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;
+1 -1
View File
@@ -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
+29 -17
View File
@@ -3,6 +3,7 @@
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <stdexcept>
#include <thread>
@@ -89,23 +90,34 @@ static void next_reservation_has_distinct_generation() {
}
static void exactly_one_thread_wins_reservation_race() {
CallGenerationGuard guard;
std::atomic<bool> 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<unsigned> ready{0};
std::atomic<bool> 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() {
@@ -20,6 +20,7 @@ def test_call_generation_guard(tmp_path):
"-std=c++17",
"-Wall",
"-Wextra",
"-Werror",
"-pthread",
str(TEST_SOURCE),
"-o",