8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/LockGuard.h"
10#include "pedigree/kernel/Log.h"
11#include "pedigree/kernel/Spinlock.h"
12#include "pedigree/kernel/process/ConditionVariable.h"
13#include "pedigree/kernel/process/Mutex.h"
14#include "pedigree/kernel/process/Scheduler.h"
15#include "pedigree/kernel/process/Thread.h"
16#include "pedigree/kernel/processor/Processor.h"
17#include "pedigree/kernel/processor/ProcessorInformation.h"
18#include "pedigree/kernel/time/Time.h"
21struct MutexOwnershipContext {
25 nonOwnerReleaseRejected(0),
26 conditionWaitRejected(0),
27 timedAcquireFinished(0),
28 timedAcquireTimedOut(0),
29 timedAcquireSucceeded(0) {}
40struct MutexGuardContext {
41 MutexGuardContext(
Mutex* mutex,
bool constexprGuard)
44 constexprGuard(constexprGuard),
49 deferredInCritical(0),
50 releaseTransitions(0),
51 releaseSawDeferral(0),
52 releasedAfterScope(0),
69MutexGuardContext* g_MutexGuardContext =
nullptr;
75void mutexTransitionHook(Semaphore::MutexTransitionWindow window) {
77 g_TransitionInterruptFailures += 1;
80 if (window == Semaphore::MutexCounterAcquired) {
81 g_AcquireTransitionSeen += 1;
82 }
else if (window == Semaphore::MutexOwnerReleased) {
83 g_ReleaseTransitionSeen += 1;
87void mutexGuardTransitionHook(Semaphore::MutexTransitionWindow window) {
88 MutexGuardContext* context = g_MutexGuardContext;
90 if (!context || thread != context->worker || window != Semaphore::MutexOwnerReleased ||
91 context->phase !=
static_cast<size_t>(1)) {
95 context->releaseTransitions += 1;
97 context->releaseSawDeferral += 1;
101bool check(
bool condition,
const char* detail,
const char* test =
"mutex-ownership") {
106 ERROR(
"HOSTED-WAIT-TEST: FAIL " << test <<
": " << detail);
110bool mutexCompletionPreservesInterruption() {
111 constexpr const char* Test =
"mutex-completion-preserves-interruption";
113 const Thread::InterruptionReason originalInterruption = thread->getInterruptionReason();
115 const Thread::InterruptionReason reasons[] = {Thread::NotInterrupted, Thread::InterruptedBySignal,
116 Thread::InterruptedByTimeout};
117 const bool interruptStates[] = {
false,
true};
121 for (
bool interrupts : interruptStates) {
123 for (Thread::InterruptionReason reason : reasons) {
124 thread->setInterruptionReason(reason);
125 const bool acquired = mutex.acquireForCompletion();
126 const bool owned = mutex.isOwnedByCurrentThread() && mutex.getValue() == 0;
127 const bool preserved =
132 thread->setInterruptionReason(originalInterruption);
134 check(acquired && owned && preserved && !mutex.isOwnedByCurrentThread() &&
136 "immediate completion lost ownership, interruption, or interrupt state", Test);
142 NOTICE(
"HOSTED-WAIT-TEST: PASS mutex-completion-preserves-interruption");
147void observeGuardedCriticalSection(MutexGuardContext* context) {
149 context->entered += 1;
150 context->ownedInCritical = context->mutex->isOwnedByCurrentThread() ? 1 : 0;
156int acquireTerminalMutexGuard(
void* parameter) {
157 MutexGuardContext* context =
reinterpret_cast<MutexGuardContext*
>(parameter);
159 if (context->constexprGuard) {
161 observeGuardedCriticalSection(context);
164 observeGuardedCriticalSection(context);
168 context->releasedAfterScope =
169 !context->mutex->isOwnedByCurrentThread() && context->mutex->getValue() == 1 ? 1 : 0;
171 context->returned += 1;
175bool waitForMutexGuardBlock(
Thread* thread) {
176 for (
size_t i = 0; i < 10000; ++i) {
178 uintptr_t debugAddress = 0;
188bool terminalMutexGuardScenario(
bool constexprGuard) {
190 MutexGuardContext context(&mutex, constexprGuard);
191 const bool supervisorAcquired = mutex.acquireForCompletion();
194 &context,
nullptr,
false,
true);
195 if (constexprGuard) {
196 worker->setName(
"hosted constexpr terminal Mutex guard");
198 worker->setName(
"hosted terminal Mutex guard");
202 const bool queued = waitForMutexGuardBlock(
worker);
203 const bool enteredBeforeRelease = context.entered !=
static_cast<size_t>(0);
204 g_MutexGuardContext = &context;
205 Semaphore::setMutexTransitionHook(mutexGuardTransitionHook);
207 if (supervisorAcquired) {
210 const bool joined =
worker->joinForCompletion();
211 Semaphore::setMutexTransitionHook(
nullptr);
212 g_MutexGuardContext =
nullptr;
214 const bool recoverable = mutex.tryAcquire();
219 return supervisorAcquired && queued && !enteredBeforeRelease && joined && context.entered == 1 &&
220 context.ownedInCritical == 1 && context.terminalPending == 1 &&
221 context.deferredInCritical == 1 && context.releaseTransitions == 1 &&
222 context.releaseSawDeferral == 1 && context.releasedAfterScope == 1 &&
223 context.returned == 1 && recoverable;
226bool mutexGuardTerminalCompletion() {
227 constexpr const char* Test =
"mutex-guard-terminal-completion";
229 passed &= check(terminalMutexGuardScenario(
false),
230 "LockGuard did not retain ownership and teardown deferral", Test);
231 passed &= check(terminalMutexGuardScenario(
true),
232 "ConstexprLockGuard did not retain ownership and teardown deferral", Test);
234 Mutex conditionalMutex;
239 "condition=false acquired or claimed the mutex", Test);
250 "disown did not transfer release responsibility", Test);
255 "HOSTED-WAIT-TEST: PASS "
256 "mutex-guard-terminal-completion");
261int attemptNonOwnerOperations(
void* parameter) {
262 MutexOwnershipContext* context =
reinterpret_cast<MutexOwnershipContext*
>(parameter);
264 const bool ownedBeforeRelease = context->mutex->isOwnedByCurrentThread();
265 context->mutex->release();
266 if (!ownedBeforeRelease && !context->mutex->isOwnedByCurrentThread() &&
267 context->mutex->getValue() == 0) {
268 context->nonOwnerReleaseRejected += 1;
271 ConditionVariable::Error error = ConditionVariable::NoError;
272 const bool waited = context->condition->wait(*context->mutex, error);
273 if (!waited && error == ConditionVariable::MutexNotLocked &&
274 !context->mutex->isOwnedByCurrentThread() && context->mutex->getValue() == 0) {
275 context->conditionWaitRejected += 1;
281int attemptTimedMutexAcquire(
void* parameter) {
282 MutexOwnershipContext* context =
reinterpret_cast<MutexOwnershipContext*
>(parameter);
283 Semaphore::SemaphoreError error = Semaphore::NoError;
284 const bool acquired = context->mutex->acquireWithError(1, 0, 20000, error);
286 context->timedAcquireSucceeded += 1;
287 context->mutex->release();
288 }
else if (error == Semaphore::TimedOut) {
289 context->timedAcquireTimedOut += 1;
291 context->timedAcquireFinished += 1;
296bool runHostedSpinlockRegressions() {
297 constexpr const char* Test =
"spinlock-interrupt-state";
299 const bool interruptStates[] = {
true,
false};
303 for (
bool interrupts : interruptStates) {
305 const bool initiallyUnlocked = !lock.acquired();
306 const bool acquired = lock.
acquire();
312 passed &= check(initiallyUnlocked && acquired && held && restored,
313 "ordinary acquire/release lost ownership or interrupt state", Test);
318 for (
bool interrupts : interruptStates) {
323 outer.interrupts() == interrupts && !inner.interrupts();
329 passed &= check(nested && outerHeld && restored,
330 "nested locks restored interrupts before the outer release", Test);
334 for (
bool interrupts : interruptStates) {
336 recursive.
acquire(Spinlock::allow_recursion);
337 recursive.
acquire(Spinlock::allow_recursion);
338 recursive.
acquire(Spinlock::allow_recursion);
342 const bool innerHeld =
345 const bool outerHeld =
350 passed &= check(nested && innerHeld && outerHeld && restored,
351 "recursive nesting lost the outer ownership or interrupt state", Test);
356 const bool savedInterrupts = lock.interrupts();
360 const bool reacquiredMasked = lock.acquired() && !lock.interrupts();
365 check(savedInterrupts && exitedMasked && reacquiredMasked && releasedMasked,
366 "exit restored interrupts or retained stale state for the next acquisition", Test);
369 Spinlock initiallyLocked(
true,
true);
371 const bool constructedLocked = initiallyLocked.acquired() && !initiallyLocked.interrupts();
372 initiallyLocked.release();
374 initiallyLocked.acquire();
375 const bool reacquired = initiallyLocked.acquired() && !initiallyLocked.interrupts();
376 initiallyLocked.release();
379 passed &= check(constructedLocked && initialRelease && reacquired && finalRelease,
380 "an initially locked lock could not be released and acquired again", Test);
383 NOTICE(
"HOSTED-WAIT-TEST: PASS spinlock-interrupt-state");
388bool runHostedMutexRegressions() {
391 MutexOwnershipContext context(&mutex, &condition);
394 g_AcquireTransitionSeen = 0;
395 g_ReleaseTransitionSeen = 0;
396 g_TransitionInterruptFailures = 0;
400 Semaphore::setMutexTransitionHook(mutexTransitionHook);
401 passed &= check(mutex.acquire(),
"the supervisor could not acquire the mutex");
402 Semaphore::setMutexTransitionHook(
nullptr);
403 passed &= check(mutex.isOwnedByCurrentThread() && mutex.getValue() == 0,
404 "acquisition did not publish a single current-thread owner");
406 "the debugger owner snapshot did not identify the mutex owner");
407 passed &= check(g_AcquireTransitionSeen == 1 && g_TransitionInterruptFailures == 0 &&
409 "acquisition exposed its counter/owner transition to interrupts");
412 &context,
nullptr,
false,
true);
413 peer->setName(
"hosted mutex ownership regression");
415 passed &= check(peer->
join(),
"the non-owner peer could not be joined");
416 passed &= check(context.nonOwnerReleaseRejected == 1,
"a non-owner release changed the mutex");
417 passed &= check(context.conditionWaitRejected == 1,
418 "condition wait accepted a mutex owned by another thread");
419 passed &= check(mutex.isOwnedByCurrentThread() && mutex.getValue() == 0,
420 "the non-owner peer disturbed the supervisor's ownership");
423 &context,
nullptr,
false,
true);
424 timedPeer->setName(
"hosted timed mutex acquisition regression");
426 const Time::Timestamp timedAcquireDeadline =
427 Time::getTicks() + (500 * Time::Multiplier::Millisecond);
428 while (!context.timedAcquireFinished && Time::getTicks() < timedAcquireDeadline) {
434 const bool timedAcquireNeededRescue = !context.timedAcquireFinished;
435 if (timedAcquireNeededRescue) {
439 passed &= check(timedPeer->
join(),
"the timed-acquire peer could not be joined");
440 const bool timedAcquirePassed = !timedAcquireNeededRescue && context.timedAcquireTimedOut == 1 &&
441 context.timedAcquireSucceeded == 0;
442 passed &= check(timedAcquirePassed,
"a held Mutex ignored its acquisition timeout");
443 if (timedAcquirePassed) {
444 NOTICE(
"HOSTED-WAIT-TEST: PASS hosted-timer-timeout-cleanup");
447 if (timedAcquireNeededRescue) {
449 check(mutex.acquire(),
"the supervisor could not restore ownership after timeout rescue");
451 passed &= check(mutex.isOwnedByCurrentThread() && mutex.getValue() == 0,
452 "the timed-acquire peer disturbed the supervisor's ownership");
455 Semaphore::setMutexTransitionHook(mutexTransitionHook);
457 Semaphore::setMutexTransitionHook(
nullptr);
458 passed &= check(!mutex.isOwnedByCurrentThread() && mutex.getValue() == 1 &&
459 mutex.getDebugMutexOwner() ==
nullptr,
460 "owner release did not restore one available item");
461 passed &= check(g_ReleaseTransitionSeen == 1 && g_TransitionInterruptFailures == 0 &&
463 "release exposed its owner/counter transition to interrupts");
466 passed &= check(!mutex.isOwnedByCurrentThread() && mutex.getValue() == 1,
467 "double release changed the mutex's binary count");
470 const size_t acquireTransitions = g_AcquireTransitionSeen;
471 const size_t releaseTransitions = g_ReleaseTransitionSeen;
473 Semaphore::setMutexTransitionHook(mutexTransitionHook);
474 passed &= check(counting.tryAcquire(),
"the counting-semaphore control acquisition failed");
476 Semaphore::setMutexTransitionHook(
nullptr);
477 passed &= check(g_AcquireTransitionSeen == acquireTransitions &&
478 g_ReleaseTransitionSeen == releaseTransitions &&
480 "counting Semaphore entered a Mutex transition window");
482 "thread join or mutex teardown lost the caller interrupt state");
484 passed &= mutexCompletionPreservesInterruption();
485 passed &= mutexGuardTerminalCompletion();
488 NOTICE(
"HOSTED-WAIT-TEST: PASS mutex-ownership");
bool isOwnedByCurrentThread() const
static bool getInterrupts()
static ProcessorInformation & information()
static void setInterrupts(bool bEnable)
static Scheduler & instance()
bool acquire(bool recurse=false, bool safe=true)
void exit(uintptr_t ra=0)
void setUnwindState(UnwindType ut)
@ Continue
No unwind necessary, carry on as normal.
@ TerminateThread
Exit only this thread during Process exit.
bool getWaitDebugInfo(WaitDebugInfo &info)
UnwindType getUnwindState()
DebugState getDebugState(uintptr_t &address)
bool isTerminationDeferred() const