Skip to content

Commit 708316c

Browse files
authored
Create test hook mechanism for XTaskQueue (#948)
* Create retail test hook mechanism * Fix for xcode
1 parent b33ebce commit 708316c

5 files changed

Lines changed: 273 additions & 204 deletions

File tree

Source/Task/TaskQueue.cpp

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,9 @@
33

44
#include "pch.h"
55
#include "referenced_ptr.h"
6+
#include "XTaskQueuePriv.h"
67
#include "TaskQueueP.h"
78
#include "TaskQueueImpl.h"
8-
#include "XTaskQueuePriv.h"
9-
10-
#ifdef HC_UNITTEST_API
11-
TestBarrier* g_testBarrier = nullptr;
12-
#endif
139

1410
//
1511
// ApiRefs tracks global refcounts for all APIs. It is used to identify memory leaks
@@ -1111,32 +1107,27 @@ bool TaskQueuePortImpl::ScheduleNextPendingCallback(
11111107
uint64_t noDueTime = UINT64_MAX;
11121108
if (m_timerDue.compare_exchange_strong(dueTime, noDueTime))
11131109
{
1114-
#ifdef HC_UNITTEST_API
1115-
// Test hook: two-phase barrier reproduces the timer race
1116-
// that results in lost delayed task wakes.
1117-
if (g_testBarrier != nullptr)
1118-
{
1119-
{
1120-
std::lock_guard<std::mutex> lk(g_testBarrier->mtx);
1121-
g_testBarrier->phase1_ready = true;
1122-
}
1123-
g_testBarrier->cv.notify_all();
1124-
1125-
std::unique_lock<std::mutex> lk(g_testBarrier->mtx);
1126-
g_testBarrier->cv.wait_for(lk, std::chrono::seconds(5),
1127-
[&] { return g_testBarrier->phase2_ready; });
1128-
}
1129-
#endif
11301110
// Bug fix: ScheduleNextPendingCallback timer race results
1131-
// in lost delayed task wakes.
1132-
//
1111+
// in lost delayed task wakes. Don't cancel the timer here
1112+
// as another scheduled callback could have been added.
11331113
// The CAS above is sufficient: the timer has already fired
11341114
// (call site 1: SubmitPendingCallback) or was already
11351115
// canceled (call site 2: CancelPendingEntries). A Cancel()
11361116
// here raced with concurrent QueueItem/Start calls on other
11371117
// threads, permanently stranding entries in m_pendingList.
11381118
// See VerifyDelayedCallbackTimerRaceOnManualQueue for full
1139-
// analysis.
1119+
// analysis. The test hook here allows unit tests to verify
1120+
// there is no race.
1121+
m_attachedContexts.Visit([&](ITaskQueuePortContext* portContext)
1122+
{
1123+
auto hooks = portContext->GetQueue()->GetTestHooks();
1124+
if (hooks != nullptr) {
1125+
hooks->NextPendingCallbackScheduled(
1126+
portContext->GetType(),
1127+
dueTime,
1128+
noDueTime);
1129+
}
1130+
});
11401131
}
11411132
}
11421133

@@ -1277,14 +1268,14 @@ void TaskQueuePortImpl::SignalTerminations()
12771268
entry->portContext->AddRef();
12781269

12791270
entry->callback(entry->callbackContext);
1280-
1281-
// Release portContext after callback completes
1282-
entry->portContext->Release();
1283-
1271+
12841272
{
12851273
std::lock_guard<std::mutex> lock(m_terminationLock);
12861274
m_terminationList->free_node(address);
12871275
}
1276+
1277+
// Release portContext after callback completes
1278+
entry->portContext->Release();
12881279
delete entry;
12891280
}
12901281
}
@@ -2349,3 +2340,17 @@ STDAPI_(bool) XTaskQueueUninitialize(
23492340

23502341
return ApiRefs::WaitZeroRefs(timeoutMilliseconds);
23512342
}
2343+
2344+
/// <summary>
2345+
/// Sets or clears test hooks on a task queue.
2346+
/// </summary>
2347+
STDAPI XTaskQueueSetTestHooks(
2348+
_In_ XTaskQueueHandle queue,
2349+
_In_ XTaskQueueTestHooks* hooks
2350+
) noexcept
2351+
{
2352+
referenced_ptr<ITaskQueue> aq(GetQueue(queue));
2353+
RETURN_HR_IF(E_GAMERUNTIME_INVALID_HANDLE, aq == nullptr);
2354+
aq->SetTestHooks(hooks);
2355+
return S_OK;
2356+
}

Source/Task/TaskQueueImpl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,8 @@ class TaskQueueImpl : public Api<ApiId::TaskQueue, ITaskQueue>
402402
_In_ XTaskQueuePortHandle completionPort);
403403

404404
XTaskQueueHandle __stdcall GetHandle() override { return &m_header; }
405+
XTaskQueueTestHooks* __stdcall GetTestHooks() override { return m_testHooks; }
406+
void __stdcall SetTestHooks(_In_ XTaskQueueTestHooks* testHooks) override { m_testHooks = testHooks; }
405407

406408
HRESULT __stdcall GetPortContext(
407409
_In_ XTaskQueuePort port,
@@ -473,6 +475,7 @@ class TaskQueueImpl : public Api<ApiId::TaskQueue, ITaskQueue>
473475
TerminationData m_termination;
474476
TaskQueuePortContextImpl m_work;
475477
TaskQueuePortContextImpl m_completion;
478+
XTaskQueueTestHooks* m_testHooks = nullptr;
476479

477480
#ifdef SUSPEND_API
478481
SuspendResumeHandler m_suspendHandler;

Source/Task/TaskQueueP.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ struct ITaskQueuePortContext : IApi
125125
struct ITaskQueue : IApi
126126
{
127127
virtual XTaskQueueHandle __stdcall GetHandle() = 0;
128+
virtual XTaskQueueTestHooks* __stdcall GetTestHooks() = 0;
129+
virtual void __stdcall SetTestHooks(_In_ XTaskQueueTestHooks* testHooks) = 0;
128130

129131
virtual HRESULT __stdcall GetPortContext(
130132
_In_ XTaskQueuePort port,

Source/Task/XTaskQueuePriv.h

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@
55

66
#include "XTaskQueue.h"
77

8-
#ifdef HC_UNITTEST_API
9-
#include <mutex>
10-
#include <condition_variable>
11-
#endif
12-
138
//----------------------------------------------------------------//
149
//
1510
// These APIs should be reserved for driving unit test harnesses.
@@ -45,6 +40,34 @@ STDAPI_(void) XTaskQueueResumeTermination(
4540
_In_ XTaskQueueHandle queue
4641
) noexcept;
4742

43+
/// <summary>
44+
/// This structure can be passed as a pointer to the task queue so unit tests
45+
/// can hook into its behavior. Some race conditions are very difficult to get
46+
/// to happen naturally so sometimes a hook is needed. A pointer to this
47+
/// structure will be stored on the task queue. It is up to the test to ensure
48+
/// the structure lifetime exceeds that of the task queue under test.
49+
/// </summary>
50+
struct XTaskQueueTestHooks
51+
{
52+
virtual void NextPendingCallbackScheduled(
53+
XTaskQueuePort port,
54+
uint64_t lastDueTime,
55+
uint64_t nextDueTime)
56+
{
57+
UNREFERENCED_PARAMETER(port);
58+
UNREFERENCED_PARAMETER(lastDueTime);
59+
UNREFERENCED_PARAMETER(nextDueTime);
60+
}
61+
};
62+
63+
/// <summary>
64+
/// Sets or clears test hooks on a task queue.
65+
/// </summary>
66+
STDAPI XTaskQueueSetTestHooks(
67+
_In_ XTaskQueueHandle queue,
68+
_In_ XTaskQueueTestHooks* hooks
69+
) noexcept;
70+
4871
//----------------------------------------------------------------//
4972
//
5073
// These APIs are internal to the runtime
@@ -125,20 +148,3 @@ STDAPI_(bool) XTaskQueueGetCurrentProcessTaskQueueWithOptions(
125148
STDAPI_(bool) XTaskQueueUninitialize(
126149
_In_ uint32_t timeoutMilliseconds = 0
127150
) noexcept;
128-
129-
#ifdef HC_UNITTEST_API
130-
131-
// Two-phase barrier for deterministic reproduction of the
132-
// ScheduleNextPendingCallback timer race that results in lost delayed
133-
// task wakes. See VerifyDelayedCallbackTimerRaceOnManualQueue.
134-
struct TestBarrier
135-
{
136-
std::mutex mtx;
137-
std::condition_variable cv;
138-
bool phase1_ready = false; // threadpool -> test: "CAS done"
139-
bool phase2_ready = false; // test -> threadpool: "Start done"
140-
};
141-
142-
extern TestBarrier* g_testBarrier;
143-
144-
#endif

0 commit comments

Comments
 (0)