8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/AdmittedThread.h"
11#include "pedigree/kernel/process/OperationBarrier.h"
12#include "pedigree/kernel/process/OwnedThread.h"
13#include "pedigree/kernel/process/Process.h"
14#include "pedigree/kernel/process/Scheduler.h"
15#include "pedigree/kernel/process/Semaphore.h"
16#include "pedigree/kernel/process/TerminationDeferral.h"
17#include "pedigree/kernel/process/Thread.h"
18#include "pedigree/kernel/processor/Processor.h"
19#include "pedigree/kernel/utilities/UniqueResource.h"
22constexpr size_t Attempts = 10000;
24bool check(
bool condition,
const char* detail) {
28 ERROR(
"HOSTED-WAIT-TEST: FAIL lifetime-leases: " << detail);
32class ObservedProcess :
public Process {
35 :
Process(DeferredPublication(), parent), m_Destroyed(destroyed) {
39 ~ObservedProcess()
override {
45 void prepareForDestructionForHostedTest() {
53class ObservedThread :
public Thread {
56 bool delayedStart =
false,
void* parameter =
nullptr)
57 :
Thread(parent, entry, parameter, nullptr, false, true, delayedStart),
58 m_Destroyed(destroyed) {}
60 ~ObservedThread()
override {
68struct OpenFinalLeaseContext {
69 explicit OpenFinalLeaseContext(
Process* parent)
98struct OpenFinalProcessLeaseContext {
99 explicit OpenFinalProcessLeaseContext(
Process* process)
128struct ClosedFinalProcessLeaseContext {
129 explicit ClosedFinalProcessLeaseContext(ObservedProcess* process)
136 drainWaitObserved(0),
137 earlyReturnObserved(0),
143 ObservedProcess* process;
157OpenFinalLeaseContext* g_OpenFinalLeaseContext =
nullptr;
158OpenFinalProcessLeaseContext* g_OpenFinalProcessLeaseContext =
nullptr;
159ClosedFinalProcessLeaseContext* g_ClosedFinalProcessLeaseContext =
nullptr;
161bool waitForLeaseDrain(
Thread*
waiter,
const void* owner);
163int blockedOpenFinalLeaseTarget(
void* parameter) {
164 OpenFinalLeaseContext* context =
reinterpret_cast<OpenFinalLeaseContext*
>(parameter);
165 context->entered += 1;
166 if (!context->exitGate.acquire()) {
167 context->failures += 1;
169 context->returned += 1;
173void observeOpenFinalLeaseRelease(
Thread* target, Thread::ExternalLeaseReleasePhase phase) {
174 OpenFinalLeaseContext* context = __atomic_load_n(&g_OpenFinalLeaseContext, __ATOMIC_ACQUIRE);
175 if (!context || target != context->target) {
179 if (phase == Thread::ExternalLeaseBeforeWaiterWake) {
180 context->wakeAttempts += 1;
184 context->hookCalls += 1;
185 if (!context->parent->acquireThread(context->safetyLease, target)) {
186 context->failures += 1;
190 context->safetyAcquired += 1;
191 context->gateReleased += 1;
192 context->exitGate.release();
193 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
194 if (target->isReapableForHostedTest()) {
195 context->reapableObserved += 1;
200 context->failures += 1;
203int deleteOpenFinalLeasedProcess(
void* parameter) {
204 OpenFinalProcessLeaseContext* context =
205 reinterpret_cast<OpenFinalProcessLeaseContext*
>(parameter);
207 context->entered += 1;
208 if (!context->deleteGate.acquire()) {
209 context->failures += 1;
211 delete context->process;
212 context->finished += 1;
216void observeOpenFinalProcessLeaseRelease(
Process* process,
217 Process::ExternalLeaseReleasePhase phase) {
218 OpenFinalProcessLeaseContext* context =
219 __atomic_load_n(&g_OpenFinalProcessLeaseContext, __ATOMIC_ACQUIRE);
220 if (!context || process != context->process) {
224 if (phase == Process::ExternalLeaseBeforeWaiterWake) {
225 context->wakeAttempts += 1;
229 context->hookCalls += 1;
231 context->failures += 1;
235 context->safetyAcquired += 1;
236 context->gateReleased += 1;
237 context->deleteGate.release();
238 if (waitForLeaseDrain(context->deleter, process)) {
239 context->drainObserved += 1;
241 context->failures += 1;
245int drainClosedFinalLeasedProcess(
void* parameter) {
246 ClosedFinalProcessLeaseContext* context =
247 reinterpret_cast<ClosedFinalProcessLeaseContext*
>(parameter);
249 context->entered += 1;
250 if (!context->beginDrain.acquire()) {
251 context->failures += 1;
253 context->process->prepareForDestructionForHostedTest();
254 context->returned += 1;
258void observeClosedFinalProcessLeaseRelease(
Process* process,
259 Process::ExternalLeaseReleasePhase phase) {
260 ClosedFinalProcessLeaseContext* context =
261 __atomic_load_n(&g_ClosedFinalProcessLeaseContext, __ATOMIC_ACQUIRE);
262 if (!context || process != context->process) {
266 if (phase == Process::ExternalLeaseBeforeWaiterWake) {
267 context->wakeAttempts += 1;
271 context->hookCalls += 1;
272 context->gateReleased += 1;
273 context->beginDrain.release();
274 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
275 if (context->returned) {
276 context->earlyReturnObserved += 1;
281 if (context->drainer->getWaitDebugInfo(info) && info.queued && info.channelOwner == process &&
282 context->drainer->getStatus() == Thread::Sleeping) {
283 context->drainWaitObserved += 1;
286 if (context->drainer->getStatus() == Thread::Zombie) {
287 context->earlyReturnObserved += 1;
292 context->failures += 1;
295struct ProcessDeleteContext {
297 : process(process), destroyed(destroyed), entered(0), finished(0) {}
305struct OwnedWorkerContext {
313 destructedBeforeJoin(0),
326struct AdmittedThreadResourceProbe {
329 : barrier(barrier), releases(releases), releasesBeforeDrain(releasesBeforeDrain) {}
336struct AdmittedThreadProbeReleaser {
337 static void release(AdmittedThreadResourceProbe* resource) {
338 *resource->releases += 1;
339 if (!resource->barrier->isClosedAndDrained()) {
340 *resource->releasesBeforeDrain += 1;
345using AdmittedThreadProbeOwner =
348struct AdmittedThreadWorkerContext {
349 explicit AdmittedThreadWorkerContext(AdmittedThreadProbeOwner&& resource)
351 resource(pedigree_std::move(resource)),
358 AdmittedThreadProbeOwner resource;
365struct AdmittedThreadCancelContext {
367 : barrier(barrier), entered(0), cancelled(0), cancelledBeforeDrain(0) {}
375class AdmittedThreadStackCanary {
377 explicit AdmittedThreadStackCanary(AdmittedThreadWorkerContext* context) : m_Context(context) {}
379 ~AdmittedThreadStackCanary() {
380 m_Context->destructed += 1;
384 AdmittedThreadWorkerContext* m_Context;
387class OwnedWorkerStackCanary {
389 explicit OwnedWorkerStackCanary(OwnedWorkerContext* context) : m_Context(context) {}
391 ~OwnedWorkerStackCanary() {
392 m_Context->destructed += 1;
396 OwnedWorkerContext* m_Context;
399OwnedWorkerContext* g_OwnedWorkerContext =
nullptr;
402 if (g_OwnedWorkerContext && target == g_OwnedWorkerContext->worker) {
403 if (g_OwnedWorkerContext->destructed == 1) {
404 g_OwnedWorkerContext->destructedBeforeJoin += 1;
406 g_OwnedWorkerContext->joins += 1;
410int blockedOwnedWorker(
void* parameter) {
411 OwnedWorkerContext* context =
reinterpret_cast<OwnedWorkerContext*
>(parameter);
412 OwnedWorkerStackCanary stackCanary(context);
413 context->entered += 1;
414 context->waitInterrupted = context->gate.acquire() ? 0 : 1;
415 context->returnedPastWait += 1;
419int blockedAdmittedThreadWorker(
void* parameter) {
420 AdmittedThreadWorkerContext* context =
reinterpret_cast<AdmittedThreadWorkerContext*
>(parameter);
421 AdmittedThreadStackCanary stackCanary(context);
422 AdmittedThreadProbeOwner resource = pedigree_std::move(context->resource);
424 context->entered += 1;
425 context->gate.acquire();
426 context->returned += 1;
430int unstartedAdmittedThreadWorker(
void* parameter) {
431 AdmittedThreadCancelContext* context =
reinterpret_cast<AdmittedThreadCancelContext*
>(parameter);
432 context->entered += 1;
436void cancelUnstartedAdmittedThread(
void* parameter) {
437 AdmittedThreadCancelContext* context =
reinterpret_cast<AdmittedThreadCancelContext*
>(parameter);
438 context->cancelled += 1;
439 if (!context->barrier->isOpen() && !context->barrier->isClosedAndDrained()) {
440 context->cancelledBeforeDrain += 1;
444void terminateAdmittedThreadBeforeStart(
Thread* thread,
void* parameter) {
447 thread->waitUntilReapableForHostedTest();
450int deleteLeasedProcess(
void* parameter) {
451 ProcessDeleteContext* context =
reinterpret_cast<ProcessDeleteContext*
>(parameter);
456 context->entered += 1;
457 delete context->process;
458 context->finished += 1;
462int immediateExit(
void*) {
466bool waitForLeaseDrain(
Thread*
waiter,
const void* owner) {
467 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
469 if (
waiter->getWaitDebugInfo(info) && info.queued && info.channelOwner == owner &&
470 waiter->getStatus() == Thread::Sleeping) {
478bool ownedThreadTerminalJoin(
Process* kernelProcess) {
479 OwnedWorkerContext context;
480 bool waiting =
false;
483 new Thread(kernelProcess, blockedOwnedWorker, &context,
nullptr,
false,
true));
484 worker->setName(
"hosted owned blocked worker");
485 context.worker =
worker.get();
486 waiting = waitForLeaseDrain(context.worker, &context.gate);
488 g_OwnedWorkerContext = &context;
489 Thread::setJoinOperationHook(observeOwnedWorkerJoin);
491 Thread::setJoinOperationHook(
nullptr);
492 g_OwnedWorkerContext =
nullptr;
494 const bool passed = check(waiting && context.entered == 1 && context.waitInterrupted == 1 &&
495 context.returnedPastWait == 1 && context.destructed == 1 &&
496 context.destructedBeforeJoin == 1 && context.joins == 1,
497 "OwnedThread did not let its blocked worker unwind and join "
500 NOTICE(
"HOSTED-WAIT-TEST: PASS owned-thread-terminal-join");
505bool idleOnlyAdmissionClose() {
510 "busy close changed operation admission");
512 passed &= check(!barrier.
tryCloseIfIdle(),
"busy close lost a remaining admission");
516 "idle close did not prevent subsequent admission");
519 NOTICE(
"HOSTED-WAIT-TEST: PASS idle-only-admission-close");
524bool admittedThreadTerminalReleaseOrder() {
528 AdmittedThreadResourceProbe resource(&barrier, &releases, &releasesBeforeDrain);
529 AdmittedThreadWorkerContext context(AdmittedThreadProbeOwner::adopt(&resource));
531 barrier,
"hosted admitted thread worker"),
532 "could not launch admitted thread");
535 for (
size_t attempt = 0; attempt < Attempts && !context.worker; ++attempt) {
538 worker =
reinterpret_cast<Thread*
>(context.worker.value());
540 const bool waiting =
worker && waitForLeaseDrain(
worker, &context.gate);
542 passed &= check(waiting && context.entered == 1 && !barrier.isClosedAndDrained(),
543 "kernel trampoline did not retain admission while worker was blocked");
550 passed &= check(context.returned == 1 && context.destructed == 1 && releases == 1 &&
551 releasesBeforeDrain == 1 && barrier.isClosedAndDrained(),
552 "kernel trampoline drained before module-style worker cleanup returned");
554 NOTICE(
"HOSTED-WAIT-TEST: PASS admitted-thread-terminal-release-order");
559bool admittedThreadPreStartCancellation() {
561 AdmittedThreadCancelContext context(&barrier);
562 AdmittedThread::setBeforeStartHookForTest(terminateAdmittedThreadBeforeStart, &barrier);
564 cancelUnstartedAdmittedThread, barrier,
565 "hosted unstarted admitted thread");
566 AdmittedThread::setBeforeStartHookForTest(
nullptr,
nullptr);
570 const bool passed = check(launched && context.entered == 0 && context.cancelled == 1 &&
571 context.cancelledBeforeDrain == 1 && barrier.isClosedAndDrained(),
572 "admitted pre-start cancellation did not retire ownership once");
574 NOTICE(
"HOSTED-WAIT-TEST: PASS admitted-thread-pre-start-cancellation");
579bool processLeaseBarrier(
Process* kernelProcess) {
581 Process* process =
new ObservedProcess(kernelProcess, &destroyed);
585 check(leaseAcquired &&
static_cast<bool>(lease),
"could not acquire the process lease");
587 ProcessDeleteContext context(process, &destroyed);
588 Thread* deleter =
new Thread(kernelProcess, deleteLeasedProcess, &context,
nullptr,
false,
true);
589 deleter->setName(
"hosted ProcessLease deleter");
591 const bool draining = waitForLeaseDrain(deleter, process);
592 passed &= check(draining && context.entered == 1 && destroyed == 0,
593 "a held ProcessLease did not stop derived destruction");
597 passed &= check(!lateLeaseAcquired && !lateLease,
598 "Process destruction admitted a lease after scheduler removal");
601 for (
size_t attempt = 0; attempt < 32; ++attempt) {
604 passed &= check(context.finished == 0 && destroyed == 0,
605 "terminal wake abandoned the ProcessLease completion drain");
608 for (
size_t attempt = 0; attempt < Attempts && !context.finished; ++attempt) {
611 passed &= check(context.finished == 1 && destroyed == 1,
612 "Process deletion did not complete after its final lease released");
613 passed &= check(deleter->
joinForCompletion(),
"the ProcessLease deleter did not retire");
617bool openFinalProcessLeaseRelease(
Process* kernelProcess) {
618 OpenFinalProcessLeaseContext context(
nullptr);
619 ObservedProcess* process =
new ObservedProcess(kernelProcess, &context.destroyed);
620 context.process = process;
624 if (!leaseAcquired) {
626 return check(
false,
"could not acquire the open-admission ProcessLease");
629 Thread* deleter =
new Thread(kernelProcess, deleteOpenFinalLeasedProcess, &context);
630 deleter->setName(
"hosted open final ProcessLease deleter");
631 context.deleter = deleter;
632 const bool waiting = waitForLeaseDrain(deleter, &context.deleteGate);
634 context.deleteGate.release();
637 FATAL(
"Open final ProcessLease cleanup could not join its deleter");
639 return check(
false,
"could not prepare the open-admission ProcessLease release window");
642 __atomic_store_n(&g_OpenFinalProcessLeaseContext, &context, __ATOMIC_RELEASE);
643 Process::setExternalLeaseReleaseHookForHostedTest(process, observeOpenFinalProcessLeaseRelease);
645 Process::setExternalLeaseReleaseHookForHostedTest(
nullptr,
nullptr);
646 __atomic_store_n(&g_OpenFinalProcessLeaseContext,
647 static_cast<OpenFinalProcessLeaseContext*
>(
nullptr), __ATOMIC_RELEASE);
649 if (!context.gateReleased) {
650 context.deleteGate.release();
652 context.safetyLease.reset();
655 const bool passed = check(
656 joined && context.entered == 1 && context.finished == 1 && context.hookCalls == 1 &&
657 context.safetyAcquired == 1 && context.gateReleased == 1 && context.drainObserved == 1 &&
658 context.wakeAttempts == 0 && !context.failures && context.destroyed == 1,
659 "an open final ProcessLease release touched its target after the predicate unlock");
661 NOTICE(
"HOSTED-WAIT-TEST: PASS process-final-open-lease-release");
666bool closedFinalProcessLeaseHandoff(
Process* kernelProcess) {
667 ClosedFinalProcessLeaseContext context(
nullptr);
668 ObservedProcess* process =
new ObservedProcess(kernelProcess, &context.destroyed);
669 context.process = process;
674 return check(
false,
"could not acquire the closed-handoff ProcessLease");
677 Thread* drainer =
new Thread(kernelProcess, drainClosedFinalLeasedProcess, &context);
678 drainer->setName(
"hosted closed final ProcessLease drainer");
679 context.drainer = drainer;
680 if (!waitForLeaseDrain(drainer, &context.beginDrain)) {
681 context.beginDrain.release();
684 FATAL(
"Closed final ProcessLease cleanup could not join its drainer");
687 return check(
false,
"could not prepare the closed final ProcessLease handoff");
693 __atomic_store_n(&g_ClosedFinalProcessLeaseContext, &context, __ATOMIC_RELEASE);
694 Process::setExternalLeaseReleaseHookForHostedTest(process, observeClosedFinalProcessLeaseRelease);
696 Process::setExternalLeaseReleaseHookForHostedTest(
nullptr,
nullptr);
697 __atomic_store_n(&g_ClosedFinalProcessLeaseContext,
698 static_cast<ClosedFinalProcessLeaseContext*
>(
nullptr), __ATOMIC_RELEASE);
700 if (!context.gateReleased) {
701 context.beginDrain.release();
703 bool drainerReapable =
false;
704 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
705 if (drainer->isReapableForHostedTest()) {
706 drainerReapable =
true;
712 FATAL(
"Closed final ProcessLease drainer did not retire after handoff");
715 bool passed = check(context.entered == 1 && context.returned == 1 && context.hookCalls == 1 &&
716 context.drainWaitObserved == 1 && !context.earlyReturnObserved &&
717 context.gateReleased == 1 && context.wakeAttempts == 1 &&
718 !context.failures && context.destroyed == 0,
719 "a closed final ProcessLease did not pin its pre-enrolment waiter handoff");
721 passed &= check(context.destroyed == 1,
722 "the closed final ProcessLease fixture did not destroy its process once");
724 NOTICE(
"HOSTED-WAIT-TEST: PASS process-final-closed-lease-handoff");
729bool threadLeaseIdLookup(
Process* kernelProcess) {
731 Thread* first =
new ObservedThread(kernelProcess, immediateExit, &destroyed,
true);
732 Thread* second =
new ObservedThread(kernelProcess, immediateExit, &destroyed,
true);
733 first->setName(
"hosted ThreadLease ID first target");
734 second->setName(
"hosted ThreadLease ID second target");
738 bool passed = check(firstAcquired && lease.get() == first,
739 "exact thread ID lookup did not acquire its target");
742 passed &= check(secondAcquired && lease.get() == second,
743 "exact thread ID lookup did not replace an active lease");
745 const bool missingAcquired = kernelProcess->
acquireThreadById(lease, ~
static_cast<size_t>(0));
747 check(!missingAcquired && !lease,
"missing thread ID lookup did not reset an active lease");
753 passed &= check(firstJoined && secondJoined && destroyed == 2,
754 "thread ID lookup fixtures did not retire cleanly");
756 NOTICE(
"HOSTED-WAIT-TEST: PASS thread-lease-id-lookup");
761bool threadLeaseBarrier(
Process* kernelProcess) {
763 Thread* target =
new ObservedThread(kernelProcess, immediateExit, &destroyed,
true);
764 target->setName(
"hosted ThreadLease target");
771 check(leaseAcquired && lease.get() == target,
"could not acquire the thread lease by ID");
778 passed &= check(target->
detach(),
"the ThreadLease target could not detach");
779 passed &= check(target->
start(),
"the delayed ThreadLease target did not start");
781 bool retirementClosed =
false;
782 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
785 retirementClosed =
true;
792 passed &= check(retirementClosed,
"Thread retirement admitted a lease after closing");
793 passed &= check(destroyed == 0,
"a held ThreadLease did not stop detached target deletion");
796 for (
size_t attempt = 0; attempt < Attempts && !destroyed; ++attempt) {
800 check(destroyed == 1,
"Thread deletion did not complete after its final lease released");
802 NOTICE(
"HOSTED-WAIT-TEST: PASS thread-final-lease-deletion");
807bool openFinalThreadLeaseRelease(
Process* kernelProcess) {
808 OpenFinalLeaseContext context(kernelProcess);
809 Thread* target =
new ObservedThread(kernelProcess, blockedOpenFinalLeaseTarget,
810 &context.destroyed,
true, &context);
811 target->setName(
"hosted open final ThreadLease target");
812 context.target = target;
815 const bool leaseAcquired = kernelProcess->
acquireThread(lease, target);
816 if (!leaseAcquired) {
819 return check(
false,
"could not acquire the open-admission ThreadLease");
822 const bool started = target->
start();
823 const bool waiting = started && waitForLeaseDrain(target, &context.exitGate);
824 if (!started || !waiting) {
828 FATAL(
"Open final ThreadLease cleanup could not join its target");
830 return check(
false,
"could not prepare the open-admission final-release window");
837 FATAL(
"Open final ThreadLease cleanup could not join its undetached target");
839 return check(
false,
"could not detach the open-admission final-release target");
842 __atomic_store_n(&g_OpenFinalLeaseContext, &context, __ATOMIC_RELEASE);
843 Thread::setExternalLeaseReleaseHookForHostedTest(target, observeOpenFinalLeaseRelease);
845 Thread::setExternalLeaseReleaseHookForHostedTest(
nullptr,
nullptr);
846 __atomic_store_n(&g_OpenFinalLeaseContext,
static_cast<OpenFinalLeaseContext*
>(
nullptr),
849 if (!context.gateReleased) {
850 context.exitGate.release();
852 context.safetyLease.reset();
853 for (
size_t attempt = 0; attempt < Attempts && !context.destroyed; ++attempt) {
856 if (!context.destroyed) {
857 FATAL(
"Open final ThreadLease target did not complete detached retirement");
861 check(context.entered == 1 && context.returned == 1 && context.hookCalls == 1 &&
862 context.safetyAcquired == 1 && context.gateReleased == 1 &&
863 context.reapableObserved == 1 && context.wakeAttempts == 0 && !context.failures &&
864 context.destroyed == 1,
865 "an open final ThreadLease release touched its target after the predicate unlock");
867 NOTICE(
"HOSTED-WAIT-TEST: PASS thread-final-open-lease-release");
873bool runHostedLifetimeLeaseRegressions() {
876 idleOnlyAdmissionClose() && admittedThreadPreStartCancellation() &&
877 admittedThreadTerminalReleaseOrder() && ownedThreadTerminalJoin(kernelProcess) &&
878 processLeaseBarrier(kernelProcess) && openFinalProcessLeaseRelease(kernelProcess) &&
879 closedFinalProcessLeaseHandoff(kernelProcess) && threadLeaseIdLookup(kernelProcess) &&
880 threadLeaseBarrier(kernelProcess) && openFinalThreadLeaseRelease(kernelProcess);
882 NOTICE(
"HOSTED-WAIT-TEST: PASS lifetime-leases");
static MUST_USE_RESULT bool launchDetached(Entry entry, void *parameter, Cancel cancel, OperationBarrier &barrier, const char *name=nullptr)
MUST_USE_RESULT bool tryCloseIfIdle()
MUST_USE_RESULT bool tryAcquire(Lease &lease)
MUST_USE_RESULT bool acquireThreadById(ThreadLease &lease, size_t id)
MUST_USE_RESULT bool acquireThread(ThreadLease &lease, size_t n)
void prepareForDestruction()
static ProcessorInformation & information()
static Scheduler & instance()
MUST_USE_RESULT bool acquireProcess(ProcessLease &lease, size_t n)
void removeProcess(Process *pProcess)
void setUnwindState(UnwindType ut)
@ TerminateThread
Exit only this thread during Process exit.