The Pedigree Project 0.1
WaitQueue.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 "pedigree/kernel/Log.h"
9#include "pedigree/kernel/Metrics.h"
10#include "pedigree/kernel/process/Mutex.h"
11#include "pedigree/kernel/process/PerProcessorScheduler.h"
12#include "pedigree/kernel/process/Thread.h"
13#include "pedigree/kernel/process/WaitQueue.h"
14#include "pedigree/kernel/processor/Processor.h"
15#include "pedigree/kernel/processor/ProcessorInformation.h"
16#include "pedigree/kernel/utilities/Iterator.h"
17#include "pedigree/kernel/utilities/assert.h"
18
19#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
20WaitQueue::BeforeBlockHook WaitQueue::m_BeforeBlockHook = nullptr;
21#endif
22
23namespace {
24#if PEDIGREE_AFFINITY_TESTS
25Thread* g_ReadyPublicationTarget = nullptr;
26WaitQueue::ReadyPublicationHook g_ReadyPublicationHook = nullptr;
27#endif
28WaitQueue::WakeReason terminalWakeReason(Thread::UnwindType state) {
29 if (state == Thread::Exit) {
30 return WaitQueue::WakeReason::Unwinding;
31 }
32 if (state == Thread::TerminateThread) {
33 return WaitQueue::WakeReason::Terminating;
34 }
35 FATAL("Unknown terminal WaitQueue unwind state.");
36 return WaitQueue::WakeReason::Spurious;
37}
38} // namespace
39
40WaitQueue::Guard::Guard(WaitQueue& queue)
41 : m_Queue(&queue), m_OwnsLock(false), m_pFirstReady(nullptr), m_pLastReady(nullptr) {
42 if (!Processor::guardDeviceHardIrqOperation(DeviceHardIrqOperation::WaitQueueAccess)) {
43 return;
44 }
45
46 m_Queue->m_Lock.acquire();
47 m_OwnsLock = true;
48}
49
50WaitQueue::Guard::Guard(Guard&& other) noexcept
51 : m_Queue(other.m_Queue),
52 m_OwnsLock(other.m_OwnsLock),
53 m_pFirstReady(other.m_pFirstReady),
54 m_pLastReady(other.m_pLastReady) {
55 other.m_Queue = nullptr;
56 other.m_OwnsLock = false;
57 other.m_pFirstReady = nullptr;
58 other.m_pLastReady = nullptr;
59}
60
61WaitQueue::Guard::~Guard() {
62 release();
63}
64
65void WaitQueue::Guard::release() {
66 if (m_Queue && m_OwnsLock) {
67 m_Queue->clearWaitIntentIfEmpty();
68 m_Queue->m_Lock.release();
69 m_OwnsLock = false;
70
71 // Ready publication takes scheduler-owned locks. Keep that ordering
72 // outside the WaitQueue lock because scheduler lifecycle paths can
73 // themselves complete waits.
74 while (m_pFirstReady) {
75 Waiter* waiter = m_pFirstReady;
76 m_pFirstReady = waiter->notificationNext;
77 waiter->notificationNext = nullptr;
78 WaitQueue::publishReady(waiter);
79 }
80 m_pLastReady = nullptr;
81 }
82}
83
85 if (m_OwnsLock) {
86 // Paired with the producer's predicate publication and intent load.
87 // Queue membership alone is too late: the final predicate check must
88 // already be protected against a producer skipping the queue lock.
89 __atomic_store_n(&m_Queue->m_WaitIntent, true, __ATOMIC_SEQ_CST);
90 }
91}
92
93void WaitQueue::Guard::queueSchedulerNotification(Waiter* waiter) {
94 assert(waiter);
95 assert(!waiter->notificationNext);
96 if (m_pLastReady) {
97 m_pLastReady->notificationNext = waiter;
98 } else {
99 m_pFirstReady = waiter;
100 }
101 m_pLastReady = waiter;
102}
103
104WaitQueue::WakeReason WaitQueue::Guard::wait(const Channel& channel, size_t debugState,
105 uintptr_t debugAddress,
106 StackDiscardCleanup onStackDiscard,
107 void* stackDiscardContext) {
108 assert(m_Queue);
109 if (!m_OwnsLock) {
110 return WakeReason::Spurious;
111 }
112 assert(m_OwnsLock);
113 Thread::StackDiscardScope discardScope(onStackDiscard, stackDiscardContext);
114 return m_Queue->wait(*this, nullptr, channel, debugState, debugAddress, false, true);
115}
116
117WaitQueue::WakeReason WaitQueue::Guard::waitForCompletion(const Channel& channel, size_t debugState,
118 uintptr_t debugAddress) {
119 assert(m_Queue);
120 if (!m_OwnsLock) {
121 return WakeReason::Spurious;
122 }
123 assert(m_OwnsLock);
124 return m_Queue->wait(*this, nullptr, channel, debugState, debugAddress, true, true);
125}
126
127WaitQueue::WakeReason WaitQueue::Guard::waitWithoutEventDispatch(const Channel& channel,
128 size_t debugState,
129 uintptr_t debugAddress) {
130 assert(m_Queue);
131 if (!m_OwnsLock) {
132 return WakeReason::Spurious;
133 }
134 assert(m_OwnsLock);
135 return m_Queue->wait(*this, nullptr, channel, debugState, debugAddress, false, false);
136}
137
138WaitQueue::WakeReason WaitQueue::Guard::waitAndUnlock(Mutex& mutex, const Channel& channel,
139 size_t debugState, uintptr_t debugAddress,
140 StackDiscardCleanup onStackDiscard,
141 void* stackDiscardContext) {
142 assert(m_Queue);
143 if (!m_OwnsLock) {
144 return WakeReason::Spurious;
145 }
146 assert(m_OwnsLock);
147 Thread::StackDiscardScope discardScope(onStackDiscard, stackDiscardContext);
148 return m_Queue->wait(*this, &mutex, channel, debugState, debugAddress, false, true);
149}
150
152 const Channel& channel,
153 size_t debugState,
154 uintptr_t debugAddress) {
155 assert(m_Queue);
156 if (!m_OwnsLock) {
157 return WakeReason::Spurious;
158 }
159 assert(m_OwnsLock);
160 return m_Queue->wait(*this, &mutex, channel, debugState, debugAddress, true, true);
161}
162
163bool WaitQueue::Guard::wakeOne(WakeReason reason, const Channel& channel) {
164 assert(m_Queue);
165 if (!m_OwnsLock) {
166 return false;
167 }
168 assert(m_OwnsLock);
169 assert(reason != WakeReason::Waiting);
170 return m_Queue->wakeOneLocked(*this, reason, channel);
171}
172
173size_t WaitQueue::Guard::wakeAll(WakeReason reason, const Channel& channel) {
174 assert(m_Queue);
175 if (!m_OwnsLock) {
176 return 0;
177 }
178 assert(m_OwnsLock);
179 assert(reason != WakeReason::Waiting);
180 return m_Queue->wakeAllLocked(*this, reason, channel);
181}
182
183size_t WaitQueue::Guard::wakeAndRequeue(const Channel& source, size_t wakeCount,
184 const Channel& destination, size_t requeueCount) {
185 assert(m_Queue);
186 if (!m_OwnsLock) {
187 return 0;
188 }
189 assert(m_OwnsLock);
190 return m_Queue->wakeAndRequeueLocked(*this, source, wakeCount, destination, requeueCount);
191}
192
193WaitQueue::WaitQueue()
194 : m_Lock(false),
195 m_pFirstWaiter(nullptr),
196 m_pLastWaiter(nullptr),
197 m_WaiterCount(0),
198 m_WaitIntent(false) {}
199
200WaitQueue::~WaitQueue() {
201 if (waiterCount()) {
202 FATAL("Destroying a WaitQueue with live waiters.");
203 }
204}
205
206WaitQueue::WakeReason WaitQueue::wait(Guard& guard, Mutex* mutex, const Channel& channel,
207 size_t debugState, uintptr_t debugAddress, bool deferTerminal,
208 bool dispatchEvents) {
209 if (mutex && !mutex->isOwnedByCurrentThread()) {
210 FATAL(
211 "WaitQueue::waitAndUnlock requires current-thread mutex "
212 "ownership");
213 }
214
215 Thread* thread = Processor::information().getCurrentThread();
216 assert(thread);
217 const size_t stateLevel = thread->getStateLevel();
218 Waiter& waiter = thread->m_StateLevels[stateLevel].m_Waiter;
219
220 thread->m_Lock.acquire();
221 if (waiter.loadQueue()) {
222 FATAL("Thread attempted to enter two wait queues at once.");
223 }
224 thread->clearTerminalWaitCancelledBeforeBlockUnlocked(stateLevel);
225 const Thread::UnwindType unwindState = thread->getUnwindState();
226 if (unwindState != Thread::Continue && !deferTerminal) {
227 thread->m_Lock.release();
228 guard.release();
229 return terminalWakeReason(unwindState);
230 }
231 waiter.thread = thread;
232 waiter.scheduler = thread->m_pScheduler;
233 assert(waiter.scheduler);
234 waiter.storeChannel(channel);
235 waiter.stateLevel = stateLevel;
236 waiter.storeReason(WakeReason::Waiting);
237 waiter.setQueued(false);
238 waiter.notificationNext = nullptr;
239 waiter.previous = m_pLastWaiter;
240 waiter.next = nullptr;
241 thread->setDebugState(static_cast<Thread::DebugState>(debugState), debugAddress);
242
243 if (m_pLastWaiter) {
244 m_pLastWaiter->next = &waiter;
245 } else {
246 m_pFirstWaiter = &waiter;
247 }
248 m_pLastWaiter = &waiter;
249 ++m_WaiterCount;
250 Metrics::increment(Metrics::WaitQueueWait);
251 waiter.setQueued(true);
252
253 // Publish only after every field and the queue membership are complete.
254 // Debugger snapshots never need to take the target thread's spinlock.
255 waiter.storeQueue(this);
256 thread->m_Lock.release();
257
258 if (mutex) {
259 mutex->release();
260 }
261
262 // The persistent waiter is visible before the queue lock is released.
263 // Any wake after this point changes waiter.reason, even if the thread has
264 // not yet committed the Sleeping state.
265 guard.release();
266
267#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
268 if (m_BeforeBlockHook) {
269 m_BeforeBlockHook(this, thread, channel, debugState);
270 }
271#endif
272
273 Processor::information().getScheduler().blockCurrent();
274
275 m_Lock.acquire();
276 thread->m_Lock.acquire();
277 thread->clearTerminalWaitCancelledBeforeBlockUnlocked(stateLevel);
278 if (waiter.loadQueue() == this) {
279 // Unpublish before the persistent record can be reused.
280 waiter.storeQueue(nullptr);
281 }
282 WakeReason reason = waiter.loadReason();
283 waiter.scheduler = nullptr;
284 thread->setDebugState(Thread::None, 0);
285 thread->m_Lock.release();
286 removeWaiterLocked(&waiter);
287 m_Lock.release();
288
289 if (reason == WakeReason::Waiting) {
290 reason = WakeReason::Spurious;
291 }
292
293 Thread::UnwindType terminalState = thread->getUnwindState();
294 if (terminalState != Thread::Continue && !deferTerminal) {
295 reason = terminalWakeReason(terminalState);
296 }
297
298 // Event handlers may themselves block. Dispatch only after removing the
299 // outer wait record so nested event state gets an independent wait.
300 // An ordinary wake and an event publication can race. Once the outer wait
301 // record is retired, dispatch any event which is now deliverable regardless
302 // of which wake reason won.
303 if (dispatchEvents && (terminalState == Thread::Continue || deferTerminal)) {
304 thread->m_StateLevels[stateLevel].m_bDispatchingWaitEvent = true;
305 Processor::information().getScheduler().checkEventState(0);
306 thread->m_StateLevels[stateLevel].m_bDispatchingWaitEvent = false;
307 }
308
309 // An event handler can itself request process termination. Do not enter a
310 // fresh external-mutex wait after that terminal decision.
311 terminalState = thread->getUnwindState();
312 if (terminalState != Thread::Continue && !deferTerminal) {
313 reason = terminalWakeReason(terminalState);
314 }
315
316 if (mutex) {
317 // Event delivery happens before reacquiring the external mutex, avoiding
318 // re-entry into a handler that needs the same lock. Reacquisition is
319 // itself an ownership barrier: an outer signal/timeout marker is
320 // retained, and terminal propagation happens only after mutex ownership
321 // has been restored.
322 const bool acquired = mutex->acquireForCompletion();
323 if (!acquired) {
324 FATAL("WaitQueue could not reacquire its caller mutex");
325 }
326
327 terminalState = thread->getUnwindState();
328 if (terminalState != Thread::Continue && !deferTerminal) {
329 reason = terminalWakeReason(terminalState);
330 }
331 }
332 return reason;
333}
334
335bool WaitQueue::wakeOne(WakeReason reason, const Channel& channel) {
336 Guard guard(*this);
337 return guard.wakeOne(reason, channel);
338}
339
340size_t WaitQueue::wakeAll(WakeReason reason, const Channel& channel) {
341 Guard guard(*this);
342 return guard.wakeAll(reason, channel);
343}
344
345size_t WaitQueue::wakeAllIfWaiting(WakeReason reason, const Channel& channel) {
346 if (!Processor::guardDeviceHardIrqOperation(DeviceHardIrqOperation::WaitQueueAccess)) {
347 return 0;
348 }
349 if (!__atomic_load_n(&m_WaitIntent, __ATOMIC_SEQ_CST)) {
350 return 0;
351 }
352 return wakeAll(reason, channel);
353}
354
355void WaitQueue::clearWaitIntentIfEmpty() {
356 // The queue lock excludes another waiter publishing intent until unlock.
357 // Clear on both abandoned enrollment and retirement of the last waiter.
358 if (!m_WaiterCount && __atomic_load_n(&m_WaitIntent, __ATOMIC_RELAXED)) {
359 __atomic_store_n(&m_WaitIntent, false, __ATOMIC_SEQ_CST);
360 }
361}
362
363bool WaitQueue::wakeOneLocked(Guard& guard, WakeReason reason, const Channel& channel) {
364 assert(reason == WakeReason::Signalled || reason == WakeReason::Event ||
365 reason == WakeReason::Spurious);
366 for (Waiter* waiter = m_pFirstWaiter; waiter; waiter = waiter->next) {
367 if (!(waiter->channel == channel)) {
368 continue;
369 }
370
371 if (completeWaiter(guard, waiter, reason)) {
372 return true;
373 }
374 }
375
376 return false;
377}
378
379size_t WaitQueue::wakeAllLocked(Guard& guard, WakeReason reason, const Channel& channel) {
380 assert(reason == WakeReason::Signalled || reason == WakeReason::Event ||
381 reason == WakeReason::Spurious);
382 size_t count = 0;
383 for (Waiter* waiter = m_pFirstWaiter; waiter; waiter = waiter->next) {
384 if (!(waiter->channel == channel)) {
385 continue;
386 }
387
388 if (completeWaiter(guard, waiter, reason)) {
389 ++count;
390 }
391 }
392
393 return count;
394}
395
396size_t WaitQueue::wakeAndRequeueLocked(Guard& guard, const Channel& source, size_t wakeCount,
397 const Channel& destination, size_t requeueCount) {
398 size_t woken = 0;
399 size_t requeued = 0;
400 for (Waiter* waiter = m_pFirstWaiter; waiter; waiter = waiter->next) {
401 if (!(waiter->channel == source) || waiter->loadReason() != WakeReason::Waiting) {
402 continue;
403 }
404
405 if (woken < wakeCount) {
406 if (completeWaiter(guard, waiter, WakeReason::Signalled)) {
407 ++woken;
408 }
409 continue;
410 }
411
412 if (requeued < requeueCount) {
413 Thread* thread = waiter->thread;
414 bool moved = false;
415 thread->m_Lock.acquire();
416 if (thread->m_StateLevels[waiter->stateLevel].m_Waiter.loadQueue() == this &&
417 waiter->loadReason() == WakeReason::Waiting && waiter->channel == source) {
418 // Queue-before-thread is the same lock order used by wake and
419 // cancellation. Diagnostics reject a channel being replaced.
420 waiter->storeChannel(destination);
421 moved = true;
422 }
423 thread->m_Lock.release();
424 if (moved) {
425 ++requeued;
426 Metrics::increment(Metrics::WaitQueueRequeue);
427 }
428 }
429 }
430
431 return woken + requeued;
432}
433
434bool WaitQueue::completeWaiter(Guard& guard, Waiter* waiter, WakeReason reason) {
435 Thread* thread = waiter->thread;
436 bool becameReady = false;
437 bool completed = false;
438
439 thread->m_Lock.acquire();
440 if (thread->m_StateLevels[waiter->stateLevel].m_Waiter.loadQueue() == this &&
441 waiter->loadReason() == WakeReason::Waiting) {
442 waiter->storeReason(reason);
443 completed = true;
444 Metrics::increment(Metrics::WaitQueueWake);
445 if (thread->m_Status == Thread::Sleeping) {
446 thread->m_Status = Thread::Ready;
447 __atomic_store_n(&thread->m_ReadyPublicationPending, true, __ATOMIC_RELEASE);
448 becameReady = true;
449 } else {
450 Metrics::increment(Metrics::WaitQueueEarlyWake);
451 }
452 }
453 thread->m_Lock.release();
454
455 if (becameReady) {
456 guard.queueSchedulerNotification(waiter);
457 }
458 return completed;
459}
460
461void WaitQueue::publishReady(Waiter* waiter) {
462 assert(waiter);
463 Thread* thread = waiter->thread;
464 assert(thread);
465#if PEDIGREE_AFFINITY_TESTS
466 const auto hook = __atomic_load_n(&g_ReadyPublicationHook, __ATOMIC_ACQUIRE);
467 if (hook && thread == __atomic_load_n(&g_ReadyPublicationTarget, __ATOMIC_ACQUIRE))
468 hook(thread);
469#endif
470 thread->publishReadyNotification();
471}
472
473#if PEDIGREE_AFFINITY_TESTS
474void WaitQueue::setReadyPublicationHookForTest(Thread* target, ReadyPublicationHook hook) {
475 __atomic_store_n(&g_ReadyPublicationTarget, target, __ATOMIC_RELEASE);
476 __atomic_store_n(&g_ReadyPublicationHook, hook, __ATOMIC_RELEASE);
477}
478#endif
479
480void WaitQueue::removeWaiterLocked(Waiter* waiter) {
481 if (!waiter->isQueued()) {
482 return;
483 }
484
485 if (waiter->previous) {
486 waiter->previous->next = waiter->next;
487 } else {
488 assert(m_pFirstWaiter == waiter);
489 m_pFirstWaiter = waiter->next;
490 }
491
492 if (waiter->next) {
493 waiter->next->previous = waiter->previous;
494 } else {
495 assert(m_pLastWaiter == waiter);
496 m_pLastWaiter = waiter->previous;
497 }
498
499 assert(m_WaiterCount);
500 --m_WaiterCount;
501 Metrics::increment(Metrics::WaitQueueUnqueue);
502 clearWaitIntentIfEmpty();
503 waiter->previous = nullptr;
504 waiter->next = nullptr;
505 waiter->setQueued(false);
506}
507
508void WaitQueue::cancel(Waiter* waiter, WakeReason reason) {
509 Thread* thread = waiter->thread;
510 bool makeReady = false;
511 {
512 Guard guard(*this);
513 if (!guard.m_OwnsLock) {
514 return;
515 }
516 thread->m_Lock.acquire();
517 if (waiter->loadQueue() != this) {
518 thread->m_Lock.release();
519 return;
520 }
521
522 removeWaiterLocked(waiter);
523 Metrics::increment(Metrics::WaitQueueCancel);
524 waiter->storeReason(reason);
525 makeReady = thread->m_Status == Thread::Sleeping;
526
527 if (reason == WakeReason::Terminating) {
528 waiter->storeQueue(nullptr);
529 if (!makeReady && thread->m_Status == Thread::Running) {
530 // blockCurrent() has not committed Sleeping yet. Keep no
531 // linked waiter, but leave an exact one-shot handoff on the
532 // waiter's state level. A nested event may have interrupted
533 // that level between publication and blockCurrent().
535 }
536 }
537 thread->m_Lock.release();
538 }
539
540 bool becameReady = false;
541 if (makeReady) {
542 thread->m_Lock.acquire();
543 if (thread->m_Status == Thread::Sleeping) {
544 thread->m_Status = Thread::Ready;
545 __atomic_store_n(&thread->m_ReadyPublicationPending, true, __ATOMIC_RELEASE);
546 becameReady = true;
547 }
548 thread->m_Lock.release();
549 }
550
551 if (becameReady) {
552 publishReady(waiter);
553 }
554}
555
556size_t WaitQueue::waiterCount() {
557 Guard guard(*this);
558 if (!guard.m_OwnsLock) {
559 return 0;
560 }
561 return m_WaiterCount;
562}
563
564#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
565void WaitQueue::setBeforeBlockHook(BeforeBlockHook hook) {
566 m_BeforeBlockHook = hook;
567}
568#endif
Definition Mutex.h:56
static ProcessorInformation & information()
static bool guardDeviceHardIrqOperation(DeviceHardIrqOperation operation)
Definition Processor.h:585
void release()
Definition Spinlock.cc:168
bool acquire(bool recurse=false, bool safe=true)
Definition Spinlock.cc:36
UnwindType
Definition Thread.h:516
@ Continue
No unwind necessary, carry on as normal.
Definition Thread.h:517
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:519
@ Exit
Exit the owning process at the next safe boundary.
Definition Thread.h:518
void setDebugState(DebugState state, uintptr_t address)
Definition Thread.h:593
volatile Status m_Status
Definition Thread.h:1310
DebugState
Definition Thread.h:177
UnwindType getUnwindState()
Definition Thread.h:535
Spinlock m_Lock
Definition Thread.h:1262
void markTerminalWaitCancelledBeforeBlockUnlocked(size_t level)
Definition Thread.cc:3695
size_t getStateLevel() const
Definition Thread.h:316
void prepareToWait()
Definition WaitQueue.cc:84
MUST_USE_RESULT WakeReason wait(const Channel &channel=Channel(), size_t debugState=0, uintptr_t debugAddress=0, StackDiscardCleanup onStackDiscard=nullptr, void *stackDiscardContext=nullptr)
Definition WaitQueue.cc:104
MUST_USE_RESULT WakeReason waitForCompletion(const Channel &channel=Channel(), size_t debugState=0, uintptr_t debugAddress=0)
Definition WaitQueue.cc:117
size_t wakeAndRequeue(const Channel &source, size_t wakeCount, const Channel &destination, size_t requeueCount)
Definition WaitQueue.cc:183
MUST_USE_RESULT WakeReason waitWithoutEventDispatch(const Channel &channel, size_t debugState, uintptr_t debugAddress)
Definition WaitQueue.cc:127
MUST_USE_RESULT WakeReason waitAndUnlock(Mutex &mutex, const Channel &channel=Channel(), size_t debugState=0, uintptr_t debugAddress=0, StackDiscardCleanup onStackDiscard=nullptr, void *stackDiscardContext=nullptr)
Definition WaitQueue.cc:138
MUST_USE_RESULT WakeReason waitAndUnlockForCompletion(Mutex &mutex, const Channel &channel=Channel(), size_t debugState=0, uintptr_t debugAddress=0)
Definition WaitQueue.cc:151
size_t wakeAllIfWaiting(WakeReason reason=WakeReason::Signalled, const Channel &channel=Channel())
Definition WaitQueue.cc:345
WaitQueue::Waiter m_Waiter
Definition Thread.h:1170
bool m_bDispatchingWaitEvent
Definition Thread.h:1149