The Pedigree Project 0.1
Thread-affinity.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include <config.h>
9
10#if THREADS
11
12#include "pedigree/kernel/LockGuard.h"
13#include "pedigree/kernel/Log.h"
14#include "pedigree/kernel/process/PerProcessorScheduler.h"
15#include "pedigree/kernel/process/Process.h"
16#include "pedigree/kernel/process/Scheduler.h"
17#include "pedigree/kernel/process/SchedulingAlgorithm.h"
18#include "pedigree/kernel/process/TerminationDeferral.h"
19#include "pedigree/kernel/process/Thread.h"
20#include "pedigree/kernel/process/Uninterruptible.h"
21#include "pedigree/kernel/processor/Processor.h"
22#include "pedigree/kernel/processor/ProcessorInformation.h"
23
24#if X86_COMMON && MULTIPROCESSOR
25#include <machine/mach_pc/LocalApic.h>
26#include <machine/mach_pc/Pc.h>
27#endif
28
29#if PEDIGREE_AFFINITY_TESTS
30namespace {
31Thread* g_AffinityCommitTarget = nullptr;
32Thread::AffinityCommitHook g_AffinityCommitHook = nullptr;
33} // namespace
34
35void Thread::setAffinityCommitHookForTest(Thread* target, AffinityCommitHook hook) {
36 __atomic_store_n(&g_AffinityCommitTarget, target, __ATOMIC_RELEASE);
37 __atomic_store_n(&g_AffinityCommitHook, hook, __ATOMIC_RELEASE);
38}
39#endif
40
41ThreadPlacement ThreadPlacement::initialUser() {
42 ThreadPlacement placement;
43 placement.allowed = Scheduler::onlineAffinity();
44 placement.migratable = true;
45 return placement;
46}
47
48ThreadPlacement ThreadPlacement::inherit(Thread& creator) {
49 ThreadPlacement placement;
50 creator.snapshotPlacement(placement);
51 return placement.migratable ? placement : initialUser();
52}
53
54void Thread::initialisePlacement(const ThreadPlacement* placement) {
55 if (placement) {
56 m_Placement = *placement;
57 m_Placement.allowed.intersect(Scheduler::onlineAffinity());
58 if (m_Placement.allowed.empty())
59 FATAL("Thread construction has no allowed online processor.");
60 }
61}
62
63void Thread::snapshotPlacement(ThreadPlacement& placement) {
65 snapshotPlacementLocked(placement);
66}
67
69 placement = m_Placement;
70}
71
72AffinityResult Thread::requestAffinity(const CpuAffinityMask& requested, uint64_t& generation) {
73 CpuAffinityMask effective = requested;
74 effective.intersect(Scheduler::onlineAffinity());
75 generation = 0;
76 if (effective.empty())
77 return AffinityResult::Invalid;
78
79 PerProcessorScheduler* owner = nullptr;
80 Process* parent = m_pParent;
81 bool rejected = false;
82 bool parentPinned = false;
83 bool threadPinned = false;
84 {
85 auto progress = m_AffinityWaiters.acquire();
87 if (m_bShutdown || getUnwindState() != Continue || m_Status == AwaitingJoin ||
88 m_Status == Zombie)
89 return AffinityResult::Terminal;
90 if (!m_Placement.migratable)
91 return AffinityResult::Pinned;
92 if (m_AffinityPending) {
93 generation = m_AffinityGeneration;
94 return AffinityResult::Busy;
95 }
96 owner = getScheduler();
97 assert(owner);
98 if (m_AffinityGeneration == ~uint64_t(0))
99 return AffinityResult::Invalid;
100
101 if (!m_AffinityWorkQueued) {
102 if (!parent->beginExternalLease())
103 return AffinityResult::Terminal;
104 parentPinned = true;
105 threadPinned = beginExternalLease();
106 rejected = !threadPinned;
107 }
108 if (!rejected) {
109 generation = ++m_AffinityGeneration;
110 m_RequestedAffinity = effective;
111 m_AffinityPending = true;
112 __atomic_store_n(&m_AffinityReturnPending, static_cast<size_t>(1), __ATOMIC_RELEASE);
113 if (!m_AffinityWorkQueued) {
114 m_AffinityWorkQueued = true;
115 if (!owner->enqueueAffinity(this)) {
116 m_AffinityWorkQueued = false;
117 m_AffinityPending = false;
118 __atomic_store_n(&m_AffinityReturnPending, static_cast<size_t>(0), __ATOMIC_RELEASE);
119 generation = 0;
120 rejected = true;
121 }
122 }
123 }
124 }
125 if (rejected) {
126 if (threadPinned)
128 if (parentPinned)
129 parent->endExternalLease();
130 return AffinityResult::Terminal;
131 }
132 owner->prompt();
133 return AffinityResult::Success;
134}
135
136AffinityResult Thread::waitAffinity(uint64_t generation) {
137 if (!generation)
138 return AffinityResult::Invalid;
139 TerminationDeferral lifetime;
140 while (true) {
141 auto progress = m_AffinityWaiters.acquire();
142 {
144 if (generation > m_AffinityGeneration)
145 return AffinityResult::Invalid;
146 if (generation <= m_AffinityCompleted)
147 return AffinityResult::Success;
148 if (!m_AffinityPending)
149 return AffinityResult::Terminal;
150 }
151 const auto reason = progress.waitForCompletion(WaitQueue::Channel(this), Thread::ProcessWait,
152 reinterpret_cast<uintptr_t>(this));
153 (void)reason;
154 }
155}
156
157AffinityResult Thread::completeAffinityAtSafePoint(bool* waited) {
158 if (!affinityWorkPending()) {
160 if (waited)
161 *waited = false;
162 return AffinityResult::Success;
163 }
165 if (waited)
166 *waited = false;
167 if (Processor::information().getCurrentThread() != this)
168 FATAL("Affinity gate entered for a non-current Thread.");
169
170 // Only this audited wait continuation may move. Suppress callbacks across
171 // its entire lifetime, including preemption before waiter publication.
172 Uninterruptible gateScope;
173 while (true) {
174 bool rejected = false;
175 bool parentPinned = false;
176 bool threadPinned = false;
177 bool finished = false;
178 AffinityResult result = AffinityResult::Success;
179 Process* parent = m_pParent;
180 PerProcessorScheduler* owner = nullptr;
181 {
182 auto progress = m_AffinityWaiters.acquire();
183 {
185 if (!m_AffinityGatePending) {
186 owner = getScheduler();
187 assert(owner);
188 if (m_bShutdown || getUnwindState() != Continue) {
189 result = AffinityResult::Terminal;
190 finished = true;
191 } else if (m_Placement.allowed.contains(owner->logicalCpu())) {
192 finished = true;
193 }
194 if (!finished && !m_AffinityWorkQueued) {
195 parentPinned = parent->beginExternalLease();
196 if (parentPinned)
197 threadPinned = beginExternalLease();
198 rejected = !threadPinned;
199 }
200 if (!finished && !rejected) {
201 m_AffinityGatePending = true;
202 __atomic_store_n(&m_AffinityReturnPending, static_cast<size_t>(1), __ATOMIC_RELEASE);
203 if (!m_AffinityWorkQueued) {
204 m_AffinityWorkQueued = true;
205 if (!owner->enqueueAffinity(this)) {
206 m_AffinityWorkQueued = false;
207 m_AffinityGatePending = false;
208 rejected = true;
209 }
210 }
211 }
212 } else {
213 owner = getScheduler();
214 }
215 }
216 if (!finished && !rejected) {
217 owner->prompt();
218 // The guard prevents completion from racing enrollment. Terminal
219 // wakeups cannot abandon an admitted node; the source worker cancels
220 // it and owns the final lease release.
221 if (waited)
222 *waited = true;
223 const auto reason =
224 m_AffinityWaiters.wait(progress, nullptr, WaitQueue::Channel(this), ProcessWait,
225 reinterpret_cast<uintptr_t>(this), true, false);
226 (void)reason;
227 }
228 }
229 if (finished) {
231 return result;
232 }
233 if (rejected) {
234 if (threadPinned)
236 if (parentPinned)
237 parent->endExternalLease();
239 return AffinityResult::Terminal;
240 }
242 }
243}
244
245void Thread::publishReadyNotification() {
246 PerProcessorScheduler* owner = nullptr;
247 {
249 assert(m_ReadyPublicationPending);
250 __atomic_store_n(&m_ReadyPublicationPending, false, __ATOMIC_RELEASE);
251 if (m_Status == Ready) {
252 owner = getScheduler();
253 assert(owner);
254 owner->m_pSchedulingAlgorithm->threadStatusChanged(this);
255 }
256 }
257 if (owner) {
258 owner->prompt(true);
259 }
260}
261
262bool PerProcessorScheduler::enqueueAffinity(Thread* thread, bool accepted) {
263 LockGuard<Spinlock> guard(m_AffinityQueueLock);
264 if (!m_AffinityAdmissionOpen && !accepted)
265 return false;
266 assert(!thread->m_AffinityNext);
267 if (m_AffinityTail)
268 m_AffinityTail->m_AffinityNext = thread;
269 else
270 m_AffinityHead = thread;
271 m_AffinityTail = thread;
272 m_AffinityRequests += 1;
273 ringIrqWorkDoorbell(m_TimeAccountingWorkerWake);
274 return true;
275}
276
277void PerProcessorScheduler::prompt(bool readyPublication) {
278 m_ReschedulePending = 1;
279 if (readyPublication) {
280 armLocalQuantumIfNeeded();
281 }
282#if X86_COMMON && MULTIPROCESSOR
283 if (this != &Processor::information().getScheduler() &&
284 m_RemotePromptPending.compareAndSwap(0, 1)) {
285 ProcessorInformation* information = Processor::informationAt(m_LogicalCpu);
286 assert(information);
287 const uint8_t apicId = information->localApicId();
288 if (!Pc::instance().getLocalApic().interProcessorInterrupt(
289 apicId, IPI_RESCHEDULE_VECTOR, LocalApic::deliveryModeFixed, true, false)) {
290 FATAL_NOLOCK("Scheduler remote prompt failed after ready publication.");
291 }
292 }
293#endif
294}
295
296void PerProcessorScheduler::drainAffinityRequests() {
297 assert(this == &Processor::information().getScheduler());
298 // Both delayed and immediate constructors may still be waiting for their
299 // add worker to install the first context. Let that worker make progress.
300 const size_t batch = m_AffinityRequests.value();
301 for (size_t i = 0; i < batch; ++i) {
302 Thread* thread = nullptr;
303 {
304 LockGuard<Spinlock> guard(m_AffinityQueueLock);
305 thread = m_AffinityHead;
306 if (!thread)
307 break;
308 m_AffinityHead = thread->m_AffinityNext;
309 if (!m_AffinityHead)
310 m_AffinityTail = nullptr;
311 thread->m_AffinityNext = nullptr;
312 }
313#if PEDIGREE_AFFINITY_TESTS
314 const auto hook = __atomic_load_n(&g_AffinityCommitHook, __ATOMIC_ACQUIRE);
315 if (hook && thread == __atomic_load_n(&g_AffinityCommitTarget, __ATOMIC_ACQUIRE))
316 hook(thread);
317#endif
318 bool retry = false;
319 PerProcessorScheduler* destination = this;
320 {
321 auto progress = thread->m_AffinityWaiters.acquire();
322 {
323 LockGuard<Spinlock> guard(thread->m_Lock);
324 assert(thread->m_AffinityWorkQueued && thread->getScheduler() == this);
325 assert(thread != Processor::information().getCurrentThread());
326 const bool terminal = m_StopTimeAccountingWorker.value() || thread->m_bShutdown ||
327 thread->getUnwindState() != Thread::Continue ||
328 thread->m_Status == Thread::AwaitingJoin ||
329 thread->m_Status == Thread::Zombie;
330 if (!terminal && !thread->m_HasSchedulerContext) {
331 retry = true;
332 } else {
333 if (!terminal) {
334 assert(thread->m_Status != Thread::Running);
335 if (thread->m_AffinityPending) {
336 thread->m_Placement.allowed = thread->m_RequestedAffinity;
337 thread->m_AffinityCompleted = thread->m_AffinityGeneration;
338 }
339 if (thread->m_AffinityGatePending) {
340 if (!thread->m_Placement.allowed.contains(m_LogicalCpu)) {
341 for (size_t cpu = 0; cpu < CpuAffinityMask::MaximumCpus; ++cpu) {
342 if (thread->m_Placement.allowed.contains(cpu)) {
343 destination = Scheduler::schedulerForCpu(cpu);
344 break;
345 }
346 }
347 }
348 assert(destination);
350 Scheduler::instance().rebindThread(thread, *destination);
351 destination->m_pSchedulingAlgorithm->threadStatusChanged(thread);
352 }
353 }
354 thread->m_AffinityPending = false;
355 thread->m_AffinityGatePending = false;
356 thread->m_AffinityWorkQueued = false;
357 // A policy acknowledgement can precede the clean return gate.
358 // Keep that gate armed until the continuation is on an allowed CPU.
359 const bool needsMove =
360 !terminal && !thread->m_Placement.allowed.contains(destination->logicalCpu());
361 __atomic_store_n(&thread->m_AffinityReturnPending, static_cast<size_t>(needsMove),
362 __ATOMIC_RELEASE);
363 }
364 }
365 if (!retry)
366 progress.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(thread));
367 }
368 m_AffinityRequests -= 1;
369 if (retry) {
370 const bool queued = enqueueAffinity(thread, true);
371 assert(queued);
372 } else {
373 destination->prompt();
374 // The node owns a lease independently of the syscall's caller. The
375 // final release may destroy a terminal detached target.
376 Process* parent = thread->m_pParent;
377 thread->endExternalLease();
378 parent->endExternalLease();
379 }
380 }
381}
382
383#endif
SchedulingAlgorithm * m_pSchedulingAlgorithm
void endExternalLease()
Definition Process.cc:1210
bool beginExternalLease()
Definition Process.cc:1200
static ProcessorInformation & information()
static ProcessorInformation * informationAt(size_t cpu)
Definition Processor.cc:39
static void setInterrupts(bool bEnable)
static Scheduler & instance()
Definition Scheduler.h:96
virtual void removeThread(Thread *pThread)=0
virtual void threadStatusChanged(Thread *pThread)=0
@ Continue
No unwind necessary, carry on as normal.
Definition Thread.h:517
volatile Status m_Status
Definition Thread.h:1310
AffinityResult waitAffinity(uint64_t generation)
bool m_bShutdown
Definition Thread.h:1339
Process * m_pParent
Definition Thread.h:1180
void snapshotPlacementLocked(ThreadPlacement &placement) const
UnwindType getUnwindState()
Definition Thread.h:535
void endExternalLease()
Definition Thread.cc:2862
Spinlock m_Lock
Definition Thread.h:1262
class PerProcessorScheduler * getScheduler() const
Definition Thread.h:929
AffinityResult requestAffinity(const CpuAffinityMask &mask, uint64_t &generation)
bool beginExternalLease()
Definition Thread.cc:2852
AffinityResult completeAffinityAtSafePoint(bool *waited=nullptr)