The Pedigree Project 0.1
wait-regressions.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 !PEDIGREE_HOSTED_CORE_SMOKE
11#include "modules/system/usb/UsbPnP.h"
12#endif
13#include "pedigree/kernel/Atomic.h"
14#include "pedigree/kernel/Log.h"
15#include "pedigree/kernel/process/ConditionVariable.h"
16#include "pedigree/kernel/process/Event.h"
17#include "pedigree/kernel/process/Ipc.h"
18#include "pedigree/kernel/process/Mutex.h"
19#include "pedigree/kernel/process/PerProcessorScheduler.h"
20#include "pedigree/kernel/process/Process.h"
21#include "pedigree/kernel/process/Scheduler.h"
22#include "pedigree/kernel/process/Semaphore.h"
23#include "pedigree/kernel/process/SignalEvent.h"
24#include "pedigree/kernel/process/TerminationDeferral.h"
25#include "pedigree/kernel/process/Thread.h"
26#include "pedigree/kernel/process/Uninterruptible.h"
27#include "pedigree/kernel/process/WaitQueue.h"
28#include "pedigree/kernel/processor/Processor.h"
29#include "pedigree/kernel/time/Time.h"
31
32bool runHostedRequestQueueRegressions();
33bool runHostedAtaPioRegressions();
34bool runHostedBufferRegressions();
35bool runHostedElfValidationRegressions();
36bool runHostedLifetimeLeaseRegressions();
37bool runHostedMutexRegressions();
38bool runHostedSpinlockRegressions();
39bool runHostedPageFaultRegressions();
40bool runHostedPayloadPageRegressions();
41bool runHostedPrimitiveRegressions(Thread* thread);
42bool runHostedRingBufferRegressions();
43bool runHostedProcessExitRegressions();
44bool runHostedPs2MousePacketRegressions();
45bool runHostedSignalInterruptionRegressions(Thread* thread);
46bool runHostedTimerRegressions(Thread* thread);
47#if PEDIGREE_HOSTED_CORE_SMOKE
48bool runHostedStoragePageRegressions();
49bool runHostedCacheDiscardRegressions();
50bool runHostedCacheTimerRegressions();
51#endif
52#if !PEDIGREE_HOSTED_CORE_SMOKE
53bool runHostedSchedulerRegressions();
54bool runHostedCacheRegressions();
55bool runHostedCdiIrqRegressions();
56bool runHostedInputRegressions();
57bool runHostedInterruptManagerRegressions();
58EXPORTED_PUBLIC bool runHostedFatSectorRegressions();
59EXPORTED_PUBLIC bool runHostedRawFsContractRegressions();
60EXPORTED_PUBLIC bool runHostedUnixDatagramRegressions();
61bool runHostedLogRegressions();
62bool runNetworkFilterConcurrencyRegressions();
63bool runVfsCallbackLifetimeRegressions();
64bool runHostedPipeRegressions();
65bool runHostedPs2ControllerRegressions();
66bool runHostedPs2MouseRegressions();
67bool runHostedSyscallRegressions();
68bool runHostedUsbCallbackDeliveryRegressions();
69bool runHostedUsbHcdPortChangeRegressions();
70bool runHostedUsbTransferLifecycleRegressions();
71EXPORTED_PUBLIC bool runHostedUsbSyncOwnershipRegression();
72EXPORTED_PUBLIC bool runHostedUsbInterruptOwnershipRegression();
73EXPORTED_PUBLIC bool runHostedUsbContainerOwnershipRegression();
74EXPORTED_PUBLIC bool runHostedUsbEndpointHaltRegressions();
75EXPORTED_PUBLIC bool runHostedUsbBotRegressions();
76EXPORTED_PUBLIC bool runHostedHidReportRegressions();
77EXPORTED_PUBLIC bool runHostedScsiWriteRegressions();
78#endif
79
80namespace {
81Atomic<size_t> g_ImmediateHookCalls(0);
82Atomic<size_t> g_ImmediateHookFailures(0);
83WaitQueue* g_ImmediateQueue = nullptr;
84WaitQueue::Channel g_ImmediateChannel;
85Thread* g_ImmediateWaiter = nullptr;
86Thread* g_ImmediateExpectedWaiter = nullptr;
87
88struct TerminalCancelBeforeBlockContext {
89 TerminalCancelBeforeBlockContext(Thread* waiter)
90 : queue(),
91 firstChannel(&queue, 0x5445524d),
92 secondChannel(&queue, 0x52455553),
94 phase(0),
95 hookCalls(0),
96 hookFailures(0),
97 cancellations(0),
98 wakeups(0) {}
99
100 WaitQueue queue;
101 WaitQueue::Channel firstChannel;
102 WaitQueue::Channel secondChannel;
103 Thread* waiter;
104 Atomic<size_t> phase;
105 Atomic<size_t> hookCalls;
106 Atomic<size_t> hookFailures;
107 Atomic<size_t> cancellations;
108 Atomic<size_t> wakeups;
109};
110
111TerminalCancelBeforeBlockContext* g_TerminalCancelBeforeBlockContext = nullptr;
112
113struct NestedTerminalShutdownContext {
114 NestedTerminalShutdownContext()
115 : queue(),
116 channel(&queue, 0x4e455354),
117 waiter(nullptr),
118 entered(0),
119 hookCalls(0),
120 hookFailures(0),
121 pushed(0),
122 shutdowns(0),
123 popped(0),
124 finished(0),
125 terminalResult(0),
126 waiterUnlinked(0) {}
127
128 WaitQueue queue;
129 WaitQueue::Channel channel;
130 Thread* waiter;
131 Atomic<size_t> entered;
132 Atomic<size_t> hookCalls;
133 Atomic<size_t> hookFailures;
134 Atomic<size_t> pushed;
135 Atomic<size_t> shutdowns;
136 Atomic<size_t> popped;
137 Atomic<size_t> finished;
138 Atomic<size_t> terminalResult;
139 Atomic<size_t> waiterUnlinked;
140};
141
142NestedTerminalShutdownContext* g_NestedTerminalShutdownContext = nullptr;
143
144struct SemaphoreHookContext {
145 SemaphoreHookContext(Semaphore* semaphore, Thread* expectedWaiter)
146 : semaphore(semaphore),
147 expectedWaiter(expectedWaiter),
148 waiter(nullptr),
149 hookCalls(0),
150 hookFailures(0) {}
151
152 Semaphore* semaphore;
153 Thread* expectedWaiter;
154 Thread* waiter;
155 Atomic<size_t> hookCalls;
156 Atomic<size_t> hookFailures;
157};
158
159SemaphoreHookContext* g_SemaphoreContext = nullptr;
160
161class StackDestructionCanary {
162 public:
163 explicit StackDestructionCanary(Atomic<size_t>* destructions) : m_Destructions(destructions) {}
164
165 ~StackDestructionCanary() {
166 *m_Destructions += 1;
167 }
168
169 private:
170 Atomic<size_t>* m_Destructions;
171};
172
173struct TerminalCancelContext {
174 TerminalCancelContext()
175 : waiter(nullptr), entered(0), resumed(0), terminalResult(0), destructed(0) {}
176
177 WaitQueue queue;
178 Thread* waiter;
179 Atomic<size_t> entered;
180 Atomic<size_t> resumed;
181 Atomic<size_t> terminalResult;
182 Atomic<size_t> destructed;
183};
184
185class PublishedWaitOwner;
186
187struct PublishedWaitDiscardContext {
188 PublishedWaitDiscardContext()
189 : owner(nullptr),
190 waiter(nullptr),
191 entered(0),
192 hookCalls(0),
193 hookFailures(0),
194 published(0),
195 cleanupCalls(0),
196 ownerDestructed(0),
197 waitersAtDestruction(0),
198 returned(0),
199 stackDestructed(0) {}
200
201 PublishedWaitOwner* owner;
202 Thread* waiter;
203 Atomic<size_t> entered;
204 Atomic<size_t> hookCalls;
205 Atomic<size_t> hookFailures;
206 Atomic<size_t> published;
207 Atomic<size_t> cleanupCalls;
208 Atomic<size_t> ownerDestructed;
209 Atomic<size_t> waitersAtDestruction;
210 Atomic<size_t> returned;
211 Atomic<size_t> stackDestructed;
212};
213
214class PublishedWaitOwner {
215 public:
216 explicit PublishedWaitOwner(PublishedWaitDiscardContext* context) : m_Context(context) {}
217
218 ~PublishedWaitOwner() {
219 m_Context->waitersAtDestruction += queue.waiterCount();
220 m_Context->ownerDestructed += 1;
221 }
222
223 WaitQueue queue;
224
225 private:
226 PublishedWaitDiscardContext* m_Context;
227};
228
229constexpr uintptr_t PublishedWaitDiscardChannel = 0x44495343;
230PublishedWaitDiscardContext* g_PublishedWaitDiscardContext = nullptr;
231
232void destroyPublishedWaitOwner(void* parameter) {
233 PublishedWaitDiscardContext* context = reinterpret_cast<PublishedWaitDiscardContext*>(parameter);
234 context->cleanupCalls += 1;
235 PublishedWaitOwner* owner = context->owner;
236 context->owner = nullptr;
237 delete owner;
238}
239
240struct ConditionVariableHookContext {
241 ConditionVariableHookContext(ConditionVariable* condition, Mutex* mutex, Thread* expectedWaiter)
242 : condition(condition),
243 mutex(mutex),
244 expectedWaiter(expectedWaiter),
245 waiter(nullptr),
246 hookCalls(0),
247 hookFailures(0),
248 acquiredMutex(0) {}
249
250 ConditionVariable* condition;
251 Mutex* mutex;
252 Thread* expectedWaiter;
253 Thread* waiter;
254 Atomic<size_t> hookCalls;
255 Atomic<size_t> hookFailures;
256 Atomic<size_t> acquiredMutex;
257};
258
259ConditionVariableHookContext* g_ConditionVariableContext = nullptr;
260
261struct CompletionConditionContext {
262 CompletionConditionContext()
263 : waiter(nullptr),
264 gate(0),
265 entered(0),
266 returned(0),
267 predicate(0),
268 terminalReported(0),
269 terminalMutexHeld(0),
270 signalPreserved(0) {}
271
272 Thread* waiter;
273 Mutex mutex;
274 ConditionVariable condition;
275 Semaphore gate;
276 Atomic<size_t> entered;
277 Atomic<size_t> returned;
278 Atomic<size_t> predicate;
279 Atomic<size_t> terminalReported;
280 Atomic<size_t> terminalMutexHeld;
281 Atomic<size_t> signalPreserved;
282};
283
284struct ContendedConditionContext {
285 enum Trigger {
286 Signal,
287 Timeout,
288 };
289
290 ContendedConditionContext(Thread* waiter, Trigger trigger)
291 : waiter(waiter),
292 trigger(trigger),
293 holderAcquired(0),
294 conditionWaitPublished(0),
295 triggerSent(0),
296 reacquireWaitPublished(0),
297 holderReleased(0) {}
298
299 Thread* waiter;
300 Trigger trigger;
301 Mutex mutex;
302 ConditionVariable condition;
303 Atomic<size_t> holderAcquired;
304 Atomic<size_t> conditionWaitPublished;
305 Atomic<size_t> triggerSent;
306 Atomic<size_t> reacquireWaitPublished;
307 Atomic<size_t> holderReleased;
308};
309
310struct TerminalConditionContext {
311 TerminalConditionContext()
312 : waiter(nullptr),
313 entered(0),
314 waitReturned(0),
315 terminalResult(0),
316 mutexHeldOnReturn(0),
317 destructed(0) {}
318
319 Thread* waiter;
320 Mutex mutex;
321 ConditionVariable condition;
322 Atomic<size_t> entered;
323 Atomic<size_t> waitReturned;
324 Atomic<size_t> terminalResult;
325 Atomic<size_t> mutexHeldOnReturn;
326 Atomic<size_t> destructed;
327};
328
329Atomic<size_t> g_ContendedSignalHandlerCalls(0);
330
331struct UnlikelyLockHookContext {
332 enum HeldMode {
333 WriterHeld,
334 ReaderHeld,
335 };
336
337 UnlikelyLockHookContext(UnlikelyLock* lock, HeldMode heldMode)
338 : lock(lock),
339 thread(nullptr),
340 heldMode(heldMode),
341 hookCalls(0),
342 hookFailures(0),
343 acquired(0),
344 returned(0),
345 signalPreserved(0) {}
346
347 UnlikelyLock* lock;
348 Thread* thread;
349 HeldMode heldMode;
350 Atomic<size_t> hookCalls;
351 Atomic<size_t> hookFailures;
352 Atomic<size_t> acquired;
353 Atomic<size_t> returned;
354 Atomic<size_t> signalPreserved;
355};
356
357UnlikelyLockHookContext* g_UnlikelyLockContext = nullptr;
358
359struct UnlikelyLockFairnessContext {
360 explicit UnlikelyLockFairnessContext(UnlikelyLock* lock)
361 : lock(lock), admissionSequence(0), writerOrder(0), writerReturned(0), readersEntered(0) {}
362
363 UnlikelyLock* lock;
364 Atomic<size_t> admissionSequence;
365 Atomic<size_t> writerOrder;
366 Atomic<size_t> writerReturned;
367 Atomic<size_t> readersEntered;
368};
369
370bool waitForDebugState(Thread* thread, size_t debugState) {
371 const Time::Timestamp deadline = Time::getTicks() + (2 * Time::Multiplier::Second);
372 while (Time::getTicks() < deadline) {
373 Thread::WaitDebugInfo wait = {};
374 uintptr_t debugAddress = 0;
375 if (thread->getWaitDebugInfo(wait) && wait.queued &&
376 thread->getDebugState(debugAddress) == debugState) {
377 return true;
378 }
380 }
381 return false;
382}
383
384void contendedConditionSignalHandler(size_t) {
385 g_ContendedSignalHandlerCalls += 1;
386}
387
388int holdConditionMutex(void* parameter) {
389 ContendedConditionContext* context = reinterpret_cast<ContendedConditionContext*>(parameter);
390 if (!context->mutex.acquireForCompletion()) {
391 return 1;
392 }
393 context->holderAcquired += 1;
394
395 if (waitForDebugState(context->waiter, Thread::CondWait)) {
396 context->conditionWaitPublished += 1;
397 }
398
399 if (context->trigger == ContendedConditionContext::Signal) {
400 SignalEvent* event = new SignalEvent(
401 reinterpret_cast<uintptr_t>(&contendedConditionSignalHandler), 10, ~0UL, 0, true, true);
402 if (context->waiter->sendEvent(event)) {
403 context->triggerSent += 1;
404 } else {
405 delete event;
406 }
407 }
408
409 const Time::Timestamp holdTime = context->trigger == ContendedConditionContext::Signal
410 ? 50 * Time::Multiplier::Millisecond
411 : 200 * Time::Multiplier::Millisecond;
412 Time::delay(holdTime);
413 if (waitForDebugState(context->waiter, Thread::SemWait)) {
414 context->reacquireWaitPublished += 1;
415 }
416
417 context->mutex.release();
418 context->holderReleased += 1;
419 return 0;
420}
421
422int waitForTerminalCondition(void* parameter) {
423 TerminalConditionContext* context = reinterpret_cast<TerminalConditionContext*>(parameter);
424 StackDestructionCanary stackCanary(&context->destructed);
425 if (!context->mutex.acquireForCompletion()) {
426 return 1;
427 }
428 context->entered += 1;
429
430 ConditionVariable::Error error = ConditionVariable::NoError;
431 const bool waited = context->condition.wait(context->mutex, error);
432 context->terminalResult =
433 !waited && error == ConditionVariable::TerminationDeferred ? static_cast<size_t>(1) : 0;
434 context->mutexHeldOnReturn = context->mutex.isOwnedByCurrentThread() ? 1 : 0;
435 context->waitReturned += 1;
436
437 if (context->mutexHeldOnReturn) {
438 context->mutex.release();
439 }
440 return context->terminalResult ? 0 : 1;
441}
442
443struct ProcessSuspendContext {
444 enum Mode {
445 ObserveBlock,
446 ResumeBeforeBlock,
447 EventThenResume,
448 };
449
450 ProcessSuspendContext(Process* process, Mode mode)
451 : process(process),
452 mode(mode),
453 waiter(nullptr),
454 hookCalls(0),
455 hookFailures(0),
456 phase(0),
457 eventWakes(0),
458 resumes(0) {}
459
460 Process* process;
461 Mode mode;
462 Thread* waiter;
463 Atomic<size_t> hookCalls;
464 Atomic<size_t> hookFailures;
465 Atomic<size_t> phase;
466 Atomic<size_t> eventWakes;
467 Atomic<size_t> resumes;
468};
469
470ProcessSuspendContext* g_ProcessSuspendContext = nullptr;
471
472struct ProcessStopReturnGateContext {
473 ProcessStopReturnGateContext()
474 : release(0), entered(0), returned(0), crossings(0), terminalReturns(0), failures(0) {}
475
476 Semaphore release;
477 Atomic<size_t> entered;
478 Atomic<size_t> returned;
479 Atomic<size_t> crossings;
480 Atomic<size_t> terminalReturns;
481 Atomic<size_t> failures;
482};
483
484struct ProcessStopOwnerContext {
485 explicit ProcessStopOwnerContext(Process* process) : process(process), entered(0), returned(0) {}
486
487 Process* process;
488 Atomic<size_t> entered;
489 Atomic<size_t> returned;
490};
491
492Atomic<size_t> g_StoppedUserHandlerCalls(0);
493
494void stoppedUserReturnHandler(size_t) {
495 g_StoppedUserHandlerCalls += 1;
496}
497
498class StoppedUserReturnEvent : public Event {
499 public:
500 StoppedUserReturnEvent()
501 : Event(reinterpret_cast<uintptr_t>(&stoppedUserReturnHandler), false, ~0UL,
502 HandlerPrivilege::User) {}
503
504 size_t serialize(uint8_t*) override {
505 return 0;
506 }
507
508 size_t getNumber() override {
509 return 0x53545550;
510 }
511};
512
513void stopCurrentProcessFromEvent(size_t) {
514 Thread* current = Processor::information().getCurrentThread();
515 current->getParent()->suspend();
516}
517
518class StopCurrentProcessEvent : public Event {
519 public:
520 StopCurrentProcessEvent()
521 : Event(reinterpret_cast<uintptr_t>(&stopCurrentProcessFromEvent), false, ~0UL,
522 HandlerPrivilege::Kernel) {}
523
524 size_t serialize(uint8_t*) override {
525 return 0;
526 }
527
528 size_t getNumber() override {
529 return 0x53544f50;
530 }
531};
532
533struct PrequeuedStopOwnerContext {
534 explicit PrequeuedStopOwnerContext(Process* process)
535 : process(process),
536 entered(0),
537 eventsQueued(0),
538 returned(0),
539 terminalReturns(0),
540 failures(0) {}
541
542 Process* process;
543 Atomic<size_t> entered;
544 Atomic<size_t> eventsQueued;
545 Atomic<size_t> returned;
546 Atomic<size_t> terminalReturns;
547 Atomic<size_t> failures;
548};
549
550Atomic<size_t> g_ImmediateThreadExits(0);
551
552struct JoinPropagationContext {
553 explicit JoinPropagationContext(Semaphore* gate)
554 : gate(gate),
555 target(nullptr),
556 targetEntered(0),
557 joinReturned(0),
558 joinSucceeded(0),
559 joinerDestructed(0) {}
560
561 Semaphore* gate;
562 Thread* target;
563 Atomic<size_t> targetEntered;
564 Atomic<size_t> joinReturned;
565 Atomic<size_t> joinSucceeded;
566 Atomic<size_t> joinerDestructed;
567};
568
569struct JoinPublicationContext {
570 JoinPublicationContext()
571 : targetGate(0),
572 target(nullptr),
573 joiner(nullptr),
574 targetEntered(0),
575 hookCalls(0),
576 hookFailures(0),
577 detachRejected(0),
578 targetReapableInHook(0),
579 joinReturned(0),
580 joinSucceeded(0) {}
581
582 Semaphore targetGate;
583 Thread* target;
584 Thread* joiner;
585 Atomic<size_t> targetEntered;
586 Atomic<size_t> hookCalls;
587 Atomic<size_t> hookFailures;
588 Atomic<size_t> detachRejected;
589 Atomic<size_t> targetReapableInHook;
590 Atomic<size_t> joinReturned;
591 Atomic<size_t> joinSucceeded;
592};
593
594JoinPublicationContext* g_JoinPublicationContext = nullptr;
595
596void immediateWakeHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
597 size_t debugState) {
598 if (thread != g_ImmediateExpectedWaiter) {
599 return;
600 }
601 g_ImmediateHookCalls += 1;
602 g_ImmediateWaiter = thread;
603 if (queue != g_ImmediateQueue || !(channel == g_ImmediateChannel) ||
604 debugState != Thread::EventWait) {
605 g_ImmediateHookFailures += 1;
606 return;
607 }
608
609 Thread::WaitDebugInfo wait = {};
610 thread->getLock().acquire();
611 const bool waitVisible = thread->getWaitDebugInfo(wait);
612 thread->getLock().release();
613 if (!waitVisible || wait.queue != queue || wait.channelOwner != channel.owner ||
614 wait.channelValue != channel.value || wait.reason != WaitQueue::WakeReason::Waiting ||
615 wait.stateLevel != thread->getStateLevel() || !wait.queued) {
616 g_ImmediateHookFailures += 1;
617 return;
618 }
619
620 if (!queue->wakeOne(WaitQueue::WakeReason::Signalled, channel)) {
621 g_ImmediateHookFailures += 1;
622 }
623}
624
625void discardPublishedWaitHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
626 size_t debugState) {
627 PublishedWaitDiscardContext* context = g_PublishedWaitDiscardContext;
628 if (!context || thread != context->waiter) {
629 return;
630 }
631
632 context->hookCalls += 1;
633 PublishedWaitOwner* owner = context->owner;
634 Thread::WaitDebugInfo wait = {};
635 if (context->hookCalls != 1 || !owner || queue != &owner->queue || channel.owner != owner ||
636 channel.value != PublishedWaitDiscardChannel || debugState != Thread::EventWait ||
637 !thread->getWaitDebugInfo(wait) || wait.queue != queue || !wait.queued ||
638 wait.reason != WaitQueue::WakeReason::Waiting || queue->waiterCount() != 1) {
639 context->hookFailures += 1;
640 } else {
641 context->published += 1;
642 }
643
645 PerProcessorScheduler::StackDiscardReason::HostedRegression);
646}
647
648void terminalCancelBeforeBlockHook(WaitQueue* queue, Thread* thread,
649 const WaitQueue::Channel& channel, size_t debugState) {
650 TerminalCancelBeforeBlockContext* context = g_TerminalCancelBeforeBlockContext;
651 if (!context || thread != context->waiter) {
652 return;
653 }
654
655 context->hookCalls += 1;
656 Thread::WaitDebugInfo wait = {};
657 if (queue != &context->queue || debugState != Thread::EventWait ||
658 !thread->getWaitDebugInfo(wait) || wait.queue != queue || !wait.queued ||
659 wait.reason != WaitQueue::WakeReason::Waiting) {
660 context->hookFailures += 1;
661 return;
662 }
663
664 if (context->phase == 0) {
665 if (!(channel == context->firstChannel)) {
666 context->hookFailures += 1;
667 return;
668 }
669
671 context->cancellations += 1;
672 context->phase = 1;
673 return;
674 }
675
676 if (context->phase != 1 || !(channel == context->secondChannel) ||
677 !context->queue.wakeOne(WaitQueue::WakeReason::Signalled, channel)) {
678 context->hookFailures += 1;
679 return;
680 }
681
682 context->wakeups += 1;
683 context->phase = 2;
684}
685
686void nestedTerminalShutdownBeforeBlockHook(WaitQueue* queue, Thread* thread,
687 const WaitQueue::Channel& channel, size_t debugState) {
688 NestedTerminalShutdownContext* context = g_NestedTerminalShutdownContext;
689 if (!context || thread != context->waiter) {
690 return;
691 }
692
693 context->hookCalls += 1;
694 Thread::WaitDebugInfo wait = {};
695 if (context->hookCalls != 1 || queue != &context->queue || !(channel == context->channel) ||
696 debugState != Thread::EventWait || !thread->getWaitDebugInfo(wait) || wait.queue != queue ||
697 !wait.queued || wait.reason != WaitQueue::WakeReason::Waiting) {
698 context->hookFailures += 1;
699 return;
700 }
701
702 SchedulerState* outerState = thread->pushState();
703 if (!outerState) {
704 context->hookFailures += 1;
705 return;
706 }
707 context->pushed += 1;
708
709 // shutdown() is the real all-state-level cancellation path. It returns
710 // here, so the outer wait must consume the handoff recorded at its own
711 // lower state level after this nested state unwinds.
712 thread->shutdown();
713 context->shutdowns += 1;
714 thread->popState(false);
715 context->popped += 1;
716}
717
718void semaphoreReleaseHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
719 size_t debugState) {
720 SemaphoreHookContext* context = g_SemaphoreContext;
721 if (!context) {
722 return;
723 }
724
725 if (thread != context->expectedWaiter) {
726 return;
727 }
728
729 context->hookCalls += 1;
730 context->waiter = thread;
731 if (!queue || channel.owner != context->semaphore || channel.value ||
732 debugState != Thread::SemWait) {
733 context->hookFailures += 1;
734 }
735
736 context->semaphore->release();
737}
738
739void semaphoreReleaseBeforeIntentHook(Semaphore* semaphore) {
740 SemaphoreHookContext* context = g_SemaphoreContext;
741 if (!context || semaphore != context->semaphore ||
742 Processor::information().getCurrentThread() != context->expectedWaiter) {
743 return;
744 }
745 context->hookCalls += 1;
746 if (semaphore->getValue() != 0) {
747 context->hookFailures += 1;
748 }
749 semaphore->release();
750}
751
752void semaphorePartialReleaseHook(WaitQueue* queue, Thread* thread,
753 const WaitQueue::Channel& channel, size_t debugState) {
754 SemaphoreHookContext* context = g_SemaphoreContext;
755 if (!context || thread != context->expectedWaiter || channel.owner != context->semaphore) {
756 return;
757 }
758 const size_t phase = context->hookCalls;
759 context->hookCalls += 1;
760 context->waiter = thread;
761 Thread::WaitDebugInfo wait = {};
762 if (phase >= 4 || !queue || channel.value || debugState != Thread::SemWait ||
763 !thread->getWaitDebugInfo(wait) || wait.queue != queue || !wait.queued ||
764 wait.reason != WaitQueue::WakeReason::Waiting || queue->waiterCount() != 1 ||
765 context->semaphore->getValue() != static_cast<ssize_t>(phase % 2)) {
766 context->hookFailures += 1;
767 }
768 context->semaphore->release(phase >= 4 ? 3 : (phase % 2 ? 2 : 1));
769}
770
771int waitForTerminalCancellation(void* parameter) {
772 TerminalCancelContext* context = reinterpret_cast<TerminalCancelContext*>(parameter);
773 StackDestructionCanary stackCanary(&context->destructed);
774
775 context->entered += 1;
776 auto guard = context->queue.acquire();
777 const WaitQueue::WakeReason reason =
778 guard.wait(WaitQueue::Channel(&context->queue), Thread::EventWait,
779 reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
780
781 context->terminalResult = reason == WaitQueue::WakeReason::Terminating ? 1 : 0;
782 context->resumed += 1;
783 return context->terminalResult ? 0 : 1;
784}
785
786int abandonPublishedWait(void* parameter) {
787 PublishedWaitDiscardContext* context = reinterpret_cast<PublishedWaitDiscardContext*>(parameter);
788 PublishedWaitOwner* owner = new PublishedWaitOwner(context);
789 context->owner = owner;
790
791 Thread::StackDiscardScope discardScope(destroyPublishedWaitOwner, context);
792 StackDestructionCanary stackCanary(&context->stackDestructed);
793 context->entered += 1;
794 {
795 auto guard = owner->queue.acquire();
796 guard.wait(WaitQueue::Channel(owner, PublishedWaitDiscardChannel), Thread::EventWait,
797 reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
798 }
799
800 context->returned += 1;
801 discardScope.disarm();
802 destroyPublishedWaitOwner(context);
803 return 1;
804}
805
806void conditionVariableSignalHook(WaitQueue* queue, Thread* thread,
807 const WaitQueue::Channel& channel, size_t debugState) {
808 ConditionVariableHookContext* context = g_ConditionVariableContext;
809 if (!context) {
810 return;
811 }
812
813 if (thread != context->expectedWaiter) {
814 return;
815 }
816
817 context->hookCalls += 1;
818 context->waiter = thread;
819 const bool mutexAcquired = context->mutex->tryAcquire();
820 if (!queue || channel.owner || channel.value || debugState != Thread::CondWait ||
821 !mutexAcquired) {
822 context->hookFailures += 1;
823 } else {
824 context->acquiredMutex += 1;
825 }
826
827 // Signal while the caller's mutex is released. If that invariant failed,
828 // release its stale hold too so the regression reports instead of hanging.
829 context->condition->signal();
830 context->mutex->release();
831}
832
833void unlikelyLockAdmissionHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
834 size_t debugState) {
835 UnlikelyLockHookContext* context = g_UnlikelyLockContext;
836 if (!context || thread != context->thread) {
837 return;
838 }
839
840 context->hookCalls += 1;
841 if (!queue || channel.owner || channel.value || debugState != Thread::CondWait) {
842 context->hookFailures += 1;
843 return;
844 }
845
846 if (context->heldMode == UnlikelyLockHookContext::WriterHeld) {
847 context->lock->release();
848 } else {
849 context->lock->leave();
850 }
851}
852
853int waitForTerminalCompletionCondition(void* parameter) {
854 CompletionConditionContext* context = reinterpret_cast<CompletionConditionContext*>(parameter);
855 TerminationDeferral terminationDeferral;
856 if (!context->gate.acquireForCompletion()) {
857 return 1;
858 }
859
860 if (!context->mutex.acquireForCompletion()) {
861 return 1;
862 }
863 context->entered += 1;
864 Thread* thread = context->waiter;
865
866 ConditionVariable::Error terminalError = ConditionVariable::NoError;
867 const bool terminalWait = context->condition.wait(context->mutex, terminalError);
868 context->terminalReported =
869 !terminalWait && terminalError == ConditionVariable::TerminationDeferred ? 1 : 0;
870 context->terminalMutexHeld = context->mutex.isOwnedByCurrentThread() ? 1 : 0;
871 if (!context->terminalMutexHeld) {
872 return 1;
873 }
874
875 thread->setInterruptionReason(Thread::InterruptedBySignal);
876 while (!context->predicate) {
877 context->condition.waitForCompletion(context->mutex);
878 }
879 context->signalPreserved = thread->getInterruptionReason() == Thread::InterruptedBySignal ? 1 : 0;
880 thread->clearInterruption();
882 context->mutex.release();
883 context->returned += 1;
884 return 0;
885}
886
887int enterUnlikelyLock(void* parameter) {
888 UnlikelyLockHookContext* context = reinterpret_cast<UnlikelyLockHookContext*>(parameter);
889 Thread* thread = context->thread;
890 thread->setInterruptionReason(Thread::InterruptedBySignal);
891 context->lock->enter();
892 context->acquired += 1;
893 context->signalPreserved = thread->getInterruptionReason() == Thread::InterruptedBySignal ? 1 : 0;
894 thread->clearInterruption();
895 context->lock->leave();
896 context->returned += 1;
897 return 0;
898}
899
900int acquireTerminalUnlikelyLock(void* parameter) {
901 UnlikelyLockHookContext* context = reinterpret_cast<UnlikelyLockHookContext*>(parameter);
902 Thread* thread = context->thread;
904 context->lock->acquire();
905 context->acquired += 1;
906 context->lock->release();
907 context->returned += 1;
908 return 0;
909}
910
911int acquireFairnessWriter(void* parameter) {
912 UnlikelyLockFairnessContext* context = reinterpret_cast<UnlikelyLockFairnessContext*>(parameter);
913 context->lock->acquire();
914 context->writerOrder = context->admissionSequence += 1;
915 context->lock->release();
916 context->writerReturned += 1;
917 return 0;
918}
919
920int enterFairnessReader(void* parameter) {
921 UnlikelyLockFairnessContext* context = reinterpret_cast<UnlikelyLockFairnessContext*>(parameter);
922 context->lock->enter();
923 context->admissionSequence += 1;
924 context->readersEntered += 1;
925 context->lock->leave();
926 return 0;
927}
928
929void processSuspendHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
930 size_t debugState) {
931 ProcessSuspendContext* context = g_ProcessSuspendContext;
932 if (!context || debugState != Thread::ProcessWait) {
933 return;
934 }
935
936 context->hookCalls += 1;
937 context->waiter = thread;
938 if (!queue || channel.owner != context->process ||
939 channel.value != static_cast<uintptr_t>(Thread::ProcessWait)) {
940 context->hookFailures += 1;
941 }
942
943 switch (context->mode) {
944 case ProcessSuspendContext::ObserveBlock:
945 if (!context->phase.compareAndSwap(0, 1)) {
946 context->hookFailures += 1;
947 }
948 break;
949 case ProcessSuspendContext::ResumeBeforeBlock:
950 context->process->resume();
951 context->resumes += 1;
952 break;
953 case ProcessSuspendContext::EventThenResume:
954 if (context->hookCalls == 1) {
955 if (queue->wakeOne(WaitQueue::WakeReason::Event, channel)) {
956 context->eventWakes += 1;
957 } else {
958 context->hookFailures += 1;
959 }
960 } else if (context->hookCalls == 2) {
961 context->process->resume();
962 context->resumes += 1;
963 } else {
964 context->hookFailures += 1;
965 }
966 break;
967 }
968}
969
970int immediateThreadExit(void*) {
971 g_ImmediateThreadExits += 1;
972 return 0;
973}
974
975int blockedJoinTarget(void* parameter) {
976 JoinPropagationContext* context = reinterpret_cast<JoinPropagationContext*>(parameter);
977 context->targetEntered += 1;
978 return context->gate->acquireForCompletion() ? 0 : 1;
979}
980
981int joinTarget(void* parameter) {
982 JoinPropagationContext* context = reinterpret_cast<JoinPropagationContext*>(parameter);
983 StackDestructionCanary stackCanary(&context->joinerDestructed);
984 if (context->target->join()) {
985 context->joinSucceeded += 1;
986 }
987 context->joinReturned += 1;
988 return 0;
989}
990
991int blockedJoinPublicationTarget(void* parameter) {
992 JoinPublicationContext* context = reinterpret_cast<JoinPublicationContext*>(parameter);
993 context->targetEntered += 1;
994 return context->targetGate.acquireForCompletion() ? 0 : 1;
995}
996
997int joinPublicationTarget(void* parameter) {
998 JoinPublicationContext* context = reinterpret_cast<JoinPublicationContext*>(parameter);
999 if (context->target->join()) {
1000 context->joinSucceeded += 1;
1001 }
1002 context->joinReturned += 1;
1003 return 0;
1004}
1005
1006void joinPublicationHook(WaitQueue*, Thread* thread, const WaitQueue::Channel&, size_t debugState) {
1007 JoinPublicationContext* context = __atomic_load_n(&g_JoinPublicationContext, __ATOMIC_ACQUIRE);
1008 if (!context || thread != context->joiner || debugState != Thread::Joining) {
1009 return;
1010 }
1011
1012 context->hookCalls += 1;
1013 Thread::WaitDebugInfo wait = {};
1014 if (!thread->getWaitDebugInfo(wait) || !wait.queue || !wait.queued ||
1015 wait.reason != WaitQueue::WakeReason::Waiting) {
1016 context->hookFailures += 1;
1017 }
1018
1019 // The exclusive join claim is already published before the waiter. A
1020 // concurrent detach must not steal ownership of the target.
1021 if (!context->target->detach()) {
1022 context->detachRejected += 1;
1023 } else {
1024 context->hookFailures += 1;
1025 }
1026
1027 // Complete the target after join() has published its waiter but before it
1028 // asks the scheduler to sleep. markReapable() must retain this wake.
1029 context->targetGate.release();
1030 constexpr size_t Attempts = 10000;
1031 for (size_t attempt = 0; attempt < Attempts; ++attempt) {
1032 if (context->target->isReapableForHostedTest()) {
1033 context->targetReapableInHook += 1;
1034 return;
1035 }
1037 }
1038 context->hookFailures += 1;
1039}
1040
1041int resumeBlockedProcess(void* parameter) {
1042 ProcessSuspendContext* context = reinterpret_cast<ProcessSuspendContext*>(parameter);
1043 while (context->phase < 1) {
1045 }
1046
1047 while (true) {
1048 context->waiter->getLock().acquire();
1049 const bool sleeping = context->waiter->getStatus() == Thread::Sleeping;
1050 context->waiter->getLock().release();
1051 if (sleeping) {
1052 break;
1053 }
1055 }
1056
1057 context->process->resume();
1058 context->resumes += 1;
1059 if (!context->phase.compareAndSwap(1, 2)) {
1060 context->hookFailures += 1;
1061 return 1;
1062 }
1063 return 0;
1064}
1065
1066int waitAtProcessStopReturnGate(void* parameter) {
1067 ProcessStopReturnGateContext* context =
1068 reinterpret_cast<ProcessStopReturnGateContext*>(parameter);
1069 Thread* current = Processor::information().getCurrentThread();
1070 context->entered += 1;
1071 if (!context->release.acquire()) {
1072 context->failures += 1;
1073 context->returned += 1;
1074 return 0;
1075 }
1076
1077 if (current->getScheduler()->serviceProcessStopAtUserReturn()) {
1078 context->terminalReturns += 1;
1079 } else {
1080 context->crossings += 1;
1081 }
1082 context->returned += 1;
1083 return 0;
1084}
1085
1086int waitAtProcessStopReturnGateWithDeferredEvents(void* parameter) {
1087 ProcessStopReturnGateContext* context =
1088 reinterpret_cast<ProcessStopReturnGateContext*>(parameter);
1089 Thread* current = Processor::information().getCurrentThread();
1090 context->entered += 1;
1091 {
1092 Uninterruptible deferPrequeuedEvent;
1093 if (!context->release.acquire()) {
1094 context->failures += 1;
1095 context->returned += 1;
1096 return 0;
1097 }
1098 }
1099
1100 if (current->getScheduler()->serviceProcessStopAtUserReturn()) {
1101 context->terminalReturns += 1;
1102 } else {
1103 context->crossings += 1;
1104 }
1105 context->returned += 1;
1106 return 0;
1107}
1108
1109void terminateStoppedReturnGatePeer(size_t) {
1110 Processor::information().getCurrentThread()->setUnwindState(Thread::TerminateThread);
1111}
1112
1113int stopOwnerWithPrequeuedTerminalEvent(void* parameter) {
1114 PrequeuedStopOwnerContext* context = reinterpret_cast<PrequeuedStopOwnerContext*>(parameter);
1115 Thread* current = Processor::information().getCurrentThread();
1116 StopCurrentProcessEvent stopEvent;
1117 SignalEvent terminalEvent(reinterpret_cast<uintptr_t>(&terminateStoppedReturnGatePeer), 9, ~0UL,
1118 0, true, false, Event::HandlerPrivilege::Kernel,
1119 SignalEvent::DeliveryDisposition::DefaultAction);
1120
1121 context->entered += 1;
1122 bool stopQueued = false;
1123 bool terminalQueued = false;
1124 {
1125 Uninterruptible deferPrequeuedEvents;
1126 stopQueued = current->sendEvent(&stopEvent);
1127 terminalQueued = stopQueued && current->sendEvent(&terminalEvent);
1128 if (!stopQueued || !terminalQueued) {
1129 if (stopQueued) {
1130 current->cullEvent(&stopEvent);
1131 }
1132 if (terminalQueued) {
1133 current->cullEvent(&terminalEvent);
1134 }
1135 }
1136 }
1137
1138 if (!stopQueued || !terminalQueued) {
1139 context->failures += 1;
1140 context->returned += 1;
1141 return 1;
1142 }
1143 context->eventsQueued += 1;
1144
1145 current->getScheduler()->checkEventState(0);
1146 if (current->getUnwindState() == Thread::TerminateThread && context->process->isSuspended()) {
1147 context->terminalReturns += 1;
1148 } else {
1149 context->failures += 1;
1150 }
1151 context->returned += 1;
1152 return 0;
1153}
1154
1155int activeDirectTransitionWithPrequeuedTerminalEvent(void* parameter) {
1156 PrequeuedStopOwnerContext* context = reinterpret_cast<PrequeuedStopOwnerContext*>(parameter);
1157 Thread* current = Processor::information().getCurrentThread();
1158 SignalEvent terminalEvent(reinterpret_cast<uintptr_t>(&terminateStoppedReturnGatePeer), 9, ~0UL,
1159 0, true, false, Event::HandlerPrivilege::Kernel,
1160 SignalEvent::DeliveryDisposition::DefaultAction);
1161
1162 context->entered += 1;
1163 bool terminalQueued = false;
1164 {
1165 Uninterruptible deferPrequeuedEvent;
1166 terminalQueued = current->sendEvent(&terminalEvent);
1167 }
1168 if (!terminalQueued) {
1169 context->failures += 1;
1170 context->returned += 1;
1171 return 1;
1172 }
1173 context->eventsQueued += 1;
1174
1175 const bool terminal = current->getScheduler()->serviceProcessStopAtUserReturn(
1176 PerProcessorScheduler::ProcessStopGateMode::DirectUserTransition);
1177 if (terminal && current->getUnwindState() == Thread::TerminateThread &&
1178 context->process->getState() == Process::Active) {
1179 context->terminalReturns += 1;
1180 } else {
1181 context->failures += 1;
1182 }
1183 context->returned += 1;
1184 return 0;
1185}
1186
1187int ownProcessStop(void* parameter) {
1188 ProcessStopOwnerContext* context = reinterpret_cast<ProcessStopOwnerContext*>(parameter);
1189 context->entered += 1;
1190 context->process->suspend();
1191 context->returned += 1;
1192 return 0;
1193}
1194
1195struct BlockingContext {
1196 explicit BlockingContext(Thread* waiter)
1197 : channel(this, 0x57414954),
1198 waiter(waiter),
1199 phase(0),
1200 hookCalls(0),
1201 hookFailures(0),
1202 workerWoke(0) {}
1203
1204 WaitQueue queue;
1205 WaitQueue::Channel channel;
1206 Thread* const waiter;
1207 Atomic<size_t> phase;
1208 Atomic<size_t> hookCalls;
1209 Atomic<size_t> hookFailures;
1210 Atomic<size_t> workerWoke;
1211};
1212
1213BlockingContext* g_BlockingContext = nullptr;
1214
1215struct RequeueWaitContext {
1216 RequeueWaitContext()
1217 : source(&queue, 0x46555458),
1218 destination(&queue, 0x4d555445),
1219 entered(0),
1220 returned(0),
1221 signalled(0),
1222 terminated(0) {}
1223
1224 WaitQueue queue;
1225 WaitQueue::Channel source;
1226 WaitQueue::Channel destination;
1227 Atomic<size_t> entered;
1228 Atomic<size_t> returned;
1229 Atomic<size_t> signalled;
1230 Atomic<size_t> terminated;
1231};
1232
1233int waitForRequeue(void* parameter) {
1234 RequeueWaitContext* context = reinterpret_cast<RequeueWaitContext*>(parameter);
1235 context->entered += 1;
1236
1237 auto guard = context->queue.acquire();
1238 const WaitQueue::WakeReason reason = guard.wait(
1239 context->source, Thread::FutexWait, reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
1240 if (reason == WaitQueue::WakeReason::Signalled) {
1241 context->signalled += 1;
1242 } else if (reason == WaitQueue::WakeReason::Terminating) {
1243 context->terminated += 1;
1244 }
1245 context->returned += 1;
1246 return 0;
1247}
1248
1249void blockingHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
1250 size_t debugState) {
1251 BlockingContext* context = __atomic_load_n(&g_BlockingContext, __ATOMIC_ACQUIRE);
1252 if (!context) {
1253 return;
1254 }
1255
1256 if (queue != &context->queue || !(channel == context->channel) ||
1257 debugState != Thread::EventWait || thread != context->waiter) {
1258 return;
1259 }
1260
1261 context->hookCalls += 1;
1262 if (!context->phase.compareAndSwap(0, 1)) {
1263 context->hookFailures += 1;
1264 }
1265}
1266
1267int blockingWaker(void* parameter) {
1268 BlockingContext* context = reinterpret_cast<BlockingContext*>(parameter);
1269 while (context->phase < 1) {
1271 }
1272 // This is deliberately later than the hook: prove the waiter committed
1273 // Sleeping before issuing the ordinary wake.
1274 while (true) {
1275 context->waiter->getLock().acquire();
1276 const bool sleeping = context->waiter->getStatus() == Thread::Sleeping;
1277 context->waiter->getLock().release();
1278
1279 if (sleeping) {
1280 break;
1281 }
1283 }
1284 if (!context->phase.compareAndSwap(1, 2)) {
1285 context->hookFailures += 1;
1286 return 1;
1287 }
1288
1289 if (context->queue.wakeOne(WaitQueue::WakeReason::Signalled, context->channel)) {
1290 context->workerWoke += 1;
1291 } else {
1292 context->hookFailures += 1;
1293 }
1294
1295 if (!context->phase.compareAndSwap(2, 3)) {
1296 context->hookFailures += 1;
1297 return 1;
1298 }
1299 return 0;
1300}
1301
1302bool check(bool condition, const char* test, const char* detail) {
1303 if (condition) {
1304 return true;
1305 }
1306
1307 ERROR("HOSTED-WAIT-TEST: FAIL " << test << ": " << detail);
1308 return false;
1309}
1310
1311bool wakeBeforeBlock() {
1312 WaitQueue queue;
1313 const WaitQueue::Channel channel(&queue, 0x5055424c);
1314
1315 g_ImmediateQueue = &queue;
1316 g_ImmediateChannel = channel;
1317 g_ImmediateExpectedWaiter = Processor::information().getCurrentThread();
1318 WaitQueue::setBeforeBlockHook(immediateWakeHook);
1319
1320 auto guard = queue.acquire();
1321 const WaitQueue::WakeReason reason = guard.wait(
1322 channel, Thread::EventWait, reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
1323
1324 WaitQueue::setBeforeBlockHook(nullptr);
1325 g_ImmediateQueue = nullptr;
1326 g_ImmediateExpectedWaiter = nullptr;
1327
1328 bool passed = true;
1329 passed &= check(reason == WaitQueue::WakeReason::Signalled, "wake-before-block",
1330 "wait returned the wrong wake reason");
1331 passed &= check(g_ImmediateHookCalls == 1, "wake-before-block",
1332 "the pre-block hook did not run exactly once");
1333 passed &= check(g_ImmediateHookFailures == 0, "wake-before-block",
1334 "the published waiter was not visible to the hook");
1335 passed &=
1336 check(queue.waiterCount() == 0, "wake-before-block", "the completed waiter remained queued");
1337 passed &= check(g_ImmediateWaiter && g_ImmediateWaiter->getStatus() == Thread::Running,
1338 "wake-before-block", "the scheduler committed a stale Sleeping transition");
1339
1340 if (passed) {
1341 NOTICE("HOSTED-WAIT-TEST: PASS wake-before-block");
1342 }
1343 return passed;
1344}
1345
1346bool semaphoreReleaseBeforeBlock() {
1347 Semaphore semaphore(0);
1348 SemaphoreHookContext context(&semaphore, Processor::information().getCurrentThread());
1349 g_SemaphoreContext = &context;
1350 WaitQueue::setBeforeBlockHook(semaphoreReleaseHook);
1351
1352 const bool acquired = semaphore.acquire();
1353
1354 WaitQueue::setBeforeBlockHook(nullptr);
1355 g_SemaphoreContext = nullptr;
1356
1357 bool passed = true;
1358 passed &= check(acquired, "semaphore-pre-block", "acquire did not consume the hook release");
1359 passed &= check(context.hookCalls == 1, "semaphore-pre-block",
1360 "the pre-block hook did not run exactly once");
1361 passed &= check(context.hookFailures == 0, "semaphore-pre-block",
1362 "the semaphore waiter was not published in the expected state");
1363 passed &= check(context.waiter && context.waiter->getStatus() == Thread::Running,
1364 "semaphore-pre-block", "the scheduler committed Sleeping after the release");
1365 passed &= check(semaphore.getValue() == 0, "semaphore-pre-block",
1366 "the released semaphore unit was not consumed");
1367
1368 if (passed) {
1369 NOTICE("HOSTED-WAIT-TEST: PASS semaphore-pre-block");
1370 }
1371 return passed;
1372}
1373
1374bool semaphoreReleaseBeforeIntent() {
1375 Semaphore semaphore(0);
1376 SemaphoreHookContext context(&semaphore, Processor::information().getCurrentThread());
1377 g_SemaphoreContext = &context;
1378 Semaphore::setBeforeWaitHook(semaphoreReleaseBeforeIntentHook);
1379
1380 // Reuse the queue: a successful final predicate check must retire its intent.
1381 const bool first = semaphore.acquire();
1382 const bool second = semaphore.acquire();
1383
1384 Semaphore::setBeforeWaitHook(nullptr);
1385 g_SemaphoreContext = nullptr;
1386 const bool passed = check(first && second && context.hookCalls == 2 &&
1387 context.hookFailures == 0 && semaphore.getValue() == 0,
1388 "semaphore-pre-intent",
1389 "the final predicate missed a release or retained abandoned intent");
1390 if (passed) {
1391 NOTICE("HOSTED-WAIT-TEST: PASS semaphore-pre-intent");
1392 }
1393 return passed;
1394}
1395
1396bool semaphorePartialReleaseBeforeBlock() {
1397 Semaphore semaphore(0);
1398 SemaphoreHookContext context(&semaphore, Processor::information().getCurrentThread());
1399 g_SemaphoreContext = &context;
1400 WaitQueue::setBeforeBlockHook(semaphorePartialReleaseHook);
1401
1402 bool acquired = true;
1403 bool exactCounts = true;
1404 for (size_t cycle = 0; cycle < 2 && acquired; ++cycle) {
1405 acquired = semaphore.acquire(3);
1406 exactCounts &= context.hookCalls == 2 * (cycle + 1) && semaphore.getValue() == 0;
1407 }
1408
1409 WaitQueue::setBeforeBlockHook(nullptr);
1410 g_SemaphoreContext = nullptr;
1411 const bool passed =
1412 check(acquired && exactCounts && context.hookCalls == 4 && context.hookFailures == 0 &&
1413 context.waiter && context.waiter->getStatus() == Thread::Running,
1414 "semaphore-partial-release-rearm",
1415 "partial releases lost tokens, completed early, or failed to rearm the queue");
1416 if (passed) {
1417 NOTICE("HOSTED-WAIT-TEST: PASS semaphore-partial-release-rearm");
1418 }
1419 return passed;
1420}
1421
1422bool terminalCancellationReturns() {
1423 TerminalCancelContext context;
1424 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), waitForTerminalCancellation,
1425 &context, nullptr, false, true);
1426 waiter->setName("hosted terminal cancellation return");
1427 context.waiter = waiter;
1428
1429 const bool queued = waitForDebugState(waiter, Thread::EventWait);
1430 waiter->setUnwindState(Thread::TerminateThread);
1431 const bool joined = waiter->joinForCompletion();
1432
1433 const bool passed = check(
1434 context.entered == 1 && queued && joined && context.terminalResult == 1 &&
1435 context.resumed == 1 && context.destructed == 1,
1436 "terminal-cancel-return", "terminal cancellation did not return through the target's stack");
1437 if (passed) {
1438 NOTICE("HOSTED-WAIT-TEST: PASS terminal-cancel-return");
1439 }
1440 return passed;
1441}
1442
1443bool publishedWaitDiscardCleanup() {
1444 PublishedWaitDiscardContext context;
1445 const auto reason = PerProcessorScheduler::StackDiscardReason::HostedRegression;
1446 const size_t discardsBefore = PerProcessorScheduler::stackDiscardCount(reason);
1447
1448 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), abandonPublishedWait,
1449 &context, nullptr, false, true, true);
1450 waiter->setName("hosted published wait stack discard");
1451 context.waiter = waiter;
1452
1453 g_PublishedWaitDiscardContext = &context;
1454 WaitQueue::setBeforeBlockHook(discardPublishedWaitHook);
1455 const bool started = waiter->start();
1456 const bool joined = started && waiter->joinForCompletion();
1457 WaitQueue::setBeforeBlockHook(nullptr);
1458 g_PublishedWaitDiscardContext = nullptr;
1459
1460 PublishedWaitOwner* residue = context.owner;
1461 context.owner = nullptr;
1462 if (residue) {
1463 delete residue;
1464 }
1465
1466 const bool passed =
1467 check(started && joined && context.entered == 1 && context.hookCalls == 1 &&
1468 context.hookFailures == 0 && context.published == 1 && context.cleanupCalls == 1 &&
1469 context.ownerDestructed == 1 && context.waitersAtDestruction == 0 &&
1470 context.returned == 0 && context.stackDestructed == 0 &&
1471 PerProcessorScheduler::stackDiscardCount(reason) == discardsBefore + 1,
1472 "waitqueue-published-stack-discard",
1473 "stack discard did not unlink the published waiter before owner cleanup");
1474 if (passed) {
1475 NOTICE("HOSTED-WAIT-TEST: PASS waitqueue-published-stack-discard");
1476 }
1477 return passed;
1478}
1479
1480bool terminalCancelBeforeBlock() {
1481 Thread* waiter = Processor::information().getCurrentThread();
1482 TerminalCancelBeforeBlockContext context(waiter);
1483 g_TerminalCancelBeforeBlockContext = &context;
1484 WaitQueue::setBeforeBlockHook(terminalCancelBeforeBlockHook);
1485
1486 auto firstGuard = context.queue.acquire();
1487 const WaitQueue::WakeReason cancelled =
1488 firstGuard.wait(context.firstChannel, Thread::EventWait,
1489 reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
1490 waiter->setUnwindState(Thread::Continue);
1491
1492 // A fresh enrolment proves returning a terminal result retired the old
1493 // waiter before the record is reused.
1494 auto secondGuard = context.queue.acquire();
1495 const WaitQueue::WakeReason signalled =
1496 secondGuard.wait(context.secondChannel, Thread::EventWait,
1497 reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
1498
1499 WaitQueue::setBeforeBlockHook(nullptr);
1500 g_TerminalCancelBeforeBlockContext = nullptr;
1501
1502 bool passed = true;
1503 passed &= check(cancelled == WaitQueue::WakeReason::Terminating, "terminal-cancel-before-block",
1504 "pre-block terminal cancellation did not return its terminal reason");
1505 passed &= check(signalled == WaitQueue::WakeReason::Signalled, "terminal-cancel-before-block",
1506 "a stale terminal handoff aborted a fresh wait");
1507 passed &= check(context.phase == 2 && context.hookCalls == 2 && context.hookFailures == 0 &&
1508 context.cancellations == 1 && context.wakeups == 1,
1509 "terminal-cancel-before-block",
1510 "the deterministic pre-block interleaving did not complete");
1511 passed &= check(context.queue.waiterCount() == 0 && waiter->getStatus() == Thread::Running,
1512 "terminal-cancel-before-block",
1513 "terminal return left a linked waiter or stale Sleeping state");
1514
1515 if (passed) {
1516 NOTICE("HOSTED-WAIT-TEST: PASS terminal-cancel-before-block");
1517 }
1518 return passed;
1519}
1520
1521int nestedTerminalShutdownWait(void* parameter) {
1522 NestedTerminalShutdownContext* context =
1523 reinterpret_cast<NestedTerminalShutdownContext*>(parameter);
1524 context->waiter = Processor::information().getCurrentThread();
1525 context->entered += 1;
1526
1527 auto guard = context->queue.acquire();
1528 const WaitQueue::WakeReason reason =
1529 guard.wait(context->channel, Thread::EventWait,
1530 reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
1531 context->terminalResult = reason == WaitQueue::WakeReason::Terminating;
1532
1533 Thread::WaitDebugInfo wait = {};
1534 context->waiterUnlinked = !context->waiter->getWaitDebugInfo(wait);
1535 context->finished += 1;
1536 return context->terminalResult ? 0 : 1;
1537}
1538
1539bool nestedTerminalShutdownBeforeBlock() {
1540 NestedTerminalShutdownContext context;
1541 g_NestedTerminalShutdownContext = &context;
1542 WaitQueue::setBeforeBlockHook(nestedTerminalShutdownBeforeBlockHook);
1543
1544 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), nestedTerminalShutdownWait,
1545 &context, nullptr, false, true);
1546 waiter->setName("hosted nested terminal pre-block waiter");
1547
1548 const bool joined = waiter->joinForCompletion();
1549 WaitQueue::setBeforeBlockHook(nullptr);
1550 g_NestedTerminalShutdownContext = nullptr;
1551
1552 const bool passed =
1553 check(context.entered == 1 && context.hookCalls == 1 && context.hookFailures == 0 &&
1554 context.pushed == 1 && context.shutdowns == 1 && context.popped == 1 &&
1555 context.finished == 1 && context.terminalResult == 1 &&
1556 context.waiterUnlinked == 1 && context.queue.waiterCount() == 0 && joined,
1557 "nested-terminal-cancel-before-block",
1558 "shutdown cancelled a lower-state waiter without returning it from "
1559 "blockCurrent cleanly");
1560 if (passed) {
1561 NOTICE(
1562 "HOSTED-WAIT-TEST: PASS "
1563 "nested-terminal-cancel-before-block");
1564 }
1565 return passed;
1566}
1567
1568bool conditionVariableSignalBeforeBlock() {
1569 Mutex mutex;
1570 ConditionVariable condition;
1571 ConditionVariableHookContext context(&condition, &mutex,
1572 Processor::information().getCurrentThread());
1573
1574 const bool initiallyAcquired = mutex.acquire();
1575 g_ConditionVariableContext = &context;
1576 WaitQueue::setBeforeBlockHook(conditionVariableSignalHook);
1577
1578 ConditionVariable::Error waitError = ConditionVariable::NoError;
1579 const bool waitSucceeded = condition.wait(mutex, waitError);
1580
1581 WaitQueue::setBeforeBlockHook(nullptr);
1582 g_ConditionVariableContext = nullptr;
1583
1584 // A failed tryAcquire proves wait() returned with the mutex held. Either
1585 // way, release the one unit held by this test.
1586 const bool mutexWasUnlocked = mutex.tryAcquire();
1587 mutex.release();
1588
1589 bool passed = true;
1590 passed &= check(initiallyAcquired, "condition-variable-pre-block",
1591 "the caller could not acquire its mutex");
1592 passed &= check(waitSucceeded && waitError == ConditionVariable::NoError,
1593 "condition-variable-pre-block", "wait did not report a successful signal");
1594 passed &= check(context.hookCalls == 1, "condition-variable-pre-block",
1595 "the pre-block hook did not run exactly once");
1596 passed &= check(context.hookFailures == 0, "condition-variable-pre-block",
1597 "the signal window did not release the caller mutex");
1598 passed &= check(context.acquiredMutex == 1, "condition-variable-pre-block",
1599 "the hook could not acquire the released caller mutex");
1600 passed &= check(!mutexWasUnlocked, "condition-variable-pre-block",
1601 "wait returned without reacquiring the caller mutex");
1602 passed &=
1603 check(context.waiter && context.waiter->getStatus() == Thread::Running,
1604 "condition-variable-pre-block", "the scheduler committed Sleeping after the signal");
1605
1606 if (passed) {
1607 NOTICE("HOSTED-WAIT-TEST: PASS condition-variable-pre-block");
1608 }
1609 return passed;
1610}
1611
1612bool conditionVariableCompletionBarrier() {
1613 CompletionConditionContext context;
1614 Thread* waiter =
1615 new Thread(Scheduler::instance().getKernelProcess(), waitForTerminalCompletionCondition,
1616 &context, nullptr, false, true, true);
1617 waiter->setName("hosted terminal-pending CV completion waiter");
1618 context.waiter = waiter;
1619 const bool started = waiter->start();
1620
1621 const bool gateQueued = [&waiter]() {
1622 for (size_t i = 0; i < 10000; ++i) {
1623 Thread::WaitDebugInfo wait = {};
1624 uintptr_t debugAddress = 0;
1625 if (waiter->getWaitDebugInfo(wait) && wait.queued &&
1626 waiter->getDebugState(debugAddress) == Thread::SemWait) {
1627 return true;
1628 }
1630 }
1631 return false;
1632 }();
1633
1634 waiter->setUnwindState(Thread::TerminateThread);
1635 context.gate.release();
1636
1637 const bool conditionQueued = [&waiter]() {
1638 for (size_t i = 0; i < 10000; ++i) {
1639 Thread::WaitDebugInfo wait = {};
1640 uintptr_t debugAddress = 0;
1641 if (waiter->getWaitDebugInfo(wait) && wait.queued &&
1642 waiter->getDebugState(debugAddress) == Thread::CondWait) {
1643 return true;
1644 }
1646 }
1647 return false;
1648 }();
1649
1650 const bool mutexAcquired = context.mutex.acquireForCompletion();
1651 context.predicate = 1;
1652 context.condition.signal();
1653 if (mutexAcquired) {
1654 context.mutex.release();
1655 }
1656 const bool joined = waiter->join();
1657
1658 const bool passed =
1659 check(started && gateQueued && conditionQueued && mutexAcquired && joined &&
1660 context.entered == 1 && context.returned == 1 && context.terminalReported == 1 &&
1661 context.terminalMutexHeld == 1 && context.signalPreserved == 1,
1662 "condition-variable-completion-barrier",
1663 "terminal state escaped the barrier, mutex ownership was lost, or "
1664 "its signal marker was discarded");
1665 if (passed) {
1666 NOTICE(
1667 "HOSTED-WAIT-TEST: PASS "
1668 "condition-variable-completion-barrier");
1669 }
1670 return passed;
1671}
1672
1673bool conditionVariableContendedReacquire(Thread* waiter,
1674 ContendedConditionContext::Trigger trigger) {
1675 const char* test = trigger == ContendedConditionContext::Signal
1676 ? "condition-variable-contended-signal-reacquire"
1677 : "condition-variable-contended-timeout-reacquire";
1678 ContendedConditionContext context(waiter, trigger);
1679
1680 const bool initiallyAcquired = context.mutex.acquire();
1681 g_ContendedSignalHandlerCalls = 0;
1682 Thread* holder = new Thread(Scheduler::instance().getKernelProcess(), holdConditionMutex,
1683 &context, nullptr, false, true);
1684 holder->setName("hosted contended condition mutex holder");
1685
1686 ConditionVariable::Error error = ConditionVariable::NoError;
1687 Time::Timestamp timeout = trigger == ContendedConditionContext::Timeout
1688 ? 100 * Time::Multiplier::Millisecond
1689 : Time::Infinity;
1690 const bool waited = context.condition.wait(context.mutex, timeout, error);
1691 const bool mutexHeld = context.mutex.isOwnedByCurrentThread();
1692 if (mutexHeld) {
1693 context.mutex.release();
1694 }
1695 const bool joined = holder->joinForCompletion();
1696
1697 const bool expectedError = trigger == ContendedConditionContext::Signal
1698 ? error == ConditionVariable::Interrupted
1699 : error == ConditionVariable::TimedOut;
1700 bool passed = true;
1701 passed &= check(initiallyAcquired && !waited && expectedError, test,
1702 "the wait did not report its interruption after mutex reacquisition");
1703 passed &= check(mutexHeld, test, "the interrupted wait returned without owning its mutex");
1704 passed &= check(joined && context.holderAcquired == 1 && context.conditionWaitPublished == 1 &&
1705 context.reacquireWaitPublished == 1 && context.holderReleased == 1,
1706 test, "the contender did not hold the mutex across interruption");
1707 if (trigger == ContendedConditionContext::Signal) {
1708 passed &= check(context.triggerSent == 1 && g_ContendedSignalHandlerCalls == 1, test,
1709 "the published signal was not delivered exactly once");
1710 } else {
1711 passed &= check(context.triggerSent == 0 && timeout == 0, test,
1712 "the timeout did not expire while the mutex was contended");
1713 }
1714
1715 if (passed) {
1716 if (trigger == ContendedConditionContext::Signal) {
1717 NOTICE(
1718 "HOSTED-WAIT-TEST: PASS "
1719 "condition-variable-contended-signal-reacquire");
1720 } else {
1721 NOTICE(
1722 "HOSTED-WAIT-TEST: PASS "
1723 "condition-variable-contended-timeout-reacquire");
1724 }
1725 }
1726 return passed;
1727}
1728
1729bool conditionVariableTerminalReacquire() {
1730 TerminalConditionContext context;
1731 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), waitForTerminalCondition,
1732 &context, nullptr, false, true, true);
1733 waiter->setName("hosted terminal condition mutex waiter");
1734 context.waiter = waiter;
1735 const bool started = waiter->start();
1736
1737 const bool conditionQueued = waitForDebugState(waiter, Thread::CondWait);
1738 const bool mutexAcquired = conditionQueued && context.mutex.acquireForCompletion();
1739 context.condition.signal();
1740 const bool reacquireQueued = waitForDebugState(waiter, Thread::SemWait);
1741 waiter->setUnwindState(Thread::TerminateThread);
1742 if (mutexAcquired) {
1743 context.mutex.release();
1744 }
1745
1746 const bool joined = waiter->joinForCompletion();
1747 const bool mutexRecoverable = context.mutex.tryAcquire();
1748 if (mutexRecoverable) {
1749 context.mutex.release();
1750 }
1751
1752 const bool passed = check(
1753 started && context.entered == 1 && conditionQueued && mutexAcquired && reacquireQueued &&
1754 joined && context.waitReturned == 1 && context.terminalResult == 1 &&
1755 context.mutexHeldOnReturn == 1 && context.destructed == 1 && mutexRecoverable,
1756 "condition-variable-terminal-reacquire",
1757 "terminal propagation did not return with mutex ownership and unwind the stack");
1758 if (passed) {
1759 NOTICE(
1760 "HOSTED-WAIT-TEST: PASS "
1761 "condition-variable-terminal-reacquire");
1762 }
1763 return passed;
1764}
1765
1766bool unlikelyLockWriterPreference() {
1767 UnlikelyLock lock;
1768 UnlikelyLockFairnessContext context(&lock);
1769 lock.enter();
1770
1771 Thread* writer = new Thread(Scheduler::instance().getKernelProcess(), acquireFairnessWriter,
1772 &context, nullptr, false, true);
1773 writer->setName("hosted queued UnlikelyLock writer");
1774 const bool writerQueued = waitForDebugState(writer, Thread::CondWait);
1775
1776 Thread* readerA = new Thread(Scheduler::instance().getKernelProcess(), enterFairnessReader,
1777 &context, nullptr, false, true);
1778 readerA->setName("hosted later UnlikelyLock reader A");
1779 Thread* readerB = new Thread(Scheduler::instance().getKernelProcess(), enterFairnessReader,
1780 &context, nullptr, false, true);
1781 readerB->setName("hosted later UnlikelyLock reader B");
1782 const bool readerAQueued = waitForDebugState(readerA, Thread::CondWait);
1783 const bool readerBQueued = waitForDebugState(readerB, Thread::CondWait);
1784
1785 lock.leave();
1786 const bool writerJoined = writer->joinForCompletion();
1787 const bool readerAJoined = readerA->joinForCompletion();
1788 const bool readerBJoined = readerB->joinForCompletion();
1789
1790 return check(writerQueued && readerAQueued && readerBQueued && writerJoined && readerAJoined &&
1791 readerBJoined && context.writerOrder == 1 && context.writerReturned == 1 &&
1792 context.readersEntered == 2 && context.admissionSequence == 3,
1793 "unlikely-lock-admission", "later readers bypassed a queued writer");
1794}
1795
1796bool unlikelyLockAdmission() {
1797 UnlikelyLock lock;
1798 bool passed = true;
1799
1800 lock.acquire();
1801 UnlikelyLockHookContext reader(&lock, UnlikelyLockHookContext::WriterHeld);
1802 g_UnlikelyLockContext = &reader;
1803 WaitQueue::setBeforeBlockHook(unlikelyLockAdmissionHook);
1804 Thread* readerThread = new Thread(Scheduler::instance().getKernelProcess(), enterUnlikelyLock,
1805 &reader, nullptr, false, true, true);
1806 readerThread->setName("hosted UnlikelyLock reader");
1807 reader.thread = readerThread;
1808 const bool readerStarted = readerThread->start();
1809 for (size_t i = 0; i < 10000 && reader.hookCalls == 0; ++i) {
1811 }
1812 if (reader.hookCalls) {
1813 WaitQueue::setBeforeBlockHook(nullptr);
1814 g_UnlikelyLockContext = nullptr;
1815 }
1816 const bool readerJoined = readerThread->join();
1817 WaitQueue::setBeforeBlockHook(nullptr);
1818 g_UnlikelyLockContext = nullptr;
1819
1820 passed &=
1821 check(readerStarted && readerJoined && reader.hookCalls == 1 && reader.hookFailures == 0 &&
1822 reader.acquired == 1 && reader.returned == 1 && reader.signalPreserved == 1,
1823 "unlikely-lock-admission", "reader admission lost a publication wake or signal marker");
1824
1825 lock.enter();
1826 UnlikelyLockHookContext writer(&lock, UnlikelyLockHookContext::ReaderHeld);
1827 g_UnlikelyLockContext = &writer;
1828 WaitQueue::setBeforeBlockHook(unlikelyLockAdmissionHook);
1829 Thread* writerThread =
1830 new Thread(Scheduler::instance().getKernelProcess(), acquireTerminalUnlikelyLock, &writer,
1831 nullptr, false, true, true);
1832 writerThread->setName("hosted terminal-pending UnlikelyLock writer");
1833 writer.thread = writerThread;
1834 const bool writerStarted = writerThread->start();
1835 for (size_t i = 0; i < 10000 && writer.hookCalls == 0; ++i) {
1837 }
1838 if (writer.hookCalls) {
1839 WaitQueue::setBeforeBlockHook(nullptr);
1840 g_UnlikelyLockContext = nullptr;
1841 }
1842 const bool writerJoined = writerThread->join();
1843 WaitQueue::setBeforeBlockHook(nullptr);
1844 g_UnlikelyLockContext = nullptr;
1845
1846 passed &=
1847 check(writerStarted && writerJoined && writer.hookCalls == 1 && writer.hookFailures == 0 &&
1848 writer.acquired == 1 && writer.returned == 1,
1849 "unlikely-lock-admission", "writer admission abandoned ownership for terminal state");
1850
1851 passed &= unlikelyLockWriterPreference();
1852
1853 if (passed) {
1854 NOTICE("HOSTED-WAIT-TEST: PASS unlikely-lock-admission");
1855 }
1856 return passed;
1857}
1858
1859bool ordinaryBlockAndWake() {
1860 BlockingContext context(Processor::information().getCurrentThread());
1861
1862 Thread* waker = new Thread(Scheduler::instance().getKernelProcess(), blockingWaker, &context,
1863 nullptr, false, true);
1864 waker->setName("hosted wait regression waker");
1865
1866 __atomic_store_n(&g_BlockingContext, &context, __ATOMIC_RELEASE);
1867 WaitQueue::setBeforeBlockHook(blockingHook);
1868 auto guard = context.queue.acquire();
1869 const WaitQueue::WakeReason reason = guard.wait(
1870 context.channel, Thread::EventWait, reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
1871 WaitQueue::setBeforeBlockHook(nullptr);
1872 __atomic_store_n(&g_BlockingContext, nullptr, __ATOMIC_RELEASE);
1873
1874 const bool joined = waker->join();
1875
1876 bool passed = true;
1877 passed &= check(reason == WaitQueue::WakeReason::Signalled, "ordinary-block-wake",
1878 "wait returned the wrong wake reason");
1879 passed &= check(context.hookCalls == 1, "ordinary-block-wake",
1880 "the publication hook did not run exactly once");
1881 passed &= check(context.hookFailures == 0, "ordinary-block-wake",
1882 "the waker did not observe the expected wait state");
1883 passed &= check(context.workerWoke == 1, "ordinary-block-wake",
1884 "the worker did not wake one published waiter");
1885 passed &= check(context.phase == 3, "ordinary-block-wake",
1886 "the worker did not complete the blocking sequence");
1887 passed &= check(joined, "ordinary-block-wake", "the waker could not be joined");
1888 passed &= check(context.queue.waiterCount() == 0, "ordinary-block-wake",
1889 "the completed waiter remained queued");
1890
1891 if (passed) {
1892 NOTICE("HOSTED-WAIT-TEST: PASS ordinary-block-wake");
1893 }
1894 return passed;
1895}
1896
1897bool waitQueueWakeAndRequeue() {
1898 constexpr const char* Test = "waitqueue-wake-and-requeue";
1899 RequeueWaitContext context;
1900 Thread* waiters[3] = {};
1901
1902 bool enrolled = true;
1903 for (size_t i = 0; i < 3; ++i) {
1904 waiters[i] = new Thread(Scheduler::instance().getKernelProcess(), waitForRequeue, &context,
1905 nullptr, false, true);
1906 waiters[i]->setName("hosted WaitQueue requeue waiter");
1907 }
1908 for (Thread* waiter : waiters) {
1909 enrolled &= waitForDebugState(waiter, Thread::FutexWait);
1910 }
1911
1912 size_t moved = 0;
1913 bool sourceReachedMovedWaiter = true;
1914 size_t destinationWoken = 0;
1915 if (enrolled) {
1916 auto guard = context.queue.acquire();
1917 moved = guard.wakeAndRequeue(context.source, 1, context.destination, 2);
1918 sourceReachedMovedWaiter = guard.wakeOne(WaitQueue::WakeReason::Signalled, context.source);
1919 destinationWoken = guard.wakeAll(WaitQueue::WakeReason::Signalled, context.destination);
1920 }
1921
1922 if (!enrolled || moved != 3 || sourceReachedMovedWaiter || destinationWoken != 2) {
1923 for (Thread* waiter : waiters) {
1924 waiter->setUnwindState(Thread::TerminateThread);
1925 }
1926 }
1927
1928 bool joined = true;
1929 for (Thread* waiter : waiters) {
1930 joined &= waiter->joinForCompletion();
1931 }
1932
1933 bool passed = true;
1934 passed &= check(enrolled, Test, "the source waiters did not all publish");
1935 passed &= check(moved == 3, Test, "wake-and-requeue did not select all three waiters");
1936 passed &= check(!sourceReachedMovedWaiter, Test,
1937 "the source channel reached a completed or requeued waiter");
1938 passed &= check(destinationWoken == 2, Test,
1939 "the destination did not wake exactly the two requeued waiters");
1940 passed &= check(joined && context.entered == 3 && context.returned == 3 &&
1941 context.signalled == 3 && context.terminated == 0,
1942 Test, "the requeued waiters did not all return as signalled");
1943 passed &=
1944 check(context.queue.waiterCount() == 0, Test, "completed requeued waiters remained linked");
1945
1946 RequeueWaitContext cancellation;
1947 Thread* cancelled = new Thread(Scheduler::instance().getKernelProcess(), waitForRequeue,
1948 &cancellation, nullptr, false, true);
1949 cancelled->setName("hosted WaitQueue cancelled requeue waiter");
1950 const bool cancellationEnrolled = waitForDebugState(cancelled, Thread::FutexWait);
1951 size_t cancellationMoved = 0;
1952 if (cancellationEnrolled) {
1953 auto guard = cancellation.queue.acquire();
1954 cancellationMoved = guard.wakeAndRequeue(cancellation.source, 0, cancellation.destination, 1);
1955 }
1956
1957 Thread::WaitDebugInfo wait = {};
1958 const bool destinationPublished = cancelled->getWaitDebugInfo(wait) &&
1959 wait.queue == &cancellation.queue && wait.queued &&
1960 wait.reason == WaitQueue::WakeReason::Waiting &&
1961 wait.channelOwner == cancellation.destination.owner &&
1962 wait.channelValue == cancellation.destination.value;
1964 const bool cancellationJoined = cancelled->joinForCompletion();
1965
1966 passed &= check(cancellationEnrolled && cancellationMoved == 1 && destinationPublished, Test,
1967 "the cancellation waiter was not requeued to the destination");
1968 passed &= check(cancellationJoined && cancellation.returned == 1 && cancellation.signalled == 0 &&
1969 cancellation.terminated == 1 && cancellation.queue.waiterCount() == 0,
1970 Test, "cancelling a requeued waiter did not unlink it safely");
1971
1972 if (passed) {
1973 NOTICE("HOSTED-WAIT-TEST: PASS waitqueue-wake-and-requeue");
1974 }
1975 return passed;
1976}
1977
1978bool processSuspendResume() {
1979 Process* process = Scheduler::instance().getKernelProcess();
1980 bool passed = true;
1981
1982 Process::ChildTransition discarded;
1983 while (process->takePendingChildTransition(true, true, discarded)) {
1984 }
1985
1986 auto consumeContinued = [process]() {
1987 Process::ChildTransition transition;
1988 const bool stopWasNotSelected = !process->takePendingChildTransition(true, false, transition);
1989 const bool continuedSelected = process->takePendingChildTransition(false, true, transition) &&
1990 transition.kind == Process::ChildTransitionKind::Continued &&
1991 !transition.stopSignal;
1992 const bool consumedExactlyOnce = !process->takePendingChildTransition(true, true, transition);
1993 return stopWasNotSelected && continuedSelected && consumedExactlyOnce;
1994 };
1995
1996 ProcessSuspendContext blocking(process, ProcessSuspendContext::ObserveBlock);
1997 Thread* resumer = new Thread(process, resumeBlockedProcess, &blocking, nullptr, false, true);
1998 resumer->setName("hosted process resume regression");
1999
2000 g_ProcessSuspendContext = &blocking;
2001 WaitQueue::setBeforeBlockHook(processSuspendHook);
2002 process->suspend();
2003 WaitQueue::setBeforeBlockHook(nullptr);
2004 g_ProcessSuspendContext = nullptr;
2005
2006 passed &=
2007 check(resumer->join(), "process-suspend-resume", "the ordinary resumer could not be joined");
2008 passed &=
2009 check(blocking.hookCalls == 1 && blocking.hookFailures == 0 && blocking.phase == 2 &&
2010 blocking.resumes == 1,
2011 "process-suspend-resume", "the ordinary suspend did not block and resume exactly once");
2012 const bool blockingTransition = consumeContinued();
2013 passed &=
2014 check(process->getState() == Process::Active && blockingTransition, "process-suspend-resume",
2015 "ordinary resume did not replace and exclusively report the pending stop");
2016
2017 ProcessSuspendContext immediate(process, ProcessSuspendContext::ResumeBeforeBlock);
2018 g_ProcessSuspendContext = &immediate;
2019 WaitQueue::setBeforeBlockHook(processSuspendHook);
2020 process->suspend();
2021 WaitQueue::setBeforeBlockHook(nullptr);
2022 g_ProcessSuspendContext = nullptr;
2023
2024 passed &= check(immediate.hookCalls == 1 && immediate.hookFailures == 0 && immediate.resumes == 1,
2025 "process-suspend-resume", "resume in the publication window was not retained");
2026 const bool immediateTransition = consumeContinued();
2027 passed &=
2028 check(process->getState() == Process::Active && immediateTransition, "process-suspend-resume",
2029 "pre-block resume did not replace and exclusively report the pending stop");
2030
2031 ProcessSuspendContext eventThenResume(process, ProcessSuspendContext::EventThenResume);
2032 g_ProcessSuspendContext = &eventThenResume;
2033 WaitQueue::setBeforeBlockHook(processSuspendHook);
2034 process->suspend();
2035 WaitQueue::setBeforeBlockHook(nullptr);
2036 g_ProcessSuspendContext = nullptr;
2037
2038 passed &=
2039 check(eventThenResume.hookCalls == 2 && eventThenResume.hookFailures == 0 &&
2040 eventThenResume.eventWakes == 1 && eventThenResume.resumes == 1,
2041 "process-suspend-resume", "an event wake bypassed the Suspended predicate recheck");
2042 const bool eventTransition = consumeContinued();
2043 passed &=
2044 check(process->getState() == Process::Active && eventTransition, "process-suspend-resume",
2045 "event/recheck resume did not replace and exclusively report the pending stop");
2046
2047 if (passed) {
2048 NOTICE("HOSTED-WAIT-TEST: PASS process-suspend-resume");
2049 }
2050 return passed;
2051}
2052
2053bool processStopGatesPeerReturns() {
2054 constexpr const char* Test = "process-stop-gates-peer-returns";
2055 Process* kernelProcess = Scheduler::instance().getKernelProcess();
2056 Process* process = new Process(kernelProcess);
2057
2058 ProcessStopReturnGateContext gateContext;
2059 Thread* first =
2060 new Thread(process, waitAtProcessStopReturnGate, &gateContext, nullptr, false, true, true);
2061 first->setName("hosted stopped return-gate peer one");
2062 Thread* second =
2063 new Thread(process, waitAtProcessStopReturnGate, &gateContext, nullptr, false, true, true);
2064 second->setName("hosted stopped return-gate peer two");
2065
2066 ProcessStopOwnerContext ownerContext(process);
2067 Thread* owner = new Thread(process, ownProcessStop, &ownerContext, nullptr, false, true, true);
2068 owner->setName("hosted process-stop owner");
2069
2070 const bool firstStarted = first->start();
2071 const bool secondStarted = second->start();
2072 const bool peersWaiting = firstStarted && secondStarted &&
2073 waitForDebugState(first, Thread::SemWait) &&
2074 waitForDebugState(second, Thread::SemWait) && gateContext.entered == 2;
2075 const bool ownerStarted = peersWaiting && owner->start();
2076 const Time::Timestamp stopDeadline = Time::getTicks() + (2 * Time::Multiplier::Second);
2077 while (ownerStarted && !process->isSuspended() && !ownerContext.returned &&
2078 Time::getTicks() < stopDeadline) {
2080 }
2081 const bool suspended = process->isSuspended();
2082 const bool ownerWaiting = suspended && waitForDebugState(owner, Thread::ProcessWait);
2083
2084 gateContext.release.release(2);
2085 const bool firstGated = suspended && waitForDebugState(first, Thread::ProcessWait);
2086 const bool secondGated = suspended && waitForDebugState(second, Thread::ProcessWait);
2087 for (size_t attempt = 0; attempt < 32; ++attempt) {
2089 }
2090 const bool bothHeld = firstGated && secondGated && !gateContext.returned &&
2091 !gateContext.crossings && !gateContext.terminalReturns;
2092
2093 bool resumed = false;
2094 if (process->isSuspended()) {
2095 process->resume();
2096 resumed = process->getState() == Process::Active;
2097 } else if (ownerStarted && !ownerContext.returned) {
2098 owner->setUnwindState(Thread::TerminateThread);
2099 }
2100
2101 const bool ownerJoined = ownerStarted && owner->joinForCompletion();
2102 // Joining the owner closes a setup-time CAS race before the cleanup resume.
2103 if (process->isSuspended()) {
2104 process->resume();
2105 }
2106 const bool firstJoined = firstStarted && first->joinForCompletion();
2107 const bool secondJoined = secondStarted && second->joinForCompletion();
2108 if (!firstStarted) {
2109 delete first;
2110 }
2111 if (!secondStarted) {
2112 delete second;
2113 }
2114 if (!ownerStarted) {
2115 delete owner;
2116 }
2117
2118 const bool passed = check(
2119 firstStarted && secondStarted && peersWaiting && ownerStarted && suspended && ownerWaiting &&
2120 bothHeld && resumed && firstJoined && secondJoined && ownerJoined &&
2121 gateContext.returned == 2 && gateContext.crossings == 2 && !gateContext.terminalReturns &&
2122 !gateContext.failures && ownerContext.entered == 1 && ownerContext.returned == 1,
2123 Test,
2124 "a running process peer crossed its user-return gate while stopped or failed to resume");
2125 delete process;
2126
2127 if (passed) {
2128 NOTICE("HOSTED-WAIT-TEST: PASS " << Test);
2129 }
2130 return passed;
2131}
2132
2133bool stoppedProcessDefersUserReturnEvent() {
2134 constexpr const char* Test = "stopped-process-defers-user-return-event";
2135 Process* kernelProcess = Scheduler::instance().getKernelProcess();
2136 Process* process = new Process(kernelProcess);
2137
2138 ProcessStopReturnGateContext gateContext;
2139 Thread* peer =
2140 new Thread(process, waitAtProcessStopReturnGate, &gateContext, nullptr, false, true, true);
2141 peer->setName("hosted stopped user-event return-gate peer");
2142 ProcessStopOwnerContext ownerContext(process);
2143 Thread* owner = new Thread(process, ownProcessStop, &ownerContext, nullptr, false, true, true);
2144 owner->setName("hosted user-event process-stop owner");
2145
2146 g_StoppedUserHandlerCalls = 0;
2147 const bool peerStarted = peer->start();
2148 const bool peerWaiting =
2149 peerStarted && waitForDebugState(peer, Thread::SemWait) && gateContext.entered == 1;
2150 const bool ownerStarted = peerWaiting && owner->start();
2151 const Time::Timestamp stopDeadline = Time::getTicks() + (2 * Time::Multiplier::Second);
2152 while (ownerStarted && !process->isSuspended() && !ownerContext.returned &&
2153 Time::getTicks() < stopDeadline) {
2155 }
2156 const bool suspended = process->isSuspended();
2157 const bool ownerWaiting = suspended && waitForDebugState(owner, Thread::ProcessWait);
2158
2159 StoppedUserReturnEvent userEvent;
2160 const bool eventQueued = ownerWaiting && peer->sendEvent(&userEvent);
2161 const bool remainedOnOriginalWait =
2162 eventQueued && waitForDebugState(peer, Thread::SemWait) && peer->hasEvent(&userEvent);
2163 gateContext.release.release();
2164 const bool peerGated = remainedOnOriginalWait && waitForDebugState(peer, Thread::ProcessWait);
2165 for (size_t attempt = 0; attempt < 32; ++attempt) {
2167 }
2168 const bool heldWhileStopped = peerGated && process->isSuspended() && !gateContext.returned &&
2169 peer->hasEvent(&userEvent) && !g_StoppedUserHandlerCalls;
2170
2171 bool resumed = false;
2172 if (process->isSuspended()) {
2173 process->resume();
2174 resumed = process->getState() == Process::Active;
2175 } else if (ownerStarted && !ownerContext.returned) {
2176 owner->setUnwindState(Thread::TerminateThread);
2177 }
2178 const bool peerJoined = peerStarted && peer->joinForCompletion();
2179 const bool ownerJoined = ownerStarted && owner->joinForCompletion();
2180 if (!peerStarted) {
2181 delete peer;
2182 }
2183 if (!ownerStarted) {
2184 delete owner;
2185 }
2186
2187 const bool passed = check(
2188 peerStarted && peerWaiting && ownerStarted && suspended && ownerWaiting && eventQueued &&
2189 remainedOnOriginalWait && heldWhileStopped && resumed && peerJoined && ownerJoined &&
2190 gateContext.returned == 1 && gateContext.crossings == 1 && !gateContext.terminalReturns &&
2191 !gateContext.failures && !g_StoppedUserHandlerCalls && ownerContext.entered == 1 &&
2192 ownerContext.returned == 1,
2193 Test, "a stopped process entered or consumed a userspace event before resume");
2194 delete process;
2195
2196 if (passed) {
2197 NOTICE("HOSTED-WAIT-TEST: PASS " << Test);
2198 }
2199 return passed;
2200}
2201
2202bool terminalUnwindEscapesProcessStopGate() {
2203 constexpr const char* Test = "terminal-unwind-escapes-process-stop-gate";
2204 Process* kernelProcess = Scheduler::instance().getKernelProcess();
2205 Process* process = new Process(kernelProcess);
2206
2207 ProcessStopReturnGateContext gateContext;
2208 Thread* peer =
2209 new Thread(process, waitAtProcessStopReturnGate, &gateContext, nullptr, false, true, true);
2210 peer->setName("hosted terminal stopped return-gate peer");
2211 ProcessStopOwnerContext ownerContext(process);
2212 Thread* owner = new Thread(process, ownProcessStop, &ownerContext, nullptr, false, true, true);
2213 owner->setName("hosted terminal process-stop owner");
2214
2215 const bool peerStarted = peer->start();
2216 const bool peerWaiting =
2217 peerStarted && waitForDebugState(peer, Thread::SemWait) && gateContext.entered == 1;
2218 const bool ownerStarted = peerWaiting && owner->start();
2219 const Time::Timestamp stopDeadline = Time::getTicks() + (2 * Time::Multiplier::Second);
2220 while (ownerStarted && !process->isSuspended() && !ownerContext.returned &&
2221 Time::getTicks() < stopDeadline) {
2223 }
2224 const bool suspended = process->isSuspended();
2225 const bool ownerWaiting = suspended && waitForDebugState(owner, Thread::ProcessWait);
2226
2227 gateContext.release.release();
2228 const bool peerGated = suspended && waitForDebugState(peer, Thread::ProcessWait);
2229 if (peerGated) {
2231 }
2232 const Time::Timestamp terminalDeadline = Time::getTicks() + (2 * Time::Multiplier::Second);
2233 while (peerGated && !gateContext.returned && Time::getTicks() < terminalDeadline) {
2235 }
2236 const bool returnedWhileStopped =
2237 peerGated && gateContext.returned == 1 && process->isSuspended();
2238 const bool terminalWon = returnedWhileStopped && gateContext.terminalReturns == 1 &&
2239 !gateContext.crossings && !gateContext.failures;
2240
2241 if (!gateContext.returned && peerStarted) {
2243 }
2244 if (process->isSuspended()) {
2245 process->resume();
2246 } else if (ownerStarted && !ownerContext.returned) {
2247 owner->setUnwindState(Thread::TerminateThread);
2248 }
2249
2250 const bool peerJoined = peerStarted && peer->joinForCompletion();
2251 const bool ownerJoined = ownerStarted && owner->joinForCompletion();
2252 if (!peerStarted) {
2253 delete peer;
2254 }
2255 if (!ownerStarted) {
2256 delete owner;
2257 }
2258 if (process->isSuspended()) {
2259 process->resume();
2260 }
2261
2262 const bool passed =
2263 check(peerStarted && peerWaiting && ownerStarted && suspended && ownerWaiting && peerGated &&
2264 returnedWhileStopped && terminalWon && peerJoined && ownerJoined &&
2265 gateContext.entered == 1 && gateContext.returned == 1 &&
2266 ownerContext.entered == 1 && ownerContext.returned == 1,
2267 Test, "terminal unwind did not escape a stopped process gate before resume");
2268 delete process;
2269
2270 if (passed) {
2271 NOTICE("HOSTED-WAIT-TEST: PASS " << Test);
2272 }
2273 return passed;
2274}
2275
2276bool prequeuedTerminalEventEscapesProcessStopGate() {
2277 constexpr const char* Test = "prequeued-terminal-event-before-resume";
2278 Process* kernelProcess = Scheduler::instance().getKernelProcess();
2279 Process* process = new Process(kernelProcess);
2280
2281 ProcessStopReturnGateContext gateContext;
2282 Thread* peer = new Thread(process, waitAtProcessStopReturnGateWithDeferredEvents, &gateContext,
2283 nullptr, false, true, true);
2284 peer->setName("hosted prequeued-terminal stopped return-gate peer");
2285 ProcessStopOwnerContext ownerContext(process);
2286 Thread* owner = new Thread(process, ownProcessStop, &ownerContext, nullptr, false, true, true);
2287 owner->setName("hosted prequeued-terminal process-stop owner");
2288
2289 const bool peerStarted = peer->start();
2290 const bool peerWaiting =
2291 peerStarted && waitForDebugState(peer, Thread::SemWait) && gateContext.entered == 1;
2292 const bool ownerStarted = peerWaiting && owner->start();
2293 const Time::Timestamp stopDeadline = Time::getTicks() + (2 * Time::Multiplier::Second);
2294 while (ownerStarted && !process->isSuspended() && !ownerContext.returned &&
2295 Time::getTicks() < stopDeadline) {
2297 }
2298 const bool suspended = process->isSuspended();
2299 const bool ownerWaiting = suspended && waitForDebugState(owner, Thread::ProcessWait);
2300
2301 SignalEvent terminalEvent(reinterpret_cast<uintptr_t>(&terminateStoppedReturnGatePeer), 9, ~0UL,
2302 0, true, false, Event::HandlerPrivilege::Kernel,
2303 SignalEvent::DeliveryDisposition::DefaultAction);
2304 const bool terminalQueued = ownerWaiting && peer->sendEvent(&terminalEvent);
2305 const bool remainedPrequeued =
2306 terminalQueued && waitForDebugState(peer, Thread::SemWait) && !gateContext.returned;
2307
2308 gateContext.release.release();
2309 const Time::Timestamp terminalDeadline = Time::getTicks() + (2 * Time::Multiplier::Second);
2310 while (remainedPrequeued && !gateContext.returned && Time::getTicks() < terminalDeadline) {
2312 }
2313 const bool returnedWhileStopped = remainedPrequeued && gateContext.returned == 1 &&
2314 process->isSuspended() && !ownerContext.returned;
2315 const bool terminalWon = returnedWhileStopped && gateContext.terminalReturns == 1 &&
2316 !gateContext.crossings && !gateContext.failures;
2317
2318 if (!gateContext.returned && peerStarted) {
2320 }
2321 const bool peerJoined = peerStarted && peer->joinForCompletion();
2322 if (process->isSuspended()) {
2323 process->resume();
2324 } else if (ownerStarted && !ownerContext.returned) {
2325 owner->setUnwindState(Thread::TerminateThread);
2326 }
2327 const bool ownerJoined = ownerStarted && owner->joinForCompletion();
2328 if (!peerStarted) {
2329 delete peer;
2330 }
2331 if (!ownerStarted) {
2332 delete owner;
2333 }
2334
2335 const bool passed =
2336 check(peerStarted && peerWaiting && ownerStarted && suspended && ownerWaiting &&
2337 terminalQueued && remainedPrequeued && terminalWon && peerJoined && ownerJoined &&
2338 gateContext.entered == 1 && gateContext.returned == 1 &&
2339 ownerContext.entered == 1 && ownerContext.returned == 1,
2340 Test, "a terminal event queued before gate enrollment was lost behind process stop");
2341 delete process;
2342
2343 if (passed) {
2344 NOTICE("HOSTED-WAIT-TEST: PASS " << Test);
2345 }
2346 return passed;
2347}
2348
2349bool prequeuedTerminalEventEscapesStopOwnerGate() {
2350 constexpr const char* Test = "prequeued-terminal-event-before-stop-owner-wait";
2351 Process* kernelProcess = Scheduler::instance().getKernelProcess();
2352 Process* process = new Process(kernelProcess);
2353
2354 PrequeuedStopOwnerContext context(process);
2355 Thread* owner = new Thread(process, stopOwnerWithPrequeuedTerminalEvent, &context, nullptr, false,
2356 true, true);
2357 owner->setName("hosted prequeued-terminal process-stop owner");
2358
2359 const bool started = owner->start();
2360 const Time::Timestamp terminalDeadline = Time::getTicks() + (2 * Time::Multiplier::Second);
2361 while (started && !context.returned && Time::getTicks() < terminalDeadline) {
2363 }
2364 const bool returnedWhileStopped = started && context.returned == 1 && process->isSuspended();
2365 const bool terminalWon = returnedWhileStopped && context.entered == 1 &&
2366 context.eventsQueued == 1 && context.terminalReturns == 1 &&
2367 !context.failures;
2368
2369 if (!context.returned && started) {
2370 owner->setUnwindState(Thread::TerminateThread);
2371 }
2372 const bool joined = started && owner->joinForCompletion();
2373 if (process->isSuspended()) {
2374 process->resume();
2375 }
2376 if (!started) {
2377 delete owner;
2378 }
2379
2380 const bool passed =
2381 check(started && terminalWon && joined, Test,
2382 "a terminal event queued before the stop owner enrolled was lost behind process stop");
2383 delete process;
2384
2385 if (passed) {
2386 NOTICE("HOSTED-WAIT-TEST: PASS " << Test);
2387 }
2388 return passed;
2389}
2390
2391bool activeDirectTransitionDrainsKernelEvent() {
2392 constexpr const char* Test = "active-direct-transition-drains-kernel-event";
2393 Process* kernelProcess = Scheduler::instance().getKernelProcess();
2394 Process* process = new Process(kernelProcess);
2395
2396 PrequeuedStopOwnerContext context(process);
2397 Thread* thread = new Thread(process, activeDirectTransitionWithPrequeuedTerminalEvent, &context,
2398 nullptr, false, true, true);
2399 thread->setName("hosted active direct-transition terminal gate");
2400
2401 const bool started = thread->start();
2402 const Time::Timestamp terminalDeadline = Time::getTicks() + (2 * Time::Multiplier::Second);
2403 while (started && !context.returned && Time::getTicks() < terminalDeadline) {
2405 }
2406 const bool terminalWon = started && context.returned == 1 && context.entered == 1 &&
2407 context.eventsQueued == 1 && context.terminalReturns == 1 &&
2408 !context.failures && process->getState() == Process::Active;
2409
2410 if (!context.returned && started) {
2412 }
2413 const bool joined = started && thread->joinForCompletion();
2414 if (!started) {
2415 delete thread;
2416 }
2417
2418 const bool passed =
2419 check(terminalWon && joined, Test,
2420 "an active direct userspace transition bypassed a queued kernel terminal event");
2421 delete process;
2422
2423 if (passed) {
2424 NOTICE("HOSTED-WAIT-TEST: PASS " << Test);
2425 }
2426 return passed;
2427}
2428
2429bool prequeuedEventDispatch() {
2430 Thread* thread = g_ImmediateWaiter;
2431 if (!check(thread != nullptr, "prequeued-event",
2432 "the suite did not capture its current thread")) {
2433 return false;
2434 }
2435
2436 const bool passed = check(thread->runHostedPrequeuedEventRegression(), "prequeued-event",
2437 "a ready event was not dispatched exactly once without blocking");
2438
2439 if (passed) {
2440 NOTICE("HOSTED-WAIT-TEST: PASS prequeued-event");
2441 }
2442 return passed;
2443}
2444
2445bool stateLevelPublication() {
2446 Thread* thread = g_ImmediateWaiter;
2447 if (!check(thread != nullptr, "state-level-publication",
2448 "the suite did not capture its current thread")) {
2449 return false;
2450 }
2451
2452 const bool passed =
2453 check(thread->runHostedStatePublicationRegression(), "state-level-publication",
2454 "an event sender observed a partially published state level");
2455
2456 if (passed) {
2457 NOTICE("HOSTED-WAIT-TEST: PASS state-level-publication");
2458 }
2459 return passed;
2460}
2461
2462bool stateCleanupOrder() {
2463 Thread* thread = g_ImmediateWaiter;
2464 if (!check(thread != nullptr, "state-cleanup-order",
2465 "the suite did not capture its current thread")) {
2466 return false;
2467 }
2468
2469 const bool passed = check(thread->runHostedStateCleanupRegression(), "state-cleanup-order",
2470 "cleanup was not LIFO, checkpointed, or level-scoped");
2471 if (passed) {
2472 NOTICE("HOSTED-WAIT-TEST: PASS state-cleanup-order");
2473 }
2474 return passed;
2475}
2476
2477bool execStackOwnership() {
2478 Thread* thread = g_ImmediateWaiter;
2479 if (!check(thread != nullptr, "exec-stack-ownership",
2480 "the suite did not capture its current thread")) {
2481 return false;
2482 }
2483
2484 const bool passed =
2485 check(thread->runHostedExecStackOwnershipRegression(), "exec-stack-ownership",
2486 "exec did not discard stale nested stack metadata or adopt the replacement at base");
2487 if (passed) {
2488 NOTICE("HOSTED-WAIT-TEST: PASS exec-stack-ownership");
2489 }
2490 return passed;
2491}
2492
2493bool activeEventDeliveryLease() {
2494 Thread* thread = g_ImmediateWaiter;
2495 if (!check(thread != nullptr, "event-delivery-lease",
2496 "the suite did not capture its current thread")) {
2497 return false;
2498 }
2499
2500 const bool passed = check(thread->runHostedEventDeliveryLeaseRegression(), "event-delivery-lease",
2501 "a dequeued Event was not retained through scheduler dispatch");
2502
2503 if (passed) {
2504 NOTICE("HOSTED-WAIT-TEST: PASS event-delivery-lease");
2505 }
2506 return passed;
2507}
2508
2509bool eventQueueShutdown() {
2510 Thread* thread = g_ImmediateWaiter;
2511 if (!check(thread != nullptr, "event-shutdown-drain",
2512 "the suite did not capture its current thread")) {
2513 return false;
2514 }
2515
2516 const bool passed = check(thread->runHostedEventShutdownRegression(), "event-shutdown-drain",
2517 "thread shutdown did not reject or drain queued event deliveries");
2518
2519 if (passed) {
2520 NOTICE("HOSTED-WAIT-TEST: PASS event-shutdown-drain");
2521 }
2522 return passed;
2523}
2524
2525bool immediateExitJoinLifecycle() {
2526 constexpr size_t JoinIterations = 4;
2527 bool passed = true;
2528
2529 for (size_t iteration = 0; iteration < JoinIterations; ++iteration) {
2530 Thread* thread = new Thread(Scheduler::instance().getKernelProcess(), immediateThreadExit,
2531 nullptr, nullptr, false, true);
2532 thread->setName("hosted immediate-exit join regression");
2533
2534 while (g_ImmediateThreadExits < (iteration + 1)) {
2536 }
2537
2538 // The entry point has returned. join() may now race only the
2539 // scheduler's final stack handoff and reapable publication.
2540 passed &= check(thread->join(), "thread-join-lifecycle",
2541 "an immediate-exit thread could not be joined");
2542 }
2543
2544 passed &= check(g_ImmediateThreadExits == JoinIterations, "thread-join-lifecycle",
2545 "the repeated immediate-exit entries did not all run");
2546
2547 if (passed) {
2548 NOTICE("HOSTED-WAIT-TEST: PASS thread-join-lifecycle");
2549 }
2550 return passed;
2551}
2552
2553bool joinPublicationAndDetachExclusion() {
2554 Process* process = Scheduler::instance().getKernelProcess();
2555 JoinPublicationContext context;
2556 bool passed = true;
2557
2558 context.target =
2559 new Thread(process, blockedJoinPublicationTarget, &context, nullptr, false, true);
2560 context.target->setName("hosted join publication target");
2561
2562 constexpr size_t Attempts = 10000;
2563 bool targetBlocked = false;
2564 for (size_t attempt = 0; attempt < Attempts; ++attempt) {
2565 if (context.targetEntered == 1 && waitForDebugState(context.target, Thread::SemWait)) {
2566 targetBlocked = true;
2567 break;
2568 }
2570 }
2571 passed &= check(targetBlocked, "thread-join-pre-block-wake",
2572 "the target did not publish its completion gate");
2573 if (!targetBlocked) {
2574 context.targetGate.release();
2575 context.target->join();
2576 return false;
2577 }
2578
2579 context.joiner = new Thread(process, joinPublicationTarget, &context, nullptr, false, true, true);
2580 context.joiner->setName("hosted exact-window joiner");
2581
2582 __atomic_store_n(&g_JoinPublicationContext, &context, __ATOMIC_RELEASE);
2583 WaitQueue::setBeforeBlockHook(joinPublicationHook);
2584 const bool joinerStarted = context.joiner->start();
2585
2586 bool joinerCompleted = false;
2587 for (size_t attempt = 0; attempt < Attempts; ++attempt) {
2588 if (context.joinReturned == 1) {
2589 joinerCompleted = true;
2590 break;
2591 }
2593 }
2594 WaitQueue::setBeforeBlockHook(nullptr);
2595 __atomic_store_n(&g_JoinPublicationContext, static_cast<JoinPublicationContext*>(nullptr),
2596 __ATOMIC_RELEASE);
2597
2598 const bool joinerJoined = context.joiner->join();
2599 passed &= check(joinerStarted && joinerCompleted && joinerJoined && context.hookCalls == 1 &&
2600 context.hookFailures == 0 && context.targetReapableInHook == 1 &&
2601 context.joinSucceeded == 1,
2602 "thread-join-pre-block-wake",
2603 "a target wake in join's published-before-sleep window was lost");
2604 passed &= check(context.detachRejected == 1, "thread-join-detach-exclusion",
2605 "detach stole a target after an exclusive join claim was published");
2606
2607 if (passed) {
2608 NOTICE("HOSTED-WAIT-TEST: PASS thread-join-pre-block-wake");
2609 NOTICE("HOSTED-WAIT-TEST: PASS thread-join-detach-exclusion");
2610 }
2611 return passed;
2612}
2613
2614bool terminalJoinPropagation() {
2615 Process* process = Scheduler::instance().getKernelProcess();
2616 Semaphore targetGate(0);
2617 JoinPropagationContext context(&targetGate);
2618 bool passed = true;
2619
2620 context.target = new Thread(process, blockedJoinTarget, &context, nullptr, false, true);
2621 context.target->setName("hosted join-propagation target");
2622
2623 while (context.targetEntered != 1) {
2625 }
2626
2627 Thread* interruptedJoiner = new Thread(process, joinTarget, &context, nullptr, false, true);
2628 interruptedJoiner->setName("hosted terminal join-propagation waiter");
2629
2630 bool enrolled = false;
2631 constexpr size_t EnrolmentAttempts = 10000;
2632 for (size_t attempt = 0; attempt < EnrolmentAttempts; ++attempt) {
2633 Thread::WaitDebugInfo info = {};
2634 uintptr_t debugAddress = 0;
2635 if (interruptedJoiner->getWaitDebugInfo(info) && info.queue && info.queued &&
2636 interruptedJoiner->getDebugState(debugAddress) == Thread::Joining &&
2637 interruptedJoiner->getStatus() == Thread::Sleeping) {
2638 enrolled = true;
2639 break;
2640 }
2642 }
2643 passed &= check(enrolled, "join-terminal-propagation",
2644 "the first joiner did not publish its join wait");
2645
2646 interruptedJoiner->setUnwindState(Thread::TerminateThread);
2647 passed &= check(interruptedJoiner->joinForCompletion(), "join-terminal-propagation",
2648 "the terminated joiner did not become reapable");
2649 passed &= check(
2650 context.joinReturned == 1 && context.joinSucceeded == 0 && context.joinerDestructed == 1,
2651 "join-terminal-propagation", "the interrupted join did not return and unwind its stack");
2652
2653 Thread* replacementJoiner = new Thread(process, joinTarget, &context, nullptr, false, true);
2654 replacementJoiner->setName("hosted replacement join waiter");
2655
2656 bool replacementEnrolled = false;
2657 for (size_t attempt = 0; attempt < EnrolmentAttempts; ++attempt) {
2658 Thread::WaitDebugInfo info = {};
2659 if (replacementJoiner->getWaitDebugInfo(info) && info.queue && info.queued &&
2660 replacementJoiner->getStatus() == Thread::Sleeping) {
2661 replacementEnrolled = true;
2662 break;
2663 }
2665 }
2666 passed &= check(replacementEnrolled, "join-terminal-propagation",
2667 "the interrupted exclusive join claim blocked a replacement joiner");
2668
2669 targetGate.release();
2670 passed &= check(replacementJoiner->join(), "join-terminal-propagation",
2671 "the replacement joiner did not become reapable");
2672 passed &= check(
2673 context.joinReturned == 2 && context.joinSucceeded == 1 && context.joinerDestructed == 2,
2674 "join-terminal-propagation", "the replacement join did not consume the released claim");
2675
2676 if (passed) {
2677 NOTICE("HOSTED-WAIT-TEST: PASS join-terminal-propagation");
2678 }
2679 return passed;
2680}
2681
2682} // namespace
2683
2684bool runHostedWaitRegressions() {
2685 NOTICE("HOSTED-WAIT-TEST: BEGIN");
2686
2687 const bool passed =
2688 runHostedSpinlockRegressions() && runHostedElfValidationRegressions() &&
2689#if !PEDIGREE_HOSTED_CORE_SMOKE
2690 runHostedPs2ControllerRegressions() &&
2691#endif
2692 wakeBeforeBlock() && semaphoreReleaseBeforeBlock() && semaphoreReleaseBeforeIntent() &&
2693 semaphorePartialReleaseBeforeBlock() && terminalCancellationReturns() &&
2694 publishedWaitDiscardCleanup() && terminalCancelBeforeBlock() &&
2695 nestedTerminalShutdownBeforeBlock() && conditionVariableSignalBeforeBlock() &&
2696 runHostedRingBufferRegressions() && runHostedBufferRegressions() &&
2697 runHostedAtaPioRegressions() &&
2699 .getBootstrapProcessorScheduler()
2700 ->runHostedNewThreadWorkerRegressions() &&
2701#if !PEDIGREE_HOSTED_CORE_SMOKE
2702 runHostedSchedulerRegressions() &&
2703#endif
2704 runHostedRequestQueueRegressions() &&
2705#if PEDIGREE_HOSTED_CORE_SMOKE
2706 runHostedStoragePageRegressions() && runHostedCacheDiscardRegressions() &&
2707 runHostedCacheTimerRegressions() &&
2708#endif
2709#if !PEDIGREE_HOSTED_CORE_SMOKE
2710 runHostedUsbCallbackDeliveryRegressions() && runHostedUsbHcdPortChangeRegressions() &&
2711 runHostedUsbTransferLifecycleRegressions() && runHostedMutexRegressions() &&
2712 runHostedInterruptManagerRegressions() && runHostedCdiIrqRegressions() &&
2713 runHostedInputRegressions() && runNetworkFilterConcurrencyRegressions() &&
2714 runVfsCallbackLifetimeRegressions() && runHostedCacheRegressions() &&
2715 runHostedFatSectorRegressions() && runHostedRawFsContractRegressions() &&
2716 runHostedUnixDatagramRegressions() &&
2717#endif
2718 runHostedLifetimeLeaseRegressions() &&
2719#if !PEDIGREE_HOSTED_CORE_SMOKE
2720 runHostedLogRegressions() &&
2721#endif
2722 runHostedProcessExitRegressions() && runHostedPs2MousePacketRegressions() &&
2723#if !PEDIGREE_HOSTED_CORE_SMOKE
2724 runHostedPs2MouseRegressions() &&
2725#endif
2726 conditionVariableContendedReacquire(g_ImmediateWaiter, ContendedConditionContext::Signal) &&
2727 conditionVariableContendedReacquire(g_ImmediateWaiter, ContendedConditionContext::Timeout) &&
2728 conditionVariableTerminalReacquire() && conditionVariableCompletionBarrier() &&
2729 unlikelyLockAdmission() && runHostedPageFaultRegressions() &&
2730 runHostedPayloadPageRegressions() &&
2731#if !PEDIGREE_HOSTED_CORE_SMOKE
2732 runHostedPipeRegressions() && runHostedUsbSyncOwnershipRegression() &&
2733 runHostedUsbInterruptOwnershipRegression() && runHostedUsbContainerOwnershipRegression() &&
2734 runHostedUsbEndpointHaltRegressions() && runHostedUsbBotRegressions() &&
2735 runHostedHidReportRegressions() && runHostedScsiWriteRegressions() &&
2736 UsbPnP::runHostedRegistrationRegression() && Ipc::runHostedIpcInterruptionRegression() &&
2737#endif
2738 runHostedTimerRegressions(g_ImmediateWaiter) &&
2739 runHostedPrimitiveRegressions(g_ImmediateWaiter) &&
2740 runHostedSignalInterruptionRegressions(g_ImmediateWaiter) &&
2741#if !PEDIGREE_HOSTED_CORE_SMOKE
2742 runHostedSyscallRegressions() &&
2743#endif
2744 ordinaryBlockAndWake() && waitQueueWakeAndRequeue() && processSuspendResume() &&
2745 processStopGatesPeerReturns() && stoppedProcessDefersUserReturnEvent() &&
2746 terminalUnwindEscapesProcessStopGate() && prequeuedTerminalEventEscapesProcessStopGate() &&
2747 prequeuedTerminalEventEscapesStopOwnerGate() && activeDirectTransitionDrainsKernelEvent() &&
2748 immediateExitJoinLifecycle() && joinPublicationAndDetachExclusion() &&
2749 terminalJoinPropagation() && prequeuedEventDispatch() && stateLevelPublication() &&
2750 stateCleanupOrder() && execStackOwnership() && activeEventDeliveryLease() &&
2751 eventQueueShutdown();
2752 if (passed) {
2753 NOTICE("HOSTED-WAIT-TEST: PASS all");
2754 } else {
2755 ERROR("HOSTED-WAIT-TEST: FAIL suite");
2756 }
2757 return passed;
2758}
MUST_USE_RESULT bool wait(Mutex &mutex, Time::Timestamp &timeout, Error &error, WaitQueue::StackDiscardCleanup onStackDiscard=nullptr, void *stackDiscardContext=nullptr)
Definition Event.h:49
virtual size_t getNumber()=0
virtual size_t serialize(uint8_t *pBuffer)=0
Definition Mutex.h:56
void checkEventState(uintptr_t userStack)
void abandonCurrentThreadStack(StackDiscardReason reason, Spinlock *pLock=0) NORETURN
MUST_USE_RESULT bool serviceProcessStopAtUserReturn(ProcessStopGateMode mode=ProcessStopGateMode::StopOnly)
bool takePendingChildTransition(bool includeStopped, bool includeContinued, ChildTransition &transition)
Definition Process.cc:2046
void suspend(int stopSignal=0)
Definition Process.cc:1864
void resume()
Definition Process.cc:1968
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
ssize_t getValue()
Definition Semaphore.cc:598
void release(size_t n=1)
Definition Semaphore.cc:546
bool acquire(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:352
void release()
Definition Spinlock.cc:161
bool acquire(bool recurse=false, bool safe=true)
Definition Spinlock.cc:35
void setUnwindState(UnwindType ut)
Definition Thread.cc:3628
@ Continue
No unwind necessary, carry on as normal.
Definition Thread.h:513
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:515
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
void shutdown()
Definition Thread.cc:614
bool hasEvent(Event *pEvent)
Definition Thread.cc:2639
bool joinForCompletion()
Definition Thread.cc:2771
bool join()
Definition Thread.cc:2767
Status getStatus() const
Definition Thread.h:431
UnwindType getUnwindState()
Definition Thread.h:531
void cullEvent(Event *pEvent)
Definition Thread.cc:2193
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
Process * getParent() const
Definition Thread.h:338
void popState(bool clean=true)
Definition Thread.cc:956
class PerProcessorScheduler * getScheduler() const
Definition Thread.h:925
Spinlock & getLock()
Definition Thread.h:609
SchedulerState * pushState()
Definition Thread.cc:884
bool start()
Definition Thread.cc:794
bool sendEvent(Event *pEvent)
Definition Thread.cc:1158
size_t getStateLevel() const
Definition Thread.h:314
Definition User.h:32
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:103
Definition waits.c:9