From cf25e345da9c7b95efa69fe00dc3eb46d6426bc5 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Thu, 2 Apr 2026 09:26:02 -0700 Subject: [PATCH 1/3] Add optional testing hook for blocking_concurrent_queue Add a global before-wait callback invoked while holding the queue mutex, right before condition_variable::wait(). This enables deterministic testing of lost-wakeup race conditions in code that uses the queue. Behind #ifdef ARCANA_TESTING_HOOKS -- zero cost when not enabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../threading/blocking_concurrent_queue.h | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Source/Shared/arcana/threading/blocking_concurrent_queue.h b/Source/Shared/arcana/threading/blocking_concurrent_queue.h index 61f6c0a..7b0fcd1 100644 --- a/Source/Shared/arcana/threading/blocking_concurrent_queue.h +++ b/Source/Shared/arcana/threading/blocking_concurrent_queue.h @@ -3,12 +3,37 @@ #include "cancellation.h" #include +#include +#include #include #include -#include +#include +#include + +#ifdef ARCANA_TESTING_HOOKS +#include +#endif namespace arcana { +#ifdef ARCANA_TESTING_HOOKS + namespace detail + { + // Callback invoked while holding the queue mutex, right before + // condition_variable::wait(). Sleeping here widens the race window + // for lost-wakeup bugs: code that notifies without the mutex will + // lose the signal, while code that coordinates through push() (which + // acquires the mutex) will block until wait() is entered and then + // deliver the notification correctly. + inline std::function beforeWaitCallback{[]() {}}; + } + + inline void set_before_wait_callback(std::function callback) + { + detail::beforeWaitCallback = std::move(callback); + } +#endif + template::max()> class blocking_concurrent_queue { @@ -95,6 +120,9 @@ namespace arcana { while (!cancel.cancelled() && m_data.empty()) { +#ifdef ARCANA_TESTING_HOOKS + detail::beforeWaitCallback(); +#endif m_dataReady.wait(lock); } } @@ -116,6 +144,9 @@ namespace arcana { while (!cancel.cancelled() && m_data.empty()) { +#ifdef ARCANA_TESTING_HOOKS + detail::beforeWaitCallback(); +#endif m_dataReady.wait(lock); } } From dc1232757480d1f2d0ed74ba1d03b1d08ddac985 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Thu, 2 Apr 2026 16:57:16 -0700 Subject: [PATCH 2/3] Add doc comment to set_before_wait_callback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Source/Shared/arcana/threading/blocking_concurrent_queue.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Shared/arcana/threading/blocking_concurrent_queue.h b/Source/Shared/arcana/threading/blocking_concurrent_queue.h index 7b0fcd1..8b54538 100644 --- a/Source/Shared/arcana/threading/blocking_concurrent_queue.h +++ b/Source/Shared/arcana/threading/blocking_concurrent_queue.h @@ -28,6 +28,9 @@ namespace arcana inline std::function beforeWaitCallback{[]() {}}; } + // Set a callback to be invoked while holding the queue mutex, right before + // condition_variable::wait(). This is used for deterministic testing of + // lost-wakeup race conditions. Pass an empty lambda [](){} to reset. inline void set_before_wait_callback(std::function callback) { detail::beforeWaitCallback = std::move(callback); From b37ebe9506c872e6bef07769cbcf9f7e794410ff Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Thu, 2 Apr 2026 16:57:42 -0700 Subject: [PATCH 3/3] Remove redundant internal comment, keep only public API doc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Source/Shared/arcana/threading/blocking_concurrent_queue.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Source/Shared/arcana/threading/blocking_concurrent_queue.h b/Source/Shared/arcana/threading/blocking_concurrent_queue.h index 8b54538..ca6b6a7 100644 --- a/Source/Shared/arcana/threading/blocking_concurrent_queue.h +++ b/Source/Shared/arcana/threading/blocking_concurrent_queue.h @@ -19,12 +19,6 @@ namespace arcana #ifdef ARCANA_TESTING_HOOKS namespace detail { - // Callback invoked while holding the queue mutex, right before - // condition_variable::wait(). Sleeping here widens the race window - // for lost-wakeup bugs: code that notifies without the mutex will - // lose the signal, while code that coordinates through push() (which - // acquires the mutex) will block until wait() is entered and then - // deliver the notification correctly. inline std::function beforeWaitCallback{[]() {}}; }