4#if PEDIGREE_AFFINITY_TESTS && THREADS
5#include "pedigree/kernel/Atomic.h"
6#include "pedigree/kernel/LockGuard.h"
7#include "pedigree/kernel/Log.h"
8#include "pedigree/kernel/process/Process.h"
9#include "pedigree/kernel/process/Scheduler.h"
10#include "pedigree/kernel/process/Semaphore.h"
11#include "pedigree/kernel/process/Thread.h"
12#include "pedigree/kernel/process/WaitQueue.h"
13#include "pedigree/kernel/processor/Processor.h"
14#include "pedigree/kernel/processor/ProcessorInformation.h"
15#include "pedigree/kernel/time/Time.h"
18bool check(
bool condition,
const char* detail) {
20 ERROR(
"AFFINITY-CORE: FAIL " << detail);
25 for (
size_t i = 0; i < CpuAffinityMask::WordCount; ++i) {
26 if (left.words[i] != right.words[i])
34 Semaphore entered{0}, firstDone{0}, secondDone{0};
39 Atomic<size_t> firstReason{
static_cast<size_t>(WaitQueue::WakeReason::Waiting)};
40 Atomic<size_t> secondReason{
static_cast<size_t>(WaitQueue::WakeReason::Waiting)};
45int waitEntry(
void* parameter) {
46 auto& state = *
static_cast<WaitState*
>(parameter);
47 state.entered.release();
48 WaitQueue::WakeReason reason;
50 auto guard = state.queue.acquire();
53 state.firstReason =
static_cast<size_t>(reason);
55 if (reason == WaitQueue::WakeReason::Signalled) {
64 state.firstDone.release();
65 if (!state.repeat || reason != WaitQueue::WakeReason::Signalled || !state.gatePassed)
68 auto guard = state.queue.acquire();
71 state.secondReason =
static_cast<size_t>(reason);
74 state.secondDone.release();
78Thread* startWaiter(WaitState& state,
size_t cpu) {
80 placement.allowed.set(cpu);
81 placement.migratable =
true;
84 new Thread(current->
getParent(), waitEntry, &state,
nullptr,
false,
false,
true, &placement);
87 peer->setPriority(current->getPriority());
90 if (!peer->joinForCompletion())
91 FATAL(
"AFFINITY-CORE: failed startup could not be joined");
97bool waitForSleeping(
Thread& peer, WaitState& state) {
98 const Time::Timestamp start = Time::getTicks();
99 while (Time::getTicks() - start < 5 * Time::Multiplier::Second) {
104 sleeping = peer.
getStatus() == Thread::Sleeping;
106 if (sleeping && state.queue.waiterCount() == 1)
113void retireWaiter(
Thread* peer, WaitState& state,
bool completed) {
116 state.queue.wakeAll();
119 FATAL(
"AFFINITY-CORE: owned waiter could not be joined");
124 WaitState* state =
nullptr;
132ReadyContext* g_ReadyContext =
nullptr;
134void beforeReadyPublication(
Thread* peer) {
135 auto* context = __atomic_load_n(&g_ReadyContext, __ATOMIC_ACQUIRE);
136 if (!context || peer != context->target)
139 if (context->calls != 1) {
140 context->passed =
false;
145 if (!context->passed)
148 uint64_t generation = 0;
149 const AffinityResult requested = peer->
requestAffinity(context->destination, generation);
151 check(requested == AffinityResult::Success,
"request while ready publication is held");
152 if (requested == AffinityResult::Success) {
153 context->passed &= check(peer->
waitAffinity(generation) == AffinityResult::Success,
154 "affinity completion while wake is held");
157 peer->snapshotPlacement(placement);
163 context->passed &= check(correctOwner && sameMask(placement.allowed, context->destination),
164 "mask acknowledged without moving the blocked kernel continuation");
169 for (
size_t i = 0; i < 8; ++i)
171 context->passed &= check(context->state->returns == 0 && context->state->queue.waiterCount() == 1,
172 "waiter executed or was reused before wake publication retired");
175bool heldWake(
size_t sourceCpu,
size_t destinationCpu) {
178 Thread* peer = startWaiter(state, sourceCpu);
179 if (!check(peer !=
nullptr,
"wake waiter allocation/start"))
181 bool passed = check(state.entered.acquireForCompletion(1, 5) && waitForSleeping(*peer, state),
182 "first wait enrollment");
183 ReadyContext context;
184 context.target = peer;
185 context.state = &state;
186 context.destination.set(destinationCpu);
187 context.moving = sourceCpu != destinationCpu;
193 __atomic_store_n(&g_ReadyContext, &context, __ATOMIC_RELEASE);
194 WaitQueue::setReadyPublicationHookForTest(peer, beforeReadyPublication);
197 WaitQueue::setReadyPublicationHookForTest(
nullptr,
nullptr);
198 __atomic_store_n(&g_ReadyContext,
static_cast<ReadyContext*
>(
nullptr), __ATOMIC_RELEASE);
199 passed &= check(woke && context.calls == 1 && context.passed,
"held wake admission");
201 bool completed =
false;
204 check(state.firstDone.acquireForCompletion(1, 5) && state.returns == 1 &&
205 state.beforeGateCpu == sourceCpu && state.gatePassed &&
206 state.firstCpu == destinationCpu &&
207 state.firstReason ==
static_cast<size_t>(WaitQueue::WakeReason::Signalled) &&
208 waitForSleeping(*peer, state),
209 "source wait return, clean gate migration and second wait enrollment");
213 completed = woke && state.secondDone.acquireForCompletion(1, 5);
215 check(completed && state.returns == 2 && state.secondCpu == destinationCpu &&
216 state.secondReason ==
static_cast<size_t>(WaitQueue::WakeReason::Signalled) &&
217 state.queue.waiterCount() == 0,
218 "waiter reuse, single execution and final wake reason");
221 retireWaiter(peer, state, completed);
223 NOTICE(
"AFFINITY-CORE: PASS extracted wake " << (context.moving ?
"migration" :
"same CPU"));
227struct CancelContext {
233CancelContext* g_CancelContext =
nullptr;
235void cancelBeforeCommit(
Thread* peer) {
236 auto* context = __atomic_load_n(&g_CancelContext, __ATOMIC_ACQUIRE);
237 if (!context || peer != context->target)
244bool cancelPending(
size_t sourceCpu,
size_t destinationCpu) {
246 Thread* peer = startWaiter(state, sourceCpu);
247 if (!check(peer !=
nullptr,
"cancel waiter allocation/start"))
249 bool passed = check(state.entered.acquireForCompletion(1, 5) && waitForSleeping(*peer, state),
250 "cancel wait enrollment");
251 CancelContext context;
252 context.target = peer;
254 destination.set(destinationCpu);
256 __atomic_store_n(&g_CancelContext, &context, __ATOMIC_RELEASE);
257 Thread::setAffinityCommitHookForTest(peer, cancelBeforeCommit);
258 uint64_t generation = 0;
259 const AffinityResult requested = peer->
requestAffinity(destination, generation);
260 passed &= check(requested == AffinityResult::Success,
"pending request admission");
261 if (requested == AffinityResult::Success) {
262 passed &= check(peer->
waitAffinity(generation) == AffinityResult::Terminal,
263 "pending generation terminal completion");
265 Thread::setAffinityCommitHookForTest(
nullptr,
nullptr);
266 __atomic_store_n(&g_CancelContext,
static_cast<CancelContext*
>(
nullptr), __ATOMIC_RELEASE);
267 passed &= check(context.calls == 1 && context.safeContext,
"terminal precommit hook");
269 retireWaiter(peer, state,
false);
271 NOTICE(
"AFFINITY-CORE: PASS pending terminal cancellation");
275int forbiddenEntry(
void* parameter) {
276 *
static_cast<bool*
>(parameter) =
true;
280bool pinnedAdmission() {
281 bool entered =
false;
284 new Thread(current->
getParent(), forbiddenEntry, &entered,
nullptr,
false,
true,
true);
285 if (!check(peer !=
nullptr,
"pinned waiter allocation"))
288 peer->snapshotPlacement(before);
289 uint64_t generation = 0;
290 const AffinityResult result = peer->
requestAffinity(before.allowed, generation);
291 peer->snapshotPlacement(after);
292 bool passed = check(!before.migratable && result == AffinityResult::Pinned && !after.migratable &&
293 sameMask(before.allowed, after.allowed),
294 "fixed placement admission changed state");
297 FATAL(
"AFFINITY-CORE: unstarted pinned waiter could not be joined");
298 passed &= check(!entered,
"unstarted pinned waiter executed");
300 NOTICE(
"AFFINITY-CORE: PASS pinned admission");
305EXPORTED_PUBLIC
bool runAffinityRegressions() {
306 NOTICE(
"AFFINITY-CORE: BEGIN");
309 "fixture entry context"))
313 if (!check(online.contains(source),
"source CPU online"))
315 size_t destination = source;
316 for (
size_t cpu = 0; cpu < CpuAffinityMask::MaximumCpus; ++cpu) {
317 if (cpu != source && online.contains(cpu)) {
322 bool passed = pinnedAdmission() && heldWake(source, source) && cancelPending(source, source);
323 if (destination != source) {
324 passed = passed && heldWake(source, destination) && cancelPending(source, destination);
326 NOTICE(
"AFFINITY-CORE: SKIP cross-CPU migration (one online CPU)");
329 NOTICE(
"AFFINITY-CORE: END PASS");
static bool getInterrupts()
static ProcessorInformation & information()
static bool inDeviceHardIrq()
static void setInterrupts(bool bEnable)
static Scheduler & instance()
void setUnwindState(UnwindType ut)
@ TerminateThread
Exit only this thread during Process exit.
AffinityResult waitAffinity(uint64_t generation)
Process * getParent() const
class PerProcessorScheduler * getScheduler() const
AffinityResult requestAffinity(const CpuAffinityMask &mask, uint64_t &generation)