The Pedigree Project 0.1
lifetime-lease-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 "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"
20
21namespace {
22constexpr size_t Attempts = 10000;
23
24bool check(bool condition, const char* detail) {
25 if (condition) {
26 return true;
27 }
28 ERROR("HOSTED-WAIT-TEST: FAIL lifetime-leases: " << detail);
29 return false;
30}
31
32class ObservedProcess : public Process {
33 public:
34 ObservedProcess(Process* parent, Atomic<size_t>* destroyed)
35 : Process(DeferredPublication(), parent), m_Destroyed(destroyed) {
36 publish();
37 }
38
39 ~ObservedProcess() override {
40 // Derived state must remain intact until external inspectors drain.
42 *m_Destroyed += 1;
43 }
44
45 void prepareForDestructionForHostedTest() {
47 }
48
49 private:
50 Atomic<size_t>* m_Destroyed;
51};
52
53class ObservedThread : public Thread {
54 public:
55 ObservedThread(Process* parent, ThreadStartFunc entry, Atomic<size_t>* destroyed,
56 bool delayedStart = false, void* parameter = nullptr)
57 : Thread(parent, entry, parameter, nullptr, false, true, delayedStart),
58 m_Destroyed(destroyed) {}
59
60 ~ObservedThread() override {
61 *m_Destroyed += 1;
62 }
63
64 private:
65 Atomic<size_t>* m_Destroyed;
66};
67
68struct OpenFinalLeaseContext {
69 explicit OpenFinalLeaseContext(Process* parent)
70 : parent(parent),
71 target(nullptr),
72 exitGate(0),
73 entered(0),
74 returned(0),
75 hookCalls(0),
76 safetyAcquired(0),
77 gateReleased(0),
78 reapableObserved(0),
79 wakeAttempts(0),
80 failures(0),
81 destroyed(0) {}
82
83 Process* parent;
84 Thread* target;
85 Process::ThreadLease safetyLease;
86 Semaphore exitGate;
87 Atomic<size_t> entered;
88 Atomic<size_t> returned;
89 Atomic<size_t> hookCalls;
90 Atomic<size_t> safetyAcquired;
91 Atomic<size_t> gateReleased;
92 Atomic<size_t> reapableObserved;
93 Atomic<size_t> wakeAttempts;
94 Atomic<size_t> failures;
95 Atomic<size_t> destroyed;
96};
97
98struct OpenFinalProcessLeaseContext {
99 explicit OpenFinalProcessLeaseContext(Process* process)
100 : process(process),
101 deleter(nullptr),
102 deleteGate(0),
103 entered(0),
104 finished(0),
105 hookCalls(0),
106 safetyAcquired(0),
107 gateReleased(0),
108 drainObserved(0),
109 wakeAttempts(0),
110 failures(0),
111 destroyed(0) {}
112
113 Process* process;
114 Thread* deleter;
115 Scheduler::ProcessLease safetyLease;
116 Semaphore deleteGate;
117 Atomic<size_t> entered;
118 Atomic<size_t> finished;
119 Atomic<size_t> hookCalls;
120 Atomic<size_t> safetyAcquired;
121 Atomic<size_t> gateReleased;
122 Atomic<size_t> drainObserved;
123 Atomic<size_t> wakeAttempts;
124 Atomic<size_t> failures;
125 Atomic<size_t> destroyed;
126};
127
128struct ClosedFinalProcessLeaseContext {
129 explicit ClosedFinalProcessLeaseContext(ObservedProcess* process)
130 : process(process),
131 drainer(nullptr),
132 beginDrain(0),
133 entered(0),
134 returned(0),
135 hookCalls(0),
136 drainWaitObserved(0),
137 earlyReturnObserved(0),
138 gateReleased(0),
139 wakeAttempts(0),
140 failures(0),
141 destroyed(0) {}
142
143 ObservedProcess* process;
144 Thread* drainer;
145 Semaphore beginDrain;
146 Atomic<size_t> entered;
147 Atomic<size_t> returned;
148 Atomic<size_t> hookCalls;
149 Atomic<size_t> drainWaitObserved;
150 Atomic<size_t> earlyReturnObserved;
151 Atomic<size_t> gateReleased;
152 Atomic<size_t> wakeAttempts;
153 Atomic<size_t> failures;
154 Atomic<size_t> destroyed;
155};
156
157OpenFinalLeaseContext* g_OpenFinalLeaseContext = nullptr;
158OpenFinalProcessLeaseContext* g_OpenFinalProcessLeaseContext = nullptr;
159ClosedFinalProcessLeaseContext* g_ClosedFinalProcessLeaseContext = nullptr;
160
161bool waitForLeaseDrain(Thread* waiter, const void* owner);
162
163int blockedOpenFinalLeaseTarget(void* parameter) {
164 OpenFinalLeaseContext* context = reinterpret_cast<OpenFinalLeaseContext*>(parameter);
165 context->entered += 1;
166 if (!context->exitGate.acquire()) {
167 context->failures += 1;
168 }
169 context->returned += 1;
170 return 0;
171}
172
173void observeOpenFinalLeaseRelease(Thread* target, Thread::ExternalLeaseReleasePhase phase) {
174 OpenFinalLeaseContext* context = __atomic_load_n(&g_OpenFinalLeaseContext, __ATOMIC_ACQUIRE);
175 if (!context || target != context->target) {
176 return;
177 }
178
179 if (phase == Thread::ExternalLeaseBeforeWaiterWake) {
180 context->wakeAttempts += 1;
181 return;
182 }
183
184 context->hookCalls += 1;
185 if (!context->parent->acquireThread(context->safetyLease, target)) {
186 context->failures += 1;
187 return;
188 }
189
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;
196 return;
197 }
199 }
200 context->failures += 1;
201}
202
203int deleteOpenFinalLeasedProcess(void* parameter) {
204 OpenFinalProcessLeaseContext* context =
205 reinterpret_cast<OpenFinalProcessLeaseContext*>(parameter);
206 TerminationDeferral lifecycle;
207 context->entered += 1;
208 if (!context->deleteGate.acquire()) {
209 context->failures += 1;
210 }
211 delete context->process;
212 context->finished += 1;
213 return 0;
214}
215
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) {
221 return;
222 }
223
224 if (phase == Process::ExternalLeaseBeforeWaiterWake) {
225 context->wakeAttempts += 1;
226 return;
227 }
228
229 context->hookCalls += 1;
230 if (!Scheduler::instance().acquireProcess(context->safetyLease, process)) {
231 context->failures += 1;
232 return;
233 }
234
235 context->safetyAcquired += 1;
236 context->gateReleased += 1;
237 context->deleteGate.release();
238 if (waitForLeaseDrain(context->deleter, process)) {
239 context->drainObserved += 1;
240 } else {
241 context->failures += 1;
242 }
243}
244
245int drainClosedFinalLeasedProcess(void* parameter) {
246 ClosedFinalProcessLeaseContext* context =
247 reinterpret_cast<ClosedFinalProcessLeaseContext*>(parameter);
248 TerminationDeferral lifecycle;
249 context->entered += 1;
250 if (!context->beginDrain.acquire()) {
251 context->failures += 1;
252 }
253 context->process->prepareForDestructionForHostedTest();
254 context->returned += 1;
255 return 0;
256}
257
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) {
263 return;
264 }
265
266 if (phase == Process::ExternalLeaseBeforeWaiterWake) {
267 context->wakeAttempts += 1;
268 return;
269 }
270
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;
277 return;
278 }
279
280 Thread::WaitDebugInfo info = {};
281 if (context->drainer->getWaitDebugInfo(info) && info.queued && info.channelOwner == process &&
282 context->drainer->getStatus() == Thread::Sleeping) {
283 context->drainWaitObserved += 1;
284 return;
285 }
286 if (context->drainer->getStatus() == Thread::Zombie) {
287 context->earlyReturnObserved += 1;
288 return;
289 }
291 }
292 context->failures += 1;
293}
294
295struct ProcessDeleteContext {
296 ProcessDeleteContext(Process* process, Atomic<size_t>* destroyed)
297 : process(process), destroyed(destroyed), entered(0), finished(0) {}
298
299 Process* process;
300 Atomic<size_t>* destroyed;
301 Atomic<size_t> entered;
302 Atomic<size_t> finished;
303};
304
305struct OwnedWorkerContext {
306 OwnedWorkerContext()
307 : gate(0),
308 worker(nullptr),
309 entered(0),
310 returnedPastWait(0),
311 waitInterrupted(0),
312 destructed(0),
313 destructedBeforeJoin(0),
314 joins(0) {}
315
316 Semaphore gate;
317 Thread* worker;
318 Atomic<size_t> entered;
319 Atomic<size_t> returnedPastWait;
320 Atomic<size_t> waitInterrupted;
321 Atomic<size_t> destructed;
322 Atomic<size_t> destructedBeforeJoin;
323 Atomic<size_t> joins;
324};
325
326struct AdmittedThreadResourceProbe {
327 AdmittedThreadResourceProbe(OperationBarrier* barrier, Atomic<size_t>* releases,
328 Atomic<size_t>* releasesBeforeDrain)
329 : barrier(barrier), releases(releases), releasesBeforeDrain(releasesBeforeDrain) {}
330
331 OperationBarrier* barrier;
332 Atomic<size_t>* releases;
333 Atomic<size_t>* releasesBeforeDrain;
334};
335
336struct AdmittedThreadProbeReleaser {
337 static void release(AdmittedThreadResourceProbe* resource) {
338 *resource->releases += 1;
339 if (!resource->barrier->isClosedAndDrained()) {
340 *resource->releasesBeforeDrain += 1;
341 }
342 }
343};
344
345using AdmittedThreadProbeOwner =
347
348struct AdmittedThreadWorkerContext {
349 explicit AdmittedThreadWorkerContext(AdmittedThreadProbeOwner&& resource)
350 : gate(0),
351 resource(pedigree_std::move(resource)),
352 worker(0),
353 entered(0),
354 returned(0),
355 destructed(0) {}
356
357 Semaphore gate;
358 AdmittedThreadProbeOwner resource;
360 Atomic<size_t> entered;
361 Atomic<size_t> returned;
362 Atomic<size_t> destructed;
363};
364
365struct AdmittedThreadCancelContext {
366 explicit AdmittedThreadCancelContext(OperationBarrier* barrier)
367 : barrier(barrier), entered(0), cancelled(0), cancelledBeforeDrain(0) {}
368
369 OperationBarrier* barrier;
370 Atomic<size_t> entered;
371 Atomic<size_t> cancelled;
372 Atomic<size_t> cancelledBeforeDrain;
373};
374
375class AdmittedThreadStackCanary {
376 public:
377 explicit AdmittedThreadStackCanary(AdmittedThreadWorkerContext* context) : m_Context(context) {}
378
379 ~AdmittedThreadStackCanary() {
380 m_Context->destructed += 1;
381 }
382
383 private:
384 AdmittedThreadWorkerContext* m_Context;
385};
386
387class OwnedWorkerStackCanary {
388 public:
389 explicit OwnedWorkerStackCanary(OwnedWorkerContext* context) : m_Context(context) {}
390
391 ~OwnedWorkerStackCanary() {
392 m_Context->destructed += 1;
393 }
394
395 private:
396 OwnedWorkerContext* m_Context;
397};
398
399OwnedWorkerContext* g_OwnedWorkerContext = nullptr;
400
401void observeOwnedWorkerJoin(Thread* target, Process*) {
402 if (g_OwnedWorkerContext && target == g_OwnedWorkerContext->worker) {
403 if (g_OwnedWorkerContext->destructed == 1) {
404 g_OwnedWorkerContext->destructedBeforeJoin += 1;
405 }
406 g_OwnedWorkerContext->joins += 1;
407 }
408}
409
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;
416 return 0;
417}
418
419int blockedAdmittedThreadWorker(void* parameter) {
420 AdmittedThreadWorkerContext* context = reinterpret_cast<AdmittedThreadWorkerContext*>(parameter);
421 AdmittedThreadStackCanary stackCanary(context);
422 AdmittedThreadProbeOwner resource = pedigree_std::move(context->resource);
423 context->worker = reinterpret_cast<uintptr_t>(Processor::information().getCurrentThread());
424 context->entered += 1;
425 context->gate.acquire();
426 context->returned += 1;
427 return 0;
428}
429
430int unstartedAdmittedThreadWorker(void* parameter) {
431 AdmittedThreadCancelContext* context = reinterpret_cast<AdmittedThreadCancelContext*>(parameter);
432 context->entered += 1;
433 return 0;
434}
435
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;
441 }
442}
443
444void terminateAdmittedThreadBeforeStart(Thread* thread, void* parameter) {
445 reinterpret_cast<OperationBarrier*>(parameter)->close();
447 thread->waitUntilReapableForHostedTest();
448}
449
450int deleteLeasedProcess(void* parameter) {
451 ProcessDeleteContext* context = reinterpret_cast<ProcessDeleteContext*>(parameter);
452 // Deletion and its ownership handoff form one lifecycle operation. A
453 // terminal request may wake the lease drain but cannot cut that operation
454 // between reclaiming the object and publishing completion.
455 TerminationDeferral lifecycle;
456 context->entered += 1;
457 delete context->process;
458 context->finished += 1;
459 return 0;
460}
461
462int immediateExit(void*) {
463 return 0;
464}
465
466bool waitForLeaseDrain(Thread* waiter, const void* owner) {
467 for (size_t attempt = 0; attempt < Attempts; ++attempt) {
468 Thread::WaitDebugInfo info = {};
469 if (waiter->getWaitDebugInfo(info) && info.queued && info.channelOwner == owner &&
470 waiter->getStatus() == Thread::Sleeping) {
471 return true;
472 }
474 }
475 return false;
476}
477
478bool ownedThreadTerminalJoin(Process* kernelProcess) {
479 OwnedWorkerContext context;
480 bool waiting = false;
481 {
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);
487
488 g_OwnedWorkerContext = &context;
489 Thread::setJoinOperationHook(observeOwnedWorkerJoin);
490 }
491 Thread::setJoinOperationHook(nullptr);
492 g_OwnedWorkerContext = nullptr;
493
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 "
498 "exactly once");
499 if (passed) {
500 NOTICE("HOSTED-WAIT-TEST: PASS owned-thread-terminal-join");
501 }
502 return passed;
503}
504
505bool idleOnlyAdmissionClose() {
506 OperationBarrier barrier;
507 OperationBarrier::Lease first, second;
508 bool passed = check(barrier.tryAcquire(first) && !barrier.tryCloseIfIdle() && barrier.isOpen() &&
509 barrier.tryAcquire(second),
510 "busy close changed operation admission");
511 first = OperationBarrier::Lease();
512 passed &= check(!barrier.tryCloseIfIdle(), "busy close lost a remaining admission");
513 second = OperationBarrier::Lease();
514 passed &=
515 check(barrier.tryCloseIfIdle() && barrier.isClosedAndDrained() && !barrier.tryAcquire(first),
516 "idle close did not prevent subsequent admission");
517 barrier.closeAndWait();
518 if (passed) {
519 NOTICE("HOSTED-WAIT-TEST: PASS idle-only-admission-close");
520 }
521 return passed;
522}
523
524bool admittedThreadTerminalReleaseOrder() {
525 OperationBarrier barrier;
526 Atomic<size_t> releases(0);
527 Atomic<size_t> releasesBeforeDrain(0);
528 AdmittedThreadResourceProbe resource(&barrier, &releases, &releasesBeforeDrain);
529 AdmittedThreadWorkerContext context(AdmittedThreadProbeOwner::adopt(&resource));
530 bool passed = check(AdmittedThread::launchDetached(blockedAdmittedThreadWorker, &context, nullptr,
531 barrier, "hosted admitted thread worker"),
532 "could not launch admitted thread");
533
534 Thread* worker = nullptr;
535 for (size_t attempt = 0; attempt < Attempts && !context.worker; ++attempt) {
537 }
538 worker = reinterpret_cast<Thread*>(context.worker.value());
539
540 const bool waiting = worker && waitForLeaseDrain(worker, &context.gate);
541 barrier.close();
542 passed &= check(waiting && context.entered == 1 && !barrier.isClosedAndDrained(),
543 "kernel trampoline did not retain admission while worker was blocked");
544
545 if (worker) {
546 worker->setUnwindState(Thread::TerminateThread);
547 }
548 barrier.wait();
549
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");
553 if (passed) {
554 NOTICE("HOSTED-WAIT-TEST: PASS admitted-thread-terminal-release-order");
555 }
556 return passed;
557}
558
559bool admittedThreadPreStartCancellation() {
560 OperationBarrier barrier;
561 AdmittedThreadCancelContext context(&barrier);
562 AdmittedThread::setBeforeStartHookForTest(terminateAdmittedThreadBeforeStart, &barrier);
563 const bool launched = AdmittedThread::launchDetached(unstartedAdmittedThreadWorker, &context,
564 cancelUnstartedAdmittedThread, barrier,
565 "hosted unstarted admitted thread");
566 AdmittedThread::setBeforeStartHookForTest(nullptr, nullptr);
567
568 barrier.close();
569 barrier.wait();
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");
573 if (passed) {
574 NOTICE("HOSTED-WAIT-TEST: PASS admitted-thread-pre-start-cancellation");
575 }
576 return passed;
577}
578
579bool processLeaseBarrier(Process* kernelProcess) {
580 Atomic<size_t> destroyed(0);
581 Process* process = new ObservedProcess(kernelProcess, &destroyed);
583 const bool leaseAcquired = Scheduler::instance().acquireProcess(lease, process);
584 bool passed =
585 check(leaseAcquired && static_cast<bool>(lease), "could not acquire the process lease");
586
587 ProcessDeleteContext context(process, &destroyed);
588 Thread* deleter = new Thread(kernelProcess, deleteLeasedProcess, &context, nullptr, false, true);
589 deleter->setName("hosted ProcessLease deleter");
590
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");
594
595 Scheduler::ProcessLease lateLease;
596 const bool lateLeaseAcquired = Scheduler::instance().acquireProcess(lateLease, process);
597 passed &= check(!lateLeaseAcquired && !lateLease,
598 "Process destruction admitted a lease after scheduler removal");
599
601 for (size_t attempt = 0; attempt < 32; ++attempt) {
603 }
604 passed &= check(context.finished == 0 && destroyed == 0,
605 "terminal wake abandoned the ProcessLease completion drain");
606
607 lease.reset();
608 for (size_t attempt = 0; attempt < Attempts && !context.finished; ++attempt) {
610 }
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");
614 return passed;
615}
616
617bool openFinalProcessLeaseRelease(Process* kernelProcess) {
618 OpenFinalProcessLeaseContext context(nullptr);
619 ObservedProcess* process = new ObservedProcess(kernelProcess, &context.destroyed);
620 context.process = process;
621
623 const bool leaseAcquired = Scheduler::instance().acquireProcess(lease, process);
624 if (!leaseAcquired) {
625 delete process;
626 return check(false, "could not acquire the open-admission ProcessLease");
627 }
628
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);
633 if (!waiting) {
634 context.deleteGate.release();
635 lease.reset();
636 if (!deleter->joinForCompletion()) {
637 FATAL("Open final ProcessLease cleanup could not join its deleter");
638 }
639 return check(false, "could not prepare the open-admission ProcessLease release window");
640 }
641
642 __atomic_store_n(&g_OpenFinalProcessLeaseContext, &context, __ATOMIC_RELEASE);
643 Process::setExternalLeaseReleaseHookForHostedTest(process, observeOpenFinalProcessLeaseRelease);
644 lease.reset();
645 Process::setExternalLeaseReleaseHookForHostedTest(nullptr, nullptr);
646 __atomic_store_n(&g_OpenFinalProcessLeaseContext,
647 static_cast<OpenFinalProcessLeaseContext*>(nullptr), __ATOMIC_RELEASE);
648
649 if (!context.gateReleased) {
650 context.deleteGate.release();
651 }
652 context.safetyLease.reset();
653 const bool joined = deleter->joinForCompletion();
654
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");
660 if (passed) {
661 NOTICE("HOSTED-WAIT-TEST: PASS process-final-open-lease-release");
662 }
663 return passed;
664}
665
666bool closedFinalProcessLeaseHandoff(Process* kernelProcess) {
667 ClosedFinalProcessLeaseContext context(nullptr);
668 ObservedProcess* process = new ObservedProcess(kernelProcess, &context.destroyed);
669 context.process = process;
670
672 if (!Scheduler::instance().acquireProcess(lease, process)) {
673 delete process;
674 return check(false, "could not acquire the closed-handoff ProcessLease");
675 }
676
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();
682 lease.reset();
683 if (!drainer->joinForCompletion()) {
684 FATAL("Closed final ProcessLease cleanup could not join its drainer");
685 }
686 delete process;
687 return check(false, "could not prepare the closed final ProcessLease handoff");
688 }
689
690 // Close enumeration admission before the final release, but do not let the
691 // destruction drain enrol until the post-unlock hook opens its gate.
693 __atomic_store_n(&g_ClosedFinalProcessLeaseContext, &context, __ATOMIC_RELEASE);
694 Process::setExternalLeaseReleaseHookForHostedTest(process, observeClosedFinalProcessLeaseRelease);
695 lease.reset();
696 Process::setExternalLeaseReleaseHookForHostedTest(nullptr, nullptr);
697 __atomic_store_n(&g_ClosedFinalProcessLeaseContext,
698 static_cast<ClosedFinalProcessLeaseContext*>(nullptr), __ATOMIC_RELEASE);
699
700 if (!context.gateReleased) {
701 context.beginDrain.release();
702 }
703 bool drainerReapable = false;
704 for (size_t attempt = 0; attempt < Attempts; ++attempt) {
705 if (drainer->isReapableForHostedTest()) {
706 drainerReapable = true;
707 break;
708 }
710 }
711 if (!drainerReapable || !drainer->joinForCompletion()) {
712 FATAL("Closed final ProcessLease drainer did not retire after handoff");
713 }
714
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");
720 delete process;
721 passed &= check(context.destroyed == 1,
722 "the closed final ProcessLease fixture did not destroy its process once");
723 if (passed) {
724 NOTICE("HOSTED-WAIT-TEST: PASS process-final-closed-lease-handoff");
725 }
726 return passed;
727}
728
729bool threadLeaseIdLookup(Process* kernelProcess) {
730 Atomic<size_t> destroyed(0);
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");
735
737 const bool firstAcquired = kernelProcess->acquireThreadById(lease, first->getId());
738 bool passed = check(firstAcquired && lease.get() == first,
739 "exact thread ID lookup did not acquire its target");
740
741 const bool secondAcquired = kernelProcess->acquireThreadById(lease, second->getId());
742 passed &= check(secondAcquired && lease.get() == second,
743 "exact thread ID lookup did not replace an active lease");
744
745 const bool missingAcquired = kernelProcess->acquireThreadById(lease, ~static_cast<size_t>(0));
746 passed &=
747 check(!missingAcquired && !lease, "missing thread ID lookup did not reset an active lease");
748
749 first->start();
750 second->start();
751 const bool firstJoined = first->joinForCompletion();
752 const bool secondJoined = second->joinForCompletion();
753 passed &= check(firstJoined && secondJoined && destroyed == 2,
754 "thread ID lookup fixtures did not retire cleanly");
755 if (passed) {
756 NOTICE("HOSTED-WAIT-TEST: PASS thread-lease-id-lookup");
757 }
758 return passed;
759}
760
761bool threadLeaseBarrier(Process* kernelProcess) {
762 Atomic<size_t> destroyed(0);
763 Thread* target = new ObservedThread(kernelProcess, immediateExit, &destroyed, true);
764 target->setName("hosted ThreadLease target");
765
766 // Pin the target before making it runnable so the test controls the
767 // retirement window instead of racing the immediate-exit trampoline.
769 const bool leaseAcquired = kernelProcess->acquireThreadById(lease, target->getId());
770 bool passed =
771 check(leaseAcquired && lease.get() == target, "could not acquire the thread lease by ID");
772 if (!lease) {
773 target->start();
774 target->joinForCompletion();
775 return false;
776 }
777
778 passed &= check(target->detach(), "the ThreadLease target could not detach");
779 passed &= check(target->start(), "the delayed ThreadLease target did not start");
780
781 bool retirementClosed = false;
782 for (size_t attempt = 0; attempt < Attempts; ++attempt) {
783 Process::ThreadLease lateLease;
784 if (!kernelProcess->acquireThreadById(lateLease, target->getId())) {
785 retirementClosed = true;
786 break;
787 }
788 lateLease.reset();
790 }
791
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");
794
795 lease.reset();
796 for (size_t attempt = 0; attempt < Attempts && !destroyed; ++attempt) {
798 }
799 passed &=
800 check(destroyed == 1, "Thread deletion did not complete after its final lease released");
801 if (passed) {
802 NOTICE("HOSTED-WAIT-TEST: PASS thread-final-lease-deletion");
803 }
804 return passed;
805}
806
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;
813
815 const bool leaseAcquired = kernelProcess->acquireThread(lease, target);
816 if (!leaseAcquired) {
818 target->joinForCompletion();
819 return check(false, "could not acquire the open-admission ThreadLease");
820 }
821
822 const bool started = target->start();
823 const bool waiting = started && waitForLeaseDrain(target, &context.exitGate);
824 if (!started || !waiting) {
826 lease.reset();
827 if (!target->joinForCompletion()) {
828 FATAL("Open final ThreadLease cleanup could not join its target");
829 }
830 return check(false, "could not prepare the open-admission final-release window");
831 }
832
833 if (!target->detach()) {
835 lease.reset();
836 if (!target->joinForCompletion()) {
837 FATAL("Open final ThreadLease cleanup could not join its undetached target");
838 }
839 return check(false, "could not detach the open-admission final-release target");
840 }
841
842 __atomic_store_n(&g_OpenFinalLeaseContext, &context, __ATOMIC_RELEASE);
843 Thread::setExternalLeaseReleaseHookForHostedTest(target, observeOpenFinalLeaseRelease);
844 lease.reset();
845 Thread::setExternalLeaseReleaseHookForHostedTest(nullptr, nullptr);
846 __atomic_store_n(&g_OpenFinalLeaseContext, static_cast<OpenFinalLeaseContext*>(nullptr),
847 __ATOMIC_RELEASE);
848
849 if (!context.gateReleased) {
850 context.exitGate.release();
851 }
852 context.safetyLease.reset();
853 for (size_t attempt = 0; attempt < Attempts && !context.destroyed; ++attempt) {
855 }
856 if (!context.destroyed) {
857 FATAL("Open final ThreadLease target did not complete detached retirement");
858 }
859
860 const bool passed =
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");
866 if (passed) {
867 NOTICE("HOSTED-WAIT-TEST: PASS thread-final-open-lease-release");
868 }
869 return passed;
870}
871} // namespace
872
873bool runHostedLifetimeLeaseRegressions() {
874 Process* kernelProcess = Scheduler::instance().getKernelProcess();
875 const bool passed =
876 idleOnlyAdmissionClose() && admittedThreadPreStartCancellation() &&
877 admittedThreadTerminalReleaseOrder() && ownedThreadTerminalJoin(kernelProcess) &&
878 processLeaseBarrier(kernelProcess) && openFinalProcessLeaseRelease(kernelProcess) &&
879 closedFinalProcessLeaseHandoff(kernelProcess) && threadLeaseIdLookup(kernelProcess) &&
880 threadLeaseBarrier(kernelProcess) && openFinalThreadLeaseRelease(kernelProcess);
881 if (passed) {
882 NOTICE("HOSTED-WAIT-TEST: PASS lifetime-leases");
883 }
884 return passed;
885}
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)
void publish()
Definition Process.cc:832
MUST_USE_RESULT bool acquireThreadById(ThreadLease &lease, size_t id)
Definition Process.cc:1309
MUST_USE_RESULT bool acquireThread(ThreadLease &lease, size_t n)
Definition Process.cc:1248
void prepareForDestruction()
Definition Process.cc:914
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
MUST_USE_RESULT bool acquireProcess(ProcessLease &lease, size_t n)
Definition Scheduler.cc:264
void yield()
Definition Scheduler.cc:226
void removeProcess(Process *pProcess)
Definition Scheduler.cc:209
void setUnwindState(UnwindType ut)
Definition Thread.cc:3628
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:515
bool joinForCompletion()
Definition Thread.cc:2771
bool detach()
Definition Thread.cc:3008
size_t getId()
Definition Thread.h:463
bool start()
Definition Thread.cc:794