8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/OperationBarrier.h"
11#include "pedigree/kernel/process/OwnedThread.h"
12#include "pedigree/kernel/process/Process.h"
13#include "pedigree/kernel/process/Scheduler.h"
14#include "pedigree/kernel/process/Semaphore.h"
15#include "pedigree/kernel/process/TerminationDeferral.h"
16#include "pedigree/kernel/process/Thread.h"
17#include "pedigree/kernel/processor/Processor.h"
18#include "pedigree/kernel/utilities/UniqueResource.h"
21constexpr size_t Attempts = 10000;
23bool check(
bool condition,
const char* detail) {
27 ERROR(
"HOSTED-WAIT-TEST: FAIL lifetime-leases: " << detail);
31class ObservedProcess :
public Process {
34 :
Process(DeferredPublication(), parent), m_Destroyed(destroyed) {
38 ~ObservedProcess()
override {
44 void prepareForDestructionForHostedTest() {
52class ObservedThread :
public Thread {
55 bool delayedStart =
false,
void* parameter =
nullptr)
56 :
Thread(parent, entry, parameter, nullptr, false, true, delayedStart),
57 m_Destroyed(destroyed) {}
59 ~ObservedThread()
override {
67struct OpenFinalLeaseContext {
68 explicit OpenFinalLeaseContext(
Process* parent)
97struct OpenFinalProcessLeaseContext {
98 explicit OpenFinalProcessLeaseContext(
Process* process)
127struct ClosedFinalProcessLeaseContext {
128 explicit ClosedFinalProcessLeaseContext(ObservedProcess* process)
135 drainWaitObserved(0),
136 earlyReturnObserved(0),
142 ObservedProcess* process;
156OpenFinalLeaseContext* g_OpenFinalLeaseContext =
nullptr;
157OpenFinalProcessLeaseContext* g_OpenFinalProcessLeaseContext =
nullptr;
158ClosedFinalProcessLeaseContext* g_ClosedFinalProcessLeaseContext =
nullptr;
160bool waitForLeaseDrain(
Thread*
waiter,
const void* owner);
162int blockedOpenFinalLeaseTarget(
void* parameter) {
163 OpenFinalLeaseContext* context =
reinterpret_cast<OpenFinalLeaseContext*
>(parameter);
164 context->entered += 1;
165 if (!context->exitGate.acquire()) {
166 context->failures += 1;
168 context->returned += 1;
172void observeOpenFinalLeaseRelease(
Thread* target, Thread::ExternalLeaseReleasePhase phase) {
173 OpenFinalLeaseContext* context = __atomic_load_n(&g_OpenFinalLeaseContext, __ATOMIC_ACQUIRE);
174 if (!context || target != context->target) {
178 if (phase == Thread::ExternalLeaseBeforeWaiterWake) {
179 context->wakeAttempts += 1;
183 context->hookCalls += 1;
184 if (!context->parent->acquireThread(context->safetyLease, target)) {
185 context->failures += 1;
189 context->safetyAcquired += 1;
190 context->gateReleased += 1;
191 context->exitGate.release();
192 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
193 if (target->isReapableForHostedTest()) {
194 context->reapableObserved += 1;
199 context->failures += 1;
202int deleteOpenFinalLeasedProcess(
void* parameter) {
203 OpenFinalProcessLeaseContext* context =
204 reinterpret_cast<OpenFinalProcessLeaseContext*
>(parameter);
206 context->entered += 1;
207 if (!context->deleteGate.acquire()) {
208 context->failures += 1;
210 delete context->process;
211 context->finished += 1;
215void observeOpenFinalProcessLeaseRelease(
Process* process,
216 Process::ExternalLeaseReleasePhase phase) {
217 OpenFinalProcessLeaseContext* context =
218 __atomic_load_n(&g_OpenFinalProcessLeaseContext, __ATOMIC_ACQUIRE);
219 if (!context || process != context->process) {
223 if (phase == Process::ExternalLeaseBeforeWaiterWake) {
224 context->wakeAttempts += 1;
228 context->hookCalls += 1;
230 context->failures += 1;
234 context->safetyAcquired += 1;
235 context->gateReleased += 1;
236 context->deleteGate.release();
237 if (waitForLeaseDrain(context->deleter, process)) {
238 context->drainObserved += 1;
240 context->failures += 1;
244int drainClosedFinalLeasedProcess(
void* parameter) {
245 ClosedFinalProcessLeaseContext* context =
246 reinterpret_cast<ClosedFinalProcessLeaseContext*
>(parameter);
248 context->entered += 1;
249 if (!context->beginDrain.acquire()) {
250 context->failures += 1;
252 context->process->prepareForDestructionForHostedTest();
253 context->returned += 1;
257void observeClosedFinalProcessLeaseRelease(
Process* process,
258 Process::ExternalLeaseReleasePhase phase) {
259 ClosedFinalProcessLeaseContext* context =
260 __atomic_load_n(&g_ClosedFinalProcessLeaseContext, __ATOMIC_ACQUIRE);
261 if (!context || process != context->process) {
265 if (phase == Process::ExternalLeaseBeforeWaiterWake) {
266 context->wakeAttempts += 1;
270 context->hookCalls += 1;
271 context->gateReleased += 1;
272 context->beginDrain.release();
273 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
274 if (context->returned) {
275 context->earlyReturnObserved += 1;
280 if (context->drainer->getWaitDebugInfo(info) && info.queued && info.channelOwner == process &&
281 context->drainer->getStatus() == Thread::Sleeping) {
282 context->drainWaitObserved += 1;
285 if (context->drainer->getStatus() == Thread::Zombie) {
286 context->earlyReturnObserved += 1;
291 context->failures += 1;
294struct ProcessDeleteContext {
296 : process(process), destroyed(destroyed), entered(0), finished(0) {}
304struct OwnedWorkerContext {
312 destructedBeforeJoin(0),
325class OwnedWorkerStackCanary {
327 explicit OwnedWorkerStackCanary(OwnedWorkerContext* context) : m_Context(context) {}
329 ~OwnedWorkerStackCanary() {
330 m_Context->destructed += 1;
334 OwnedWorkerContext* m_Context;
337OwnedWorkerContext* g_OwnedWorkerContext =
nullptr;
340 if (g_OwnedWorkerContext && target == g_OwnedWorkerContext->worker) {
341 if (g_OwnedWorkerContext->destructed == 1) {
342 g_OwnedWorkerContext->destructedBeforeJoin += 1;
344 g_OwnedWorkerContext->joins += 1;
348int blockedOwnedWorker(
void* parameter) {
349 OwnedWorkerContext* context =
reinterpret_cast<OwnedWorkerContext*
>(parameter);
350 OwnedWorkerStackCanary stackCanary(context);
351 context->entered += 1;
352 context->waitInterrupted = context->gate.acquire() ? 0 : 1;
353 context->returnedPastWait += 1;
357int deleteLeasedProcess(
void* parameter) {
358 ProcessDeleteContext* context =
reinterpret_cast<ProcessDeleteContext*
>(parameter);
363 context->entered += 1;
364 delete context->process;
365 context->finished += 1;
369int immediateExit(
void*) {
373bool waitForLeaseDrain(
Thread*
waiter,
const void* owner) {
374 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
376 if (
waiter->getWaitDebugInfo(info) && info.queued && info.channelOwner == owner &&
377 waiter->getStatus() == Thread::Sleeping) {
385bool ownedThreadTerminalJoin(
Process* kernelProcess) {
386 OwnedWorkerContext context;
387 bool waiting =
false;
390 new Thread(kernelProcess, blockedOwnedWorker, &context,
nullptr,
false,
true));
391 worker->setName(
"hosted owned blocked worker");
392 context.worker =
worker.get();
393 waiting = waitForLeaseDrain(context.worker, &context.gate);
395 g_OwnedWorkerContext = &context;
396 Thread::setJoinOperationHook(observeOwnedWorkerJoin);
398 Thread::setJoinOperationHook(
nullptr);
399 g_OwnedWorkerContext =
nullptr;
401 const bool passed = check(waiting && context.entered == 1 && context.waitInterrupted == 1 &&
402 context.returnedPastWait == 1 && context.destructed == 1 &&
403 context.destructedBeforeJoin == 1 && context.joins == 1,
404 "OwnedThread did not let its blocked worker unwind and join "
407 NOTICE(
"HOSTED-WAIT-TEST: PASS owned-thread-terminal-join");
412bool idleOnlyAdmissionClose() {
417 "busy close changed operation admission");
419 passed &= check(!barrier.
tryCloseIfIdle(),
"busy close lost a remaining admission");
423 "idle close did not prevent subsequent admission");
426 NOTICE(
"HOSTED-WAIT-TEST: PASS idle-only-admission-close");
431bool processLeaseBarrier(
Process* kernelProcess) {
433 Process* process =
new ObservedProcess(kernelProcess, &destroyed);
437 check(leaseAcquired &&
static_cast<bool>(lease),
"could not acquire the process lease");
439 ProcessDeleteContext context(process, &destroyed);
440 Thread* deleter =
new Thread(kernelProcess, deleteLeasedProcess, &context,
nullptr,
false,
true);
441 deleter->setName(
"hosted ProcessLease deleter");
443 const bool draining = waitForLeaseDrain(deleter, process);
444 passed &= check(draining && context.entered == 1 && destroyed == 0,
445 "a held ProcessLease did not stop derived destruction");
449 passed &= check(!lateLeaseAcquired && !lateLease,
450 "Process destruction admitted a lease after scheduler removal");
453 for (
size_t attempt = 0; attempt < 32; ++attempt) {
456 passed &= check(context.finished == 0 && destroyed == 0,
457 "terminal wake abandoned the ProcessLease completion drain");
460 for (
size_t attempt = 0; attempt < Attempts && !context.finished; ++attempt) {
463 passed &= check(context.finished == 1 && destroyed == 1,
464 "Process deletion did not complete after its final lease released");
465 passed &= check(deleter->
joinForCompletion(),
"the ProcessLease deleter did not retire");
469bool openFinalProcessLeaseRelease(
Process* kernelProcess) {
470 OpenFinalProcessLeaseContext context(
nullptr);
471 ObservedProcess* process =
new ObservedProcess(kernelProcess, &context.destroyed);
472 context.process = process;
476 if (!leaseAcquired) {
478 return check(
false,
"could not acquire the open-admission ProcessLease");
481 Thread* deleter =
new Thread(kernelProcess, deleteOpenFinalLeasedProcess, &context);
482 deleter->setName(
"hosted open final ProcessLease deleter");
483 context.deleter = deleter;
484 const bool waiting = waitForLeaseDrain(deleter, &context.deleteGate);
486 context.deleteGate.release();
489 FATAL(
"Open final ProcessLease cleanup could not join its deleter");
491 return check(
false,
"could not prepare the open-admission ProcessLease release window");
494 __atomic_store_n(&g_OpenFinalProcessLeaseContext, &context, __ATOMIC_RELEASE);
495 Process::setExternalLeaseReleaseHookForHostedTest(process, observeOpenFinalProcessLeaseRelease);
497 Process::setExternalLeaseReleaseHookForHostedTest(
nullptr,
nullptr);
498 __atomic_store_n(&g_OpenFinalProcessLeaseContext,
499 static_cast<OpenFinalProcessLeaseContext*
>(
nullptr), __ATOMIC_RELEASE);
501 if (!context.gateReleased) {
502 context.deleteGate.release();
504 context.safetyLease.reset();
507 const bool passed = check(
508 joined && context.entered == 1 && context.finished == 1 && context.hookCalls == 1 &&
509 context.safetyAcquired == 1 && context.gateReleased == 1 && context.drainObserved == 1 &&
510 context.wakeAttempts == 0 && !context.failures && context.destroyed == 1,
511 "an open final ProcessLease release touched its target after the predicate unlock");
513 NOTICE(
"HOSTED-WAIT-TEST: PASS process-final-open-lease-release");
518bool closedFinalProcessLeaseHandoff(
Process* kernelProcess) {
519 ClosedFinalProcessLeaseContext context(
nullptr);
520 ObservedProcess* process =
new ObservedProcess(kernelProcess, &context.destroyed);
521 context.process = process;
526 return check(
false,
"could not acquire the closed-handoff ProcessLease");
529 Thread* drainer =
new Thread(kernelProcess, drainClosedFinalLeasedProcess, &context);
530 drainer->setName(
"hosted closed final ProcessLease drainer");
531 context.drainer = drainer;
532 if (!waitForLeaseDrain(drainer, &context.beginDrain)) {
533 context.beginDrain.release();
536 FATAL(
"Closed final ProcessLease cleanup could not join its drainer");
539 return check(
false,
"could not prepare the closed final ProcessLease handoff");
545 __atomic_store_n(&g_ClosedFinalProcessLeaseContext, &context, __ATOMIC_RELEASE);
546 Process::setExternalLeaseReleaseHookForHostedTest(process, observeClosedFinalProcessLeaseRelease);
548 Process::setExternalLeaseReleaseHookForHostedTest(
nullptr,
nullptr);
549 __atomic_store_n(&g_ClosedFinalProcessLeaseContext,
550 static_cast<ClosedFinalProcessLeaseContext*
>(
nullptr), __ATOMIC_RELEASE);
552 if (!context.gateReleased) {
553 context.beginDrain.release();
555 bool drainerReapable =
false;
556 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
557 if (drainer->isReapableForHostedTest()) {
558 drainerReapable =
true;
564 FATAL(
"Closed final ProcessLease drainer did not retire after handoff");
567 bool passed = check(context.entered == 1 && context.returned == 1 && context.hookCalls == 1 &&
568 context.drainWaitObserved == 1 && !context.earlyReturnObserved &&
569 context.gateReleased == 1 && context.wakeAttempts == 1 &&
570 !context.failures && context.destroyed == 0,
571 "a closed final ProcessLease did not pin its pre-enrolment waiter handoff");
573 passed &= check(context.destroyed == 1,
574 "the closed final ProcessLease fixture did not destroy its process once");
576 NOTICE(
"HOSTED-WAIT-TEST: PASS process-final-closed-lease-handoff");
581bool threadLeaseIdLookup(
Process* kernelProcess) {
583 Thread* first =
new ObservedThread(kernelProcess, immediateExit, &destroyed,
true);
584 Thread* second =
new ObservedThread(kernelProcess, immediateExit, &destroyed,
true);
585 first->setName(
"hosted ThreadLease ID first target");
586 second->setName(
"hosted ThreadLease ID second target");
590 bool passed = check(firstAcquired && lease.get() == first,
591 "exact thread ID lookup did not acquire its target");
594 passed &= check(secondAcquired && lease.get() == second,
595 "exact thread ID lookup did not replace an active lease");
597 const bool missingAcquired = kernelProcess->
acquireThreadById(lease, ~
static_cast<size_t>(0));
599 check(!missingAcquired && !lease,
"missing thread ID lookup did not reset an active lease");
605 passed &= check(firstJoined && secondJoined && destroyed == 2,
606 "thread ID lookup fixtures did not retire cleanly");
608 NOTICE(
"HOSTED-WAIT-TEST: PASS thread-lease-id-lookup");
613bool threadLeaseBarrier(
Process* kernelProcess) {
615 Thread* target =
new ObservedThread(kernelProcess, immediateExit, &destroyed,
true);
616 target->setName(
"hosted ThreadLease target");
623 check(leaseAcquired && lease.get() == target,
"could not acquire the thread lease by ID");
630 passed &= check(target->
detach(),
"the ThreadLease target could not detach");
631 passed &= check(target->
start(),
"the delayed ThreadLease target did not start");
633 bool retirementClosed =
false;
634 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
637 retirementClosed =
true;
644 passed &= check(retirementClosed,
"Thread retirement admitted a lease after closing");
645 passed &= check(destroyed == 0,
"a held ThreadLease did not stop detached target deletion");
648 for (
size_t attempt = 0; attempt < Attempts && !destroyed; ++attempt) {
652 check(destroyed == 1,
"Thread deletion did not complete after its final lease released");
654 NOTICE(
"HOSTED-WAIT-TEST: PASS thread-final-lease-deletion");
659bool openFinalThreadLeaseRelease(
Process* kernelProcess) {
660 OpenFinalLeaseContext context(kernelProcess);
661 Thread* target =
new ObservedThread(kernelProcess, blockedOpenFinalLeaseTarget,
662 &context.destroyed,
true, &context);
663 target->setName(
"hosted open final ThreadLease target");
664 context.target = target;
667 const bool leaseAcquired = kernelProcess->
acquireThread(lease, target);
668 if (!leaseAcquired) {
671 return check(
false,
"could not acquire the open-admission ThreadLease");
674 const bool started = target->
start();
675 const bool waiting = started && waitForLeaseDrain(target, &context.exitGate);
676 if (!started || !waiting) {
680 FATAL(
"Open final ThreadLease cleanup could not join its target");
682 return check(
false,
"could not prepare the open-admission final-release window");
689 FATAL(
"Open final ThreadLease cleanup could not join its undetached target");
691 return check(
false,
"could not detach the open-admission final-release target");
694 __atomic_store_n(&g_OpenFinalLeaseContext, &context, __ATOMIC_RELEASE);
695 Thread::setExternalLeaseReleaseHookForHostedTest(target, observeOpenFinalLeaseRelease);
697 Thread::setExternalLeaseReleaseHookForHostedTest(
nullptr,
nullptr);
698 __atomic_store_n(&g_OpenFinalLeaseContext,
static_cast<OpenFinalLeaseContext*
>(
nullptr),
701 if (!context.gateReleased) {
702 context.exitGate.release();
704 context.safetyLease.reset();
705 for (
size_t attempt = 0; attempt < Attempts && !context.destroyed; ++attempt) {
708 if (!context.destroyed) {
709 FATAL(
"Open final ThreadLease target did not complete detached retirement");
713 check(context.entered == 1 && context.returned == 1 && context.hookCalls == 1 &&
714 context.safetyAcquired == 1 && context.gateReleased == 1 &&
715 context.reapableObserved == 1 && context.wakeAttempts == 0 && !context.failures &&
716 context.destroyed == 1,
717 "an open final ThreadLease release touched its target after the predicate unlock");
719 NOTICE(
"HOSTED-WAIT-TEST: PASS thread-final-open-lease-release");
725bool runHostedLifetimeLeaseRegressions() {
728 idleOnlyAdmissionClose() && ownedThreadTerminalJoin(kernelProcess) &&
729 processLeaseBarrier(kernelProcess) && openFinalProcessLeaseRelease(kernelProcess) &&
730 closedFinalProcessLeaseHandoff(kernelProcess) && threadLeaseIdLookup(kernelProcess) &&
731 threadLeaseBarrier(kernelProcess) && openFinalThreadLeaseRelease(kernelProcess);
733 NOTICE(
"HOSTED-WAIT-TEST: PASS lifetime-leases");
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 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.