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