Skip to content

Commit 63bc00d

Browse files
bghgaryCopilot
andcommitted
Add optional testing hooks for blocking_concurrent_queue
Add a per-instance before-wait callback to blocking_concurrent_queue and expose it through dispatcher and manual_dispatcher. The callback is invoked while holding the queue mutex, right before condition_variable::wait(). This enables deterministic testing of race conditions involving the condition variable (e.g. lost wakeups during shutdown). All changes are behind #ifdef ARCANA_TESTING_HOOKS and have zero cost when not enabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 53e5b05 commit 63bc00d

File tree

2 files changed

+32
-0
lines changed

2 files changed

+32
-0
lines changed

Source/Shared/arcana/threading/blocking_concurrent_queue.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
#include <queue>
88
#include <condition_variable>
99

10+
#ifdef ARCANA_TESTING_HOOKS
11+
#include <functional>
12+
#endif
13+
1014
namespace arcana
1115
{
1216
template<typename T, size_t max_size = std::numeric_limits<size_t>::max()>
@@ -86,6 +90,14 @@ namespace arcana
8690
m_dataReady.notify_all();
8791
}
8892

93+
#ifdef ARCANA_TESTING_HOOKS
94+
void set_before_wait_callback(std::function<void()> callback)
95+
{
96+
std::unique_lock<std::mutex> lock{ m_mutex };
97+
m_beforeWaitCallback = std::move(callback);
98+
}
99+
#endif
100+
89101
private:
90102
bool internal_pop(T& dest, const cancellation& cancel, bool block)
91103
{
@@ -95,6 +107,9 @@ namespace arcana
95107
{
96108
while (!cancel.cancelled() && m_data.empty())
97109
{
110+
#ifdef ARCANA_TESTING_HOOKS
111+
if (m_beforeWaitCallback) m_beforeWaitCallback();
112+
#endif
98113
m_dataReady.wait(lock);
99114
}
100115
}
@@ -116,6 +131,9 @@ namespace arcana
116131
{
117132
while (!cancel.cancelled() && m_data.empty())
118133
{
134+
#ifdef ARCANA_TESTING_HOOKS
135+
if (m_beforeWaitCallback) m_beforeWaitCallback();
136+
#endif
119137
m_dataReady.wait(lock);
120138
}
121139
}
@@ -135,5 +153,9 @@ namespace arcana
135153
std::queue<T> m_data;
136154
mutable std::mutex m_mutex;
137155
std::condition_variable m_dataReady;
156+
157+
#ifdef ARCANA_TESTING_HOOKS
158+
std::function<void()> m_beforeWaitCallback;
159+
#endif
138160
};
139161
}

Source/Shared/arcana/threading/dispatcher.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ namespace arcana
4545
dispatcher& operator=(dispatcher&&) = default;
4646
dispatcher(dispatcher&&) = default;
4747

48+
#ifdef ARCANA_TESTING_HOOKS
49+
void set_before_wait_callback(std::function<void()> callback)
50+
{
51+
m_work.set_before_wait_callback(std::move(callback));
52+
}
53+
#endif
54+
4855
bool tick(const cancellation& token)
4956
{
5057
return internal_tick(token, false);
@@ -114,6 +121,9 @@ namespace arcana
114121
using dispatcher<WorkSize>::clear;
115122
using dispatcher<WorkSize>::set_affinity;
116123
using dispatcher<WorkSize>::tick;
124+
#ifdef ARCANA_TESTING_HOOKS
125+
using dispatcher<WorkSize>::set_before_wait_callback;
126+
#endif
117127
};
118128

119129
template<size_t WorkSize>

0 commit comments

Comments
 (0)