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/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"
19
20namespace {
21constexpr size_t Attempts = 10000;
22
23bool check(bool condition, const char* detail) {
24 if (condition) {
25 return true;
26 }
27 ERROR("HOSTED-WAIT-TEST: FAIL lifetime-leases: " << detail);
28 return false;
29}
30
31class ObservedProcess : public Process {
32 public:
33 ObservedProcess(Process* parent, Atomic<size_t>* destroyed)
34 : Process(DeferredPublication(), parent), m_Destroyed(destroyed) {
35 publish();
36 }
37
38 ~ObservedProcess() override {
39 // Derived state must remain intact until external inspectors drain.
41 *m_Destroyed += 1;
42 }
43
44 void prepareForDestructionForHostedTest() {
46 }
47
48 private:
49 Atomic<size_t>* m_Destroyed;
50};
51
52class ObservedThread : public Thread {
53 public:
54 ObservedThread(Process* parent, ThreadStartFunc entry, Atomic<size_t>* destroyed,
55 bool delayedStart = false, void* parameter = nullptr)
56 : Thread(parent, entry, parameter, nullptr, false, true, delayedStart),
57 m_Destroyed(destroyed) {}
58
59 ~ObservedThread() override {
60 *m_Destroyed += 1;
61 }
62
63 private:
64 Atomic<size_t>* m_Destroyed;
65};
66
67struct OpenFinalLeaseContext {
68 explicit OpenFinalLeaseContext(Process* parent)
69 : parent(parent),
70 target(nullptr),
71 exitGate(0),
72 entered(0),
73 returned(0),
74 hookCalls(0),
75 safetyAcquired(0),
76 gateReleased(0),
77 reapableObserved(0),
78 wakeAttempts(0),
79 failures(0),
80 destroyed(0) {}
81
82 Process* parent;
83 Thread* target;
84 Process::ThreadLease safetyLease;
85 Semaphore exitGate;
86 Atomic<size_t> entered;
87 Atomic<size_t> returned;
88 Atomic<size_t> hookCalls;
89 Atomic<size_t> safetyAcquired;
90 Atomic<size_t> gateReleased;
91 Atomic<size_t> reapableObserved;
92 Atomic<size_t> wakeAttempts;
93 Atomic<size_t> failures;
94 Atomic<size_t> destroyed;
95};
96
97struct OpenFinalProcessLeaseContext {
98 explicit OpenFinalProcessLeaseContext(Process* process)
99 : process(process),
100 deleter(nullptr),
101 deleteGate(0),
102 entered(0),
103 finished(0),
104 hookCalls(0),
105 safetyAcquired(0),
106 gateReleased(0),
107 drainObserved(0),
108 wakeAttempts(0),
109 failures(0),
110 destroyed(0) {}
111
112 Process* process;
113 Thread* deleter;
114 Scheduler::ProcessLease safetyLease;
115 Semaphore deleteGate;
116 Atomic<size_t> entered;
117 Atomic<size_t> finished;
118 Atomic<size_t> hookCalls;
119 Atomic<size_t> safetyAcquired;
120 Atomic<size_t> gateReleased;
121 Atomic<size_t> drainObserved;
122 Atomic<size_t> wakeAttempts;
123 Atomic<size_t> failures;
124 Atomic<size_t> destroyed;
125};
126
127struct ClosedFinalProcessLeaseContext {
128 explicit ClosedFinalProcessLeaseContext(ObservedProcess* process)
129 : process(process),
130 drainer(nullptr),
131 beginDrain(0),
132 entered(0),
133 returned(0),
134 hookCalls(0),
135 drainWaitObserved(0),
136 earlyReturnObserved(0),
137 gateReleased(0),
138 wakeAttempts(0),
139 failures(0),
140 destroyed(0) {}
141
142 ObservedProcess* process;
143 Thread* drainer;
144 Semaphore beginDrain;
145 Atomic<size_t> entered;
146 Atomic<size_t> returned;
147 Atomic<size_t> hookCalls;
148 Atomic<size_t> drainWaitObserved;
149 Atomic<size_t> earlyReturnObserved;
150 Atomic<size_t> gateReleased;
151 Atomic<size_t> wakeAttempts;
152 Atomic<size_t> failures;
153 Atomic<size_t> destroyed;
154};
155
156OpenFinalLeaseContext* g_OpenFinalLeaseContext = nullptr;
157OpenFinalProcessLeaseContext* g_OpenFinalProcessLeaseContext = nullptr;
158ClosedFinalProcessLeaseContext* g_ClosedFinalProcessLeaseContext = nullptr;
159
160bool waitForLeaseDrain(Thread* waiter, const void* owner);
161
162int blockedOpenFinalLeaseTarget(void* parameter) {
163 OpenFinalLeaseContext* context = reinterpret_cast<OpenFinalLeaseContext*>(parameter);
164 context->entered += 1;
165 if (!context->exitGate.acquire()) {
166 context->failures += 1;
167 }
168 context->returned += 1;
169 return 0;
170}
171
172void observeOpenFinalLeaseRelease(Thread* target, Thread::ExternalLeaseReleasePhase phase) {
173 OpenFinalLeaseContext* context = __atomic_load_n(&g_OpenFinalLeaseContext, __ATOMIC_ACQUIRE);
174 if (!context || target != context->target) {
175 return;
176 }
177
178 if (phase == Thread::ExternalLeaseBeforeWaiterWake) {
179 context->wakeAttempts += 1;
180 return;
181 }
182
183 context->hookCalls += 1;
184 if (!context->parent->acquireThread(context->safetyLease, target)) {
185 context->failures += 1;
186 return;
187 }
188
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;
195 return;
196 }
198 }
199 context->failures += 1;
200}
201
202int deleteOpenFinalLeasedProcess(void* parameter) {
203 OpenFinalProcessLeaseContext* context =
204 reinterpret_cast<OpenFinalProcessLeaseContext*>(parameter);
205 TerminationDeferral lifecycle;
206 context->entered += 1;
207 if (!context->deleteGate.acquire()) {
208 context->failures += 1;
209 }
210 delete context->process;
211 context->finished += 1;
212 return 0;
213}
214
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) {
220 return;
221 }
222
223 if (phase == Process::ExternalLeaseBeforeWaiterWake) {
224 context->wakeAttempts += 1;
225 return;
226 }
227
228 context->hookCalls += 1;
229 if (!Scheduler::instance().acquireProcess(context->safetyLease, process)) {
230 context->failures += 1;
231 return;
232 }
233
234 context->safetyAcquired += 1;
235 context->gateReleased += 1;
236 context->deleteGate.release();
237 if (waitForLeaseDrain(context->deleter, process)) {
238 context->drainObserved += 1;
239 } else {
240 context->failures += 1;
241 }
242}
243
244int drainClosedFinalLeasedProcess(void* parameter) {
245 ClosedFinalProcessLeaseContext* context =
246 reinterpret_cast<ClosedFinalProcessLeaseContext*>(parameter);
247 TerminationDeferral lifecycle;
248 context->entered += 1;
249 if (!context->beginDrain.acquire()) {
250 context->failures += 1;
251 }
252 context->process->prepareForDestructionForHostedTest();
253 context->returned += 1;
254 return 0;
255}
256
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) {
262 return;
263 }
264
265 if (phase == Process::ExternalLeaseBeforeWaiterWake) {
266 context->wakeAttempts += 1;
267 return;
268 }
269
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;
276 return;
277 }
278
279 Thread::WaitDebugInfo info = {};
280 if (context->drainer->getWaitDebugInfo(info) && info.queued && info.channelOwner == process &&
281 context->drainer->getStatus() == Thread::Sleeping) {
282 context->drainWaitObserved += 1;
283 return;
284 }
285 if (context->drainer->getStatus() == Thread::Zombie) {
286 context->earlyReturnObserved += 1;
287 return;
288 }
290 }
291 context->failures += 1;
292}
293
294struct ProcessDeleteContext {
295 ProcessDeleteContext(Process* process, Atomic<size_t>* destroyed)
296 : process(process), destroyed(destroyed), entered(0), finished(0) {}
297
298 Process* process;
299 Atomic<size_t>* destroyed;
300 Atomic<size_t> entered;
301 Atomic<size_t> finished;
302};
303
304struct OwnedWorkerContext {
305 OwnedWorkerContext()
306 : gate(0),
307 worker(nullptr),
308 entered(0),
309 returnedPastWait(0),
310 waitInterrupted(0),
311 destructed(0),
312 destructedBeforeJoin(0),
313 joins(0) {}
314
315 Semaphore gate;
316 Thread* worker;
317 Atomic<size_t> entered;
318 Atomic<size_t> returnedPastWait;
319 Atomic<size_t> waitInterrupted;
320 Atomic<size_t> destructed;
321 Atomic<size_t> destructedBeforeJoin;
322 Atomic<size_t> joins;
323};
324
325class OwnedWorkerStackCanary {
326 public:
327 explicit OwnedWorkerStackCanary(OwnedWorkerContext* context) : m_Context(context) {}
328
329 ~OwnedWorkerStackCanary() {
330 m_Context->destructed += 1;
331 }
332
333 private:
334 OwnedWorkerContext* m_Context;
335};
336
337OwnedWorkerContext* g_OwnedWorkerContext = nullptr;
338
339void observeOwnedWorkerJoin(Thread* target, Process*) {
340 if (g_OwnedWorkerContext && target == g_OwnedWorkerContext->worker) {
341 if (g_OwnedWorkerContext->destructed == 1) {
342 g_OwnedWorkerContext->destructedBeforeJoin += 1;
343 }
344 g_OwnedWorkerContext->joins += 1;
345 }
346}
347
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;
354 return 0;
355}
356
357int deleteLeasedProcess(void* parameter) {
358 ProcessDeleteContext* context = reinterpret_cast<ProcessDeleteContext*>(parameter);
359 // Deletion and its ownership handoff form one lifecycle operation. A
360 // terminal request may wake the lease drain but cannot cut that operation
361 // between reclaiming the object and publishing completion.
362 TerminationDeferral lifecycle;
363 context->entered += 1;
364 delete context->process;
365 context->finished += 1;
366 return 0;
367}
368
369int immediateExit(void*) {
370 return 0;
371}
372
373bool waitForLeaseDrain(Thread* waiter, const void* owner) {
374 for (size_t attempt = 0; attempt < Attempts; ++attempt) {
375 Thread::WaitDebugInfo info = {};
376 if (waiter->getWaitDebugInfo(info) && info.queued && info.channelOwner == owner &&
377 waiter->getStatus() == Thread::Sleeping) {
378 return true;
379 }
381 }
382 return false;
383}
384
385bool ownedThreadTerminalJoin(Process* kernelProcess) {
386 OwnedWorkerContext context;
387 bool waiting = false;
388 {
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);
394
395 g_OwnedWorkerContext = &context;
396 Thread::setJoinOperationHook(observeOwnedWorkerJoin);
397 }
398 Thread::setJoinOperationHook(nullptr);
399 g_OwnedWorkerContext = nullptr;
400
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 "
405 "exactly once");
406 if (passed) {
407 NOTICE("HOSTED-WAIT-TEST: PASS owned-thread-terminal-join");
408 }
409 return passed;
410}
411
412bool idleOnlyAdmissionClose() {
413 OperationBarrier barrier;
414 OperationBarrier::Lease first, second;
415 bool passed = check(barrier.tryAcquire(first) && !barrier.tryCloseIfIdle() && barrier.isOpen() &&
416 barrier.tryAcquire(second),
417 "busy close changed operation admission");
418 first = OperationBarrier::Lease();
419 passed &= check(!barrier.tryCloseIfIdle(), "busy close lost a remaining admission");
420 second = OperationBarrier::Lease();
421 passed &=
422 check(barrier.tryCloseIfIdle() && barrier.isClosedAndDrained() && !barrier.tryAcquire(first),
423 "idle close did not prevent subsequent admission");
424 barrier.closeAndWait();
425 if (passed) {
426 NOTICE("HOSTED-WAIT-TEST: PASS idle-only-admission-close");
427 }
428 return passed;
429}
430
431bool processLeaseBarrier(Process* kernelProcess) {
432 Atomic<size_t> destroyed(0);
433 Process* process = new ObservedProcess(kernelProcess, &destroyed);
435 const bool leaseAcquired = Scheduler::instance().acquireProcess(lease, process);
436 bool passed =
437 check(leaseAcquired && static_cast<bool>(lease), "could not acquire the process lease");
438
439 ProcessDeleteContext context(process, &destroyed);
440 Thread* deleter = new Thread(kernelProcess, deleteLeasedProcess, &context, nullptr, false, true);
441 deleter->setName("hosted ProcessLease deleter");
442
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");
446
447 Scheduler::ProcessLease lateLease;
448 const bool lateLeaseAcquired = Scheduler::instance().acquireProcess(lateLease, process);
449 passed &= check(!lateLeaseAcquired && !lateLease,
450 "Process destruction admitted a lease after scheduler removal");
451
453 for (size_t attempt = 0; attempt < 32; ++attempt) {
455 }
456 passed &= check(context.finished == 0 && destroyed == 0,
457 "terminal wake abandoned the ProcessLease completion drain");
458
459 lease.reset();
460 for (size_t attempt = 0; attempt < Attempts && !context.finished; ++attempt) {
462 }
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");
466 return passed;
467}
468
469bool openFinalProcessLeaseRelease(Process* kernelProcess) {
470 OpenFinalProcessLeaseContext context(nullptr);
471 ObservedProcess* process = new ObservedProcess(kernelProcess, &context.destroyed);
472 context.process = process;
473
475 const bool leaseAcquired = Scheduler::instance().acquireProcess(lease, process);
476 if (!leaseAcquired) {
477 delete process;
478 return check(false, "could not acquire the open-admission ProcessLease");
479 }
480
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);
485 if (!waiting) {
486 context.deleteGate.release();
487 lease.reset();
488 if (!deleter->joinForCompletion()) {
489 FATAL("Open final ProcessLease cleanup could not join its deleter");
490 }
491 return check(false, "could not prepare the open-admission ProcessLease release window");
492 }
493
494 __atomic_store_n(&g_OpenFinalProcessLeaseContext, &context, __ATOMIC_RELEASE);
495 Process::setExternalLeaseReleaseHookForHostedTest(process, observeOpenFinalProcessLeaseRelease);
496 lease.reset();
497 Process::setExternalLeaseReleaseHookForHostedTest(nullptr, nullptr);
498 __atomic_store_n(&g_OpenFinalProcessLeaseContext,
499 static_cast<OpenFinalProcessLeaseContext*>(nullptr), __ATOMIC_RELEASE);
500
501 if (!context.gateReleased) {
502 context.deleteGate.release();
503 }
504 context.safetyLease.reset();
505 const bool joined = deleter->joinForCompletion();
506
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");
512 if (passed) {
513 NOTICE("HOSTED-WAIT-TEST: PASS process-final-open-lease-release");
514 }
515 return passed;
516}
517
518bool closedFinalProcessLeaseHandoff(Process* kernelProcess) {
519 ClosedFinalProcessLeaseContext context(nullptr);
520 ObservedProcess* process = new ObservedProcess(kernelProcess, &context.destroyed);
521 context.process = process;
522
524 if (!Scheduler::instance().acquireProcess(lease, process)) {
525 delete process;
526 return check(false, "could not acquire the closed-handoff ProcessLease");
527 }
528
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();
534 lease.reset();
535 if (!drainer->joinForCompletion()) {
536 FATAL("Closed final ProcessLease cleanup could not join its drainer");
537 }
538 delete process;
539 return check(false, "could not prepare the closed final ProcessLease handoff");
540 }
541
542 // Close enumeration admission before the final release, but do not let the
543 // destruction drain enrol until the post-unlock hook opens its gate.
545 __atomic_store_n(&g_ClosedFinalProcessLeaseContext, &context, __ATOMIC_RELEASE);
546 Process::setExternalLeaseReleaseHookForHostedTest(process, observeClosedFinalProcessLeaseRelease);
547 lease.reset();
548 Process::setExternalLeaseReleaseHookForHostedTest(nullptr, nullptr);
549 __atomic_store_n(&g_ClosedFinalProcessLeaseContext,
550 static_cast<ClosedFinalProcessLeaseContext*>(nullptr), __ATOMIC_RELEASE);
551
552 if (!context.gateReleased) {
553 context.beginDrain.release();
554 }
555 bool drainerReapable = false;
556 for (size_t attempt = 0; attempt < Attempts; ++attempt) {
557 if (drainer->isReapableForHostedTest()) {
558 drainerReapable = true;
559 break;
560 }
562 }
563 if (!drainerReapable || !drainer->joinForCompletion()) {
564 FATAL("Closed final ProcessLease drainer did not retire after handoff");
565 }
566
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");
572 delete process;
573 passed &= check(context.destroyed == 1,
574 "the closed final ProcessLease fixture did not destroy its process once");
575 if (passed) {
576 NOTICE("HOSTED-WAIT-TEST: PASS process-final-closed-lease-handoff");
577 }
578 return passed;
579}
580
581bool threadLeaseIdLookup(Process* kernelProcess) {
582 Atomic<size_t> destroyed(0);
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");
587
589 const bool firstAcquired = kernelProcess->acquireThreadById(lease, first->getId());
590 bool passed = check(firstAcquired && lease.get() == first,
591 "exact thread ID lookup did not acquire its target");
592
593 const bool secondAcquired = kernelProcess->acquireThreadById(lease, second->getId());
594 passed &= check(secondAcquired && lease.get() == second,
595 "exact thread ID lookup did not replace an active lease");
596
597 const bool missingAcquired = kernelProcess->acquireThreadById(lease, ~static_cast<size_t>(0));
598 passed &=
599 check(!missingAcquired && !lease, "missing thread ID lookup did not reset an active lease");
600
601 first->start();
602 second->start();
603 const bool firstJoined = first->joinForCompletion();
604 const bool secondJoined = second->joinForCompletion();
605 passed &= check(firstJoined && secondJoined && destroyed == 2,
606 "thread ID lookup fixtures did not retire cleanly");
607 if (passed) {
608 NOTICE("HOSTED-WAIT-TEST: PASS thread-lease-id-lookup");
609 }
610 return passed;
611}
612
613bool threadLeaseBarrier(Process* kernelProcess) {
614 Atomic<size_t> destroyed(0);
615 Thread* target = new ObservedThread(kernelProcess, immediateExit, &destroyed, true);
616 target->setName("hosted ThreadLease target");
617
618 // Pin the target before making it runnable so the test controls the
619 // retirement window instead of racing the immediate-exit trampoline.
621 const bool leaseAcquired = kernelProcess->acquireThreadById(lease, target->getId());
622 bool passed =
623 check(leaseAcquired && lease.get() == target, "could not acquire the thread lease by ID");
624 if (!lease) {
625 target->start();
626 target->joinForCompletion();
627 return false;
628 }
629
630 passed &= check(target->detach(), "the ThreadLease target could not detach");
631 passed &= check(target->start(), "the delayed ThreadLease target did not start");
632
633 bool retirementClosed = false;
634 for (size_t attempt = 0; attempt < Attempts; ++attempt) {
635 Process::ThreadLease lateLease;
636 if (!kernelProcess->acquireThreadById(lateLease, target->getId())) {
637 retirementClosed = true;
638 break;
639 }
640 lateLease.reset();
642 }
643
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");
646
647 lease.reset();
648 for (size_t attempt = 0; attempt < Attempts && !destroyed; ++attempt) {
650 }
651 passed &=
652 check(destroyed == 1, "Thread deletion did not complete after its final lease released");
653 if (passed) {
654 NOTICE("HOSTED-WAIT-TEST: PASS thread-final-lease-deletion");
655 }
656 return passed;
657}
658
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;
665
667 const bool leaseAcquired = kernelProcess->acquireThread(lease, target);
668 if (!leaseAcquired) {
670 target->joinForCompletion();
671 return check(false, "could not acquire the open-admission ThreadLease");
672 }
673
674 const bool started = target->start();
675 const bool waiting = started && waitForLeaseDrain(target, &context.exitGate);
676 if (!started || !waiting) {
678 lease.reset();
679 if (!target->joinForCompletion()) {
680 FATAL("Open final ThreadLease cleanup could not join its target");
681 }
682 return check(false, "could not prepare the open-admission final-release window");
683 }
684
685 if (!target->detach()) {
687 lease.reset();
688 if (!target->joinForCompletion()) {
689 FATAL("Open final ThreadLease cleanup could not join its undetached target");
690 }
691 return check(false, "could not detach the open-admission final-release target");
692 }
693
694 __atomic_store_n(&g_OpenFinalLeaseContext, &context, __ATOMIC_RELEASE);
695 Thread::setExternalLeaseReleaseHookForHostedTest(target, observeOpenFinalLeaseRelease);
696 lease.reset();
697 Thread::setExternalLeaseReleaseHookForHostedTest(nullptr, nullptr);
698 __atomic_store_n(&g_OpenFinalLeaseContext, static_cast<OpenFinalLeaseContext*>(nullptr),
699 __ATOMIC_RELEASE);
700
701 if (!context.gateReleased) {
702 context.exitGate.release();
703 }
704 context.safetyLease.reset();
705 for (size_t attempt = 0; attempt < Attempts && !context.destroyed; ++attempt) {
707 }
708 if (!context.destroyed) {
709 FATAL("Open final ThreadLease target did not complete detached retirement");
710 }
711
712 const bool passed =
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");
718 if (passed) {
719 NOTICE("HOSTED-WAIT-TEST: PASS thread-final-open-lease-release");
720 }
721 return passed;
722}
723} // namespace
724
725bool runHostedLifetimeLeaseRegressions() {
726 Process* kernelProcess = Scheduler::instance().getKernelProcess();
727 const bool passed =
728 idleOnlyAdmissionClose() && ownedThreadTerminalJoin(kernelProcess) &&
729 processLeaseBarrier(kernelProcess) && openFinalProcessLeaseRelease(kernelProcess) &&
730 closedFinalProcessLeaseHandoff(kernelProcess) && threadLeaseIdLookup(kernelProcess) &&
731 threadLeaseBarrier(kernelProcess) && openFinalThreadLeaseRelease(kernelProcess);
732 if (passed) {
733 NOTICE("HOSTED-WAIT-TEST: PASS lifetime-leases");
734 }
735 return passed;
736}
MUST_USE_RESULT bool tryCloseIfIdle()
MUST_USE_RESULT bool tryAcquire(Lease &lease)
void publish()
Definition Process.cc:944
MUST_USE_RESULT bool acquireThreadById(ThreadLease &lease, size_t id)
Definition Process.cc:1446
MUST_USE_RESULT bool acquireThread(ThreadLease &lease, size_t n)
Definition Process.cc:1385
void prepareForDestruction()
Definition Process.cc:1029
static Scheduler & instance()
Definition Scheduler.h:96
MUST_USE_RESULT bool acquireProcess(ProcessLease &lease, size_t n)
Definition Scheduler.cc:275
void yield()
Definition Scheduler.cc:236
void removeProcess(Process *pProcess)
Definition Scheduler.cc:219
void setUnwindState(UnwindType ut)
Definition Thread.cc:3580
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:519
bool joinForCompletion()
Definition Thread.cc:2750
bool detach()
Definition Thread.cc:2987
size_t getId()
Definition Thread.h:465
bool start()
Definition Thread.cc:751