8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/Mutex.h"
11#include "pedigree/kernel/process/PerProcessorScheduler.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/utilities/ZombieQueue.h"
20bool check(
bool condition,
const char* detail);
22bool schedulerContains(
Process* process) {
24 for (
size_t i = 0; i < count; ++i) {
33bool processLeaseReplacement(
Process* process) {
36 "the process-lease replacement fixture could not acquire its first lease");
40 "an active ProcessLease could not be replaced under a newer deferral");
41 passed &= check(lease.get() == process,
"ProcessLease replacement changed its target");
43 const bool invalidReplacement =
45 passed &= check(!invalidReplacement && !lease,
46 "a failed ProcessLease replacement did not reset the active lease");
50 "ProcessLease could not be reacquired after nested reset");
54 NOTICE(
"HOSTED-WAIT-TEST: PASS process-lease-active-replacement");
59struct PublicationProbe {
60 explicit PublicationProbe(
Process* owner)
61 : visibleDuringMemberConstruction(schedulerContains(owner)) {}
63 bool visibleDuringMemberConstruction;
66class DeferredHostedProcess :
public Process {
68 explicit DeferredHostedProcess(
Process* parent)
69 :
Process(DeferredPublication(), parent), probe(this) {}
71 ~DeferredHostedProcess()
override {
75 void finishAssembly() {
79 PublicationProbe probe;
82struct ProcessExitContext {
83 explicit ProcessExitContext(
Mutex* mutex)
84 : mutex(mutex), peerEntered(0), peerAcquiredMutex(0), unstartedPeerEntered(0) {}
92struct JoinReaperContext {
96 releaseJoiner(0, false),
115JoinReaperContext* g_JoinReaperContext =
nullptr;
117bool check(
bool condition,
const char* detail) {
122 ERROR(
"HOSTED-WAIT-TEST: FAIL process-exit-rendezvous: " << detail);
126bool checkJoinLease(
bool condition,
const char* detail) {
131 ERROR(
"HOSTED-WAIT-TEST: FAIL join-reaper-lease: " << detail);
135int blockedProcessPeer(
void* parameter) {
136 ProcessExitContext* context =
reinterpret_cast<ProcessExitContext*
>(parameter);
137 context->peerEntered += 1;
138 if (context->mutex->acquire()) {
139 context->peerAcquiredMutex += 1;
140 context->mutex->release();
145int unstartedProcessPeer(
void* parameter) {
146 ProcessExitContext* context =
reinterpret_cast<ProcessExitContext*
>(parameter);
147 context->unstartedPeerEntered += 1;
151int terminateChildProcess(
void* parameter) {
156int immediateThreadExit(
void*) {
161 JoinReaperContext* context = __atomic_load_n(&g_JoinReaperContext, __ATOMIC_ACQUIRE);
166 context->hookCalls += 1;
167 if (target != context->target || parent != context->process) {
168 context->hookFailures += 1;
172 if (!context->releaseJoiner.acquireForCompletion()) {
173 context->hookFailures += 1;
177int joinRaceTarget(
void* parameter) {
178 JoinReaperContext* context =
reinterpret_cast<JoinReaperContext*
>(parameter);
179 const bool joined = context->target->join();
180 context->joinSucceeded = joined ? 1 : 0;
181 context->joinReturned += 1;
185int reapJoinProcess(
void* parameter) {
186 JoinReaperContext* context =
reinterpret_cast<JoinReaperContext*
>(parameter);
187 context->reaperEntered += 1;
188 delete context->process;
189 context->processDeleted += 1;
193bool joinReaperLease(
Process* kernelProcess) {
194 constexpr size_t Attempts = 10000;
197 Thread* target =
new Thread(process, immediateThreadExit,
nullptr,
nullptr,
false,
true,
true);
198 target->setName(
"hosted join/reaper target");
200 Thread* retained =
new Thread(process, immediateThreadExit,
nullptr,
nullptr,
false,
true,
true);
201 retained->setName(
"hosted join/reaper retained target");
203 const bool targetStarted = target->
start();
204 const bool retainedStarted = retained->
start();
205 const bool targetsStarted = targetStarted && retainedStarted;
207 bool retainedReapable =
false;
208 for (
size_t attempt = 0; targetsStarted && attempt < Attempts; ++attempt) {
209 if (retained->isReapableForHostedTest()) {
210 retainedReapable =
true;
216 bool passed = checkJoinLease(targetsStarted && retainedReapable,
217 "the retained admission target did not become reapable");
218 if (targetsStarted && retainedReapable) {
219 NOTICE(
"HOSTED-WAIT-TEST: PASS scheduler-same-priority-progress");
221 if (!retainedReapable) {
225 JoinReaperContext context(process, target);
226 Thread* joiner =
new Thread(kernelProcess, joinRaceTarget, &context,
nullptr,
false,
true,
true);
227 joiner->setName(
"hosted join/reaper joiner");
229 __atomic_store_n(&g_JoinReaperContext, &context, __ATOMIC_RELEASE);
230 Thread::setJoinOperationHook(joinOperationHook);
232 passed &= checkJoinLease(joiner->
start(),
"the delayed joiner did not start");
234 bool joinerPinned =
false;
235 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
236 if (context.hookCalls) {
242 passed &= checkJoinLease(joinerPinned && context.hookFailures == 0,
243 "the joiner did not reach the leased pre-delete window");
245 Thread::setJoinOperationHook(
nullptr);
246 __atomic_store_n(&g_JoinReaperContext,
static_cast<JoinReaperContext*
>(
nullptr),
250 context.releaseJoiner.release();
251 passed &= checkJoinLease(joiner->
join(),
"the failed-window joiner did not retire");
252 passed &= checkJoinLease(retained->
join(),
"the retained target did not retire");
257 Thread* reaper =
new Thread(kernelProcess, reapJoinProcess, &context,
nullptr,
false,
true,
true);
258 reaper->setName(
"hosted join/reaper process reaper");
259 passed &= checkJoinLease(reaper->
start(),
"the delayed process reaper did not start");
261 bool reaperBlocked =
false;
262 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
264 if (context.reaperEntered && reaper->
getWaitDebugInfo(info) && info.queue && info.queued &&
265 info.channelOwner == process && reaper->
getStatus() == Thread::Sleeping) {
266 reaperBlocked =
true;
272 passed &= checkJoinLease(reaperBlocked && context.processDeleted == 0,
273 "Process destruction did not wait for the active join lease");
275 const bool lateJoin = retained->
join();
276 passed &= checkJoinLease(!lateJoin,
"Process destruction admitted a new join after closing");
278 context.releaseJoiner.release();
280 bool completed =
false;
281 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
282 if (context.joinReturned && context.processDeleted) {
288 passed &= checkJoinLease(completed && context.joinSucceeded == 1,
289 "the join lease did not release into orderly Process destruction");
290 passed &= checkJoinLease(joiner->
join(),
"the joiner thread did not retire");
291 passed &= checkJoinLease(reaper->
join(),
"the process reaper thread did not retire");
294 NOTICE(
"HOSTED-WAIT-TEST: PASS join-reaper-lease");
299class ExitElectionProcess :
public Process {
301 explicit ExitElectionProcess(
Process* parent)
302 :
Process(DeferredPublication(), parent),
305 cleanupBeforePeerReapable(0) {
309 ~ExitElectionProcess()
override {
319 if (competitor && !competitor->isReapableForHostedTest()) {
320 cleanupBeforePeerReapable += 1;
326struct ExitElectionContext {
327 explicit ExitElectionContext(ExitElectionProcess* process)
329 releaseCompetitor(0, false),
333 competitorEntered(0),
334 competitorElected(0),
335 resumeSawTerminating(0),
338 ExitElectionProcess* process;
349ExitElectionContext* g_ExitElectionContext =
nullptr;
352 ExitElectionContext* context = __atomic_load_n(&g_ExitElectionContext, __ATOMIC_ACQUIRE);
353 if (!context || process != context->process) {
357 context->hookCalls += 1;
360int competingProcessExit(
void* parameter) {
361 ExitElectionContext* context =
reinterpret_cast<ExitElectionContext*
>(parameter);
362 if (!context->releaseCompetitor.acquireForCompletion()) {
363 context->hookFailures += 1;
368 context->process->resume();
369 context->resumeSawTerminating = context->process->getState() == Process::Terminating ? 1 : 0;
371 const bool elected = context->process->beginTermination();
372 context->competitorElected = elected ? 1 : 0;
373 context->competitorEntered += 1;
374 context->process->competitor->getScheduler()->commitCurrentThreadExit();
377int owningProcessExit(
void* parameter) {
378 ExitElectionContext* context =
reinterpret_cast<ExitElectionContext*
>(parameter);
379 const bool elected = context->process->beginTermination();
380 context->ownerElected = elected ? 1 : 0;
381 if (!elected || !context->process->quiesceTermination()) {
382 context->hookFailures += 1;
383 context->owner->getScheduler()->commitCurrentThreadExit();
385 context->process->finishTermination();
388bool exitElectionQuiescence(
Process* kernelProcess) {
389 ExitElectionProcess* process =
new ExitElectionProcess(kernelProcess);
390 ExitElectionContext context(process);
392 new Thread(process, competingProcessExit, &context,
nullptr,
false,
true,
true);
393 competitor->setName(
"hosted process-exit competitor");
394 process->competitor = competitor;
395 Thread* owner =
new Thread(process, owningProcessExit, &context,
nullptr,
false,
true,
true);
396 owner->setName(
"hosted process-exit owner");
397 context.owner = owner;
399 bool passed = check(competitor->
start(),
"the competing exit thread did not start");
401 constexpr size_t Attempts = 10000;
402 bool competitorBlocked =
false;
403 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
406 competitor->
getStatus() == Thread::Sleeping) {
407 competitorBlocked =
true;
412 passed &= check(competitorBlocked,
"the competing exit thread did not publish its election gate");
414 __atomic_store_n(&g_ExitElectionContext, &context, __ATOMIC_RELEASE);
415 Process::setTerminationElectionHook(exitElectionHook);
416 passed &= check(owner->start(),
"the exit owner did not start");
418 bool ownerBlocked =
false;
419 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
421 if (context.hookCalls == 1 && owner->getWaitDebugInfo(info) && info.queue && info.queued &&
422 owner->getStatus() == Thread::Sleeping) {
428 passed &= check(ownerBlocked,
"the elected exit owner did not publish its peer rendezvous");
430 context.releaseCompetitor.release();
432 Process::setTerminationElectionHook(
nullptr);
433 __atomic_store_n(&g_ExitElectionContext,
static_cast<ExitElectionContext*
>(
nullptr),
436 passed &= check(reapable && process->getState() == Process::Terminated,
437 "the competing exit process never became reapable");
438 passed &= check(context.hookCalls == 1 && context.hookFailures == 0,
439 "the deterministic election hook did not complete cleanly");
441 context.ownerElected == 1 && context.competitorEntered == 1 && context.competitorElected == 0,
442 "more than one exiting thread won the process election");
443 passed &= check(context.resumeSawTerminating == 1,
444 "a concurrent resume downgraded a terminating process");
445 passed &= check(process->cleanupCalls == 1 && process->cleanupBeforePeerReapable == 0,
446 "shared process cleanup was repeated or ran before peer quiescence");
448 owner->getStatus() == Thread::AwaitingJoin && competitor->
getStatus() == Thread::AwaitingJoin,
449 "process cleanup completed before every peer was off-stack");
453 NOTICE(
"HOSTED-WAIT-TEST: PASS process-exit-election");
454 NOTICE(
"HOSTED-WAIT-TEST: PASS process-resume-vs-termination");
459struct CreationDrainContext {
460 explicit CreationDrainContext(ExitElectionProcess* process)
462 releaseCreator(0, false),
471 ExitElectionProcess* process;
482int delayedCreationChild(
void* parameter) {
483 CreationDrainContext* context =
reinterpret_cast<CreationDrainContext*
>(parameter);
484 context->childEntered += 1;
488int admittedProcessCreator(
void* parameter) {
489 CreationDrainContext* context =
reinterpret_cast<CreationDrainContext*
>(parameter);
492 context->failures += 1;
495 context->admitted = 1;
496 if (!context->releaseCreator.acquireForCompletion() ||
497 context->process->getState() != Process::Terminating) {
498 context->failures += 1;
503 new Thread(context->process, delayedCreationChild, context,
nullptr,
false,
true,
true);
505 context->failures += 1;
507 context->child = child;
511int creationDrainOwner(
void* parameter) {
512 CreationDrainContext* context =
reinterpret_cast<CreationDrainContext*
>(parameter);
513 const bool elected = context->process->beginTermination();
514 context->ownerElected = elected ? 1 : 0;
515 if (!elected || !context->process->quiesceTermination()) {
516 context->failures += 1;
517 context->owner->getScheduler()->commitCurrentThreadExit();
519 context->quiesced = 1;
520 context->process->finishTermination();
523bool processCreationDrain(
Process* kernelProcess) {
524 ExitElectionProcess* process =
new ExitElectionProcess(kernelProcess);
525 CreationDrainContext context(process);
527 new Thread(process, admittedProcessCreator, &context,
nullptr,
false,
true,
true);
528 Thread* owner =
new Thread(process, creationDrainOwner, &context,
nullptr,
false,
true,
true);
529 creator->setName(
"hosted admitted process creator");
530 owner->setName(
"hosted creation-drain exit owner");
532 context.owner = owner;
534 bool passed = check(
creator->start(),
"the admitted creator did not start");
535 constexpr size_t Attempts = 10000;
536 bool creatorBlocked =
false;
537 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
539 if (context.admitted == 1 &&
creator->getWaitDebugInfo(info) && info.queued &&
540 info.channelOwner == &context.releaseCreator &&
creator->getStatus() == Thread::Sleeping) {
541 creatorBlocked =
true;
546 passed &= check(creatorBlocked,
"the admitted creator did not publish its creation gate");
551 FATAL(
"The creation-drain fixture could not reserve its terminal owner.");
555 passed &= check(!denied,
"a terminal-owner reservation admitted a new creator");
557 reservation.install(owner);
559 passed &= check(owner->start(),
"the creation-drain exit owner did not start");
561 bool ownerBlocked =
false;
562 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
564 uintptr_t address = 0;
565 if (context.ownerElected == 1 && owner->getWaitDebugInfo(info) && info.queued &&
566 owner->getStatus() == Thread::Sleeping &&
567 owner->getDebugState(address) == Thread::ProcessWait &&
568 address ==
reinterpret_cast<uintptr_t
>(process)) {
574 passed &= check(ownerBlocked && context.quiesced == 0 && context.child ==
nullptr,
575 "termination did not wait for the admitted creation before sealing");
578 passed &= check(!denied,
"a terminating process admitted a new creator");
581 context.releaseCreator.release();
582 bool reapable =
false;
583 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
584 if (process->isTerminationReapableForHostedTest()) {
590 passed &= check(reapable,
"termination did not finish after the admitted creator was released");
592 FATAL(
"The creation-drain fixture cannot release live worker context.");
594 Thread* child = context.child;
595 passed &= check(context.failures == 0 && context.admitted == 1 && context.ownerElected == 1 &&
596 context.quiesced == 1 && child && context.childEntered == 0,
597 "late creation failed or its terminal child executed an entry point");
599 check(process->getState() == Process::Terminated && process->cleanupCalls == 1 &&
600 process->cleanupBeforePeerReapable == 0 &&
creator->isReapableForHostedTest() &&
601 owner->isReapableForHostedTest() && child && child->isReapableForHostedTest(),
602 "creation-drain cleanup ran before every retained thread was off-stack");
605 NOTICE(
"HOSTED-WAIT-TEST: PASS process-creation-termination-drain");
610struct OrphanExitContext {
616 workerEnteredBeforeOwnerExit(0),
621 ownerInPublication(0),
622 duplicateClaimsRejected(0) {}
637OrphanExitContext* g_OrphanExitContext =
nullptr;
639class OrphanExitProcess :
public Process {
641 OrphanExitProcess(
Process* parent, OrphanExitContext* context)
642 :
Process(DeferredPublication(), parent), m_Context(context) {
643 makeOrphanBeforePublicationForHostedTest();
647 ~OrphanExitProcess()
override {
649 if (
static_cast<size_t>(m_Context->reapableCalls) != 1 || getState() != Process::Terminated) {
650 m_Context->hookFailures += 1;
652 m_Context->destructorCalls += 1;
657 m_Context->cleanupCalls += 1;
660 OrphanExitContext* m_Context;
663void orphanPublicationHook(
Process* process, Process::OrphanPublicationPhase phase,
664 bool interruptsEnabled,
bool processLockHeld) {
665 OrphanExitContext* context = __atomic_load_n(&g_OrphanExitContext, __ATOMIC_ACQUIRE);
666 if (!context || context->process != process) {
670 if (!interruptsEnabled || processLockHeld) {
671 context->hookFailures += 1;
674 if (phase == Process::OrphanPublicationPhase::Preparing) {
675 context->preparingCalls += 1;
676 context->ownerInPublication = 1;
677 if (process->getState() != Process::Terminated ||
678 process->isTerminationReapableForHostedTest()) {
679 context->hookFailures += 1;
683 context->hookFailures += 1;
686 context->duplicateClaimsRejected += 1;
691 context->publishedCalls += 1;
692 constexpr size_t Attempts = 10000;
693 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
694 if (context->workerEntered) {
699 if (!context->workerEntered) {
700 context->hookFailures += 1;
702 context->ownerInPublication = 0;
705void orphanReapHook(
Process* process, ZombieProcess::ReapPhase phase) {
706 OrphanExitContext* context = __atomic_load_n(&g_OrphanExitContext, __ATOMIC_ACQUIRE);
707 if (!context || context->process != process) {
711 if (phase == ZombieProcess::ReapPhase::Entered) {
712 context->workerEntered += 1;
713 if (context->ownerInPublication) {
714 context->workerEnteredBeforeOwnerExit += 1;
716 context->hookFailures += 1;
721 context->reapableCalls += 1;
722 if (process->getState() != Process::Terminated) {
723 context->hookFailures += 1;
727bool orphanPublicationInterleaving(
Process* kernelProcess) {
728 OrphanExitContext* context =
new OrphanExitContext;
729 OrphanExitProcess* process =
new OrphanExitProcess(kernelProcess, context);
730 context->process = process;
732 __atomic_store_n(&g_OrphanExitContext, context, __ATOMIC_RELEASE);
733 Process::setOrphanPublicationHook(orphanPublicationHook);
734 ZombieProcess::setReapHook(orphanReapHook);
736 Thread* owner =
new Thread(process, terminateChildProcess, process,
nullptr,
false,
true,
true);
737 owner->setName(
"hosted orphan-exit owner");
738 if (!owner->start()) {
739 Process::setOrphanPublicationHook(
nullptr);
740 ZombieProcess::setReapHook(
nullptr);
741 __atomic_store_n(&g_OrphanExitContext,
static_cast<OrphanExitContext*
>(
nullptr),
745 return check(
false,
"the orphan-exit owner did not start");
748 constexpr size_t Attempts = 20000;
749 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
750 if (context->destructorCalls) {
756 const bool destroyed = context->destructorCalls == 1;
757 const bool drained = destroyed && ZombieQueue::instance().
drain();
758 Process::setOrphanPublicationHook(
nullptr);
759 ZombieProcess::setReapHook(
nullptr);
760 __atomic_store_n(&g_OrphanExitContext,
static_cast<OrphanExitContext*
>(
nullptr),
763 if (!destroyed || !drained) {
765 return check(
false,
"orphan destruction did not complete and drain");
769 passed &= check(context->preparingCalls == 1 && context->publishedCalls == 1,
770 "orphan publication did not cross both unlocked checkpoints");
771 passed &= check(context->workerEntered == 1 && context->workerEnteredBeforeOwnerExit == 1,
772 "the ZombieQueue worker did not enter before owner stack retirement");
773 passed &= check(context->reapableCalls == 1 && context->destructorCalls == 1 &&
774 context->cleanupCalls == 1 && context->duplicateClaimsRejected == 1,
775 "orphan destruction was not exactly once and post-reapable");
776 passed &= check(context->hookFailures == 0,
777 "orphan publication violated its status, stack, lock, or interrupt ordering");
781 NOTICE(
"HOSTED-WAIT-TEST: PASS process-orphan-publication-handoff");
786struct ZombieBacklogContext {
787 ZombieBacklogContext() : release(0, false), entered(0), destroyed(0), failures(0) {}
797 explicit HostedBacklogZombie(ZombieBacklogContext* context) : m_Context(context) {}
799 ~HostedBacklogZombie()
override {
800 m_Context->entered += 1;
801 if (!m_Context->release.acquireForCompletion()) {
802 m_Context->failures += 1;
804 m_Context->destroyed += 1;
808 ZombieBacklogContext* m_Context;
811bool mandatoryZombieBacklog() {
812 constexpr size_t Backlog = 300;
813 ZombieBacklogContext context;
814 for (
size_t i = 0; i < Backlog; ++i) {
815 ZombieQueue::instance().addObject(
new HostedBacklogZombie(&context));
818 bool passed = check(context.destroyed == 0,
819 "mandatory ZombieQueue work executed through its closed test gate");
820 context.release.release(Backlog);
821 passed &= check(ZombieQueue::instance().drain(),
"mandatory ZombieQueue backlog did not drain");
823 check(context.entered == Backlog && context.destroyed == Backlog && context.failures == 0,
824 "mandatory ZombieQueue work above the legacy limit was lost");
826 NOTICE(
"HOSTED-WAIT-TEST: PASS zombiequeue-mandatory-backlog");
832bool runHostedProcessExitRegressions() {
835 ProcessExitContext context(&mutex);
838 if (!processLeaseReplacement(kernelProcess)) {
842 if (!joinReaperLease(kernelProcess)) {
845 passed &= mandatoryZombieBacklog();
846 passed &= orphanPublicationInterleaving(kernelProcess);
847 passed &= exitElectionQuiescence(kernelProcess);
848 passed &= processCreationDrain(kernelProcess);
850 DeferredHostedProcess* publishedChild =
new DeferredHostedProcess(kernelProcess);
851 passed &= check(!publishedChild->probe.visibleDuringMemberConstruction,
852 "a derived Process was visible while its members were constructing");
853 passed &= check(!schedulerContains(publishedChild),
854 "an incomplete derived Process was visible after its constructor");
855 publishedChild->finishAssembly();
857 check(schedulerContains(publishedChild),
"a complete derived Process was not published");
858 delete publishedChild;
861 DeferredHostedProcess* abandonedChild =
new DeferredHostedProcess(kernelProcess);
862 delete abandonedChild;
864 "destroying an unpublished Process changed scheduler enumeration");
867 terminatingParent->markTerminating();
868 DeferredHostedProcess* freshChild =
new DeferredHostedProcess(terminatingParent);
869 freshChild->finishAssembly();
870 passed &= check(freshChild->getState() == Process::Active,
871 "a new child inherited its parent's terminal state");
872 passed &= check(freshChild->getParent() != terminatingParent,
873 "a terminating parent retained a newly published child");
875 delete terminatingParent;
877 passed &= check(mutex.acquire(),
"the supervisor could not hold the peer mutex");
881 Thread* peer =
new Thread(child, blockedProcessPeer, &context,
nullptr,
false,
true,
true);
882 peer->setName(
"hosted process-exit blocked peer");
884 new Thread(child, unstartedProcessPeer, &context,
nullptr,
false,
true,
true);
885 unstartedPeer->setName(
"hosted process-exit unstarted peer");
886 Thread* terminator =
new Thread(child, terminateChildProcess, child,
nullptr,
false,
true,
true);
887 terminator->setName(
"hosted process-exit terminator");
889 passed &= check(peer->
start(),
"the delayed blocked peer did not start");
891 bool peerEnrolled =
false;
892 constexpr size_t EnrolmentAttempts = 10000;
893 for (
size_t attempt = 0; attempt < EnrolmentAttempts; ++attempt) {
903 check(context.peerEntered == 1 && peerEnrolled,
"the peer did not publish its mutex wait");
905 passed &= check(terminator->
start(),
"the delayed terminating thread did not start");
908 passed &= check(reapable && child->getState() == Process::Terminated,
909 "the child did not publish off-stack termination");
910 passed &= check(peer->
getStatus() == Thread::AwaitingJoin &&
911 unstartedPeer->
getStatus() == Thread::AwaitingJoin &&
912 terminator->
getStatus() == Thread::AwaitingJoin,
913 "not every retained child thread reached AwaitingJoin");
915 check(context.peerAcquiredMutex == 0,
"the blocked peer escaped terminal wait cancellation");
916 passed &= check(context.unstartedPeerEntered == 0,
917 "the delayed unstarted peer executed during process exit");
919 check(orphan->
getParent() != child,
"process exit left a child parented to the dead process");
926 NOTICE(
"HOSTED-WAIT-TEST: PASS process-publication-reparent");
927 NOTICE(
"HOSTED-WAIT-TEST: PASS process-exit-rendezvous");
virtual void processTerminated()
bool waitUntilTerminationReapable()
void prepareForDestruction()
TerminalOwnerReservation reserveTerminalOwner()
ReaperClaim tryClaimReaper()
static Scheduler & instance()
MUST_USE_RESULT bool acquireProcess(ProcessLease &lease, size_t n)
@ TerminateThread
Exit only this thread during Process exit.
bool getWaitDebugInfo(WaitDebugInfo &info)
UnwindType getUnwindState()