The Pedigree Project 0.1
paging-request-regressions.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/Atomic.h"
3#include "pedigree/kernel/Log.h"
4#include "pedigree/kernel/process/Scheduler.h"
5#include "pedigree/kernel/process/Semaphore.h"
6#include "pedigree/kernel/process/Thread.h"
7#include "pedigree/kernel/utilities/RequestQueue.h"
8
9#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS && THREADS
10namespace {
11class Queue final : public RequestQueue {
12 public:
13 Queue() : RequestQueue(MakeConstantString("paging completion fixture")), entered(0), release(0) {}
14 ~Queue() override {
15 destroy();
16 }
17 Semaphore entered;
18 Semaphore release;
19 Atomic<size_t> failures{0};
20 Atomic<size_t> cancellations{0};
21
22 protected:
23 uint64_t executeRequest(uint64_t hold, uint64_t token, uint64_t, uint64_t, uint64_t, uint64_t,
24 uint64_t, uint64_t) override {
26 (token && waitForPreallocated(*reinterpret_cast<PreallocatedRequest*>(token))))
27 failures += 1;
28 if (hold) {
29 entered.release();
30 if (!release.acquireForCompletion(1, 10))
31 failures += 1;
32 }
33 return 42;
34 }
35 void cancelRequest(const Request& request) override {
36 cancellations += 1;
38 (request.p2 && waitForPreallocated(*reinterpret_cast<PreallocatedRequest*>(request.p2))))
39 failures += 1;
40 }
41};
42struct Wait {
43 Queue& queue;
45 Semaphore done{0};
46 bool result = false;
47 static int run(void* opaque) {
48 auto& self = *static_cast<Wait*>(opaque);
49 self.result = self.queue.waitForPreallocated(self.token);
50 self.done.release();
51 return 0;
52 }
53};
54struct ReleaseGate {
55 Queue& queue;
56 RequestQueue::PreallocatedRequest* token = nullptr;
57 Semaphore entered{0};
58 Semaphore release{0};
59 size_t failures = 0;
60 static void released(void* opaque) {
61 auto& self = *static_cast<ReleaseGate*>(opaque);
62 if (self.queue.canWaitForCompletion() || self.queue.waitForPreallocated(*self.token))
63 ++self.failures;
64 self.entered.release();
65 // This controlled fixture gate exposes the token's Releasing interval.
66 if (!self.release.acquireForCompletion(1, 10))
67 ++self.failures;
68 }
69};
70bool queued(Thread* thread) {
71 for (size_t attempts = 0; attempts < 10000; ++attempts) {
73 if (thread->getWaitDebugInfo(info) && info.queued)
74 return true;
76 }
77 return false;
78}
79struct Destroy {
80 Queue& queue;
81 Semaphore done{0};
82 static int run(void* opaque) {
83 auto& self = *static_cast<Destroy*>(opaque);
84 self.queue.destroy();
85 self.done.release();
86 return 0;
87 }
88};
89Thread* worker(Thread::ThreadStartFunc function, void* context) {
90 return new Thread(Scheduler::instance().getKernelProcess(), function, context, nullptr, false,
91 true);
92}
93} // namespace
94
95bool runHostedPagingRequestRegressions() {
96 using Result = RequestQueue::PreallocatedPublishResult;
97 bool passed = true;
98 {
99 Queue queue;
100 ReleaseGate release{queue};
101 RequestQueue::PreallocatedRequest token(ReleaseGate::released, &release);
102 release.token = &token;
103 queue.initialise();
104 passed &= queue.publishPreallocated(token, 0, 1, reinterpret_cast<uintptr_t>(&token)) ==
105 Result::Accepted;
106 const bool entered = queue.entered.acquireForCompletion(1, 10);
107 Wait waiter{queue, token};
108 Thread* thread = worker(Wait::run, &waiter);
109 const bool beforeCompletion = queued(thread) && !waiter.done.tryAcquire();
110 queue.release.release();
111 const bool releasing = release.entered.acquireForCompletion(1, 10);
112 const bool duringRelease = !token.isAvailable() && !waiter.done.tryAcquire();
113 release.release.release();
114 const bool completed = waiter.done.acquireForCompletion(1, 10);
115 passed &= entered && beforeCompletion && releasing && duringRelease && completed;
116 passed &= thread->joinForCompletion() && waiter.result && token.isAvailable();
117 passed &= queue.waitForPreallocated(token); // Completion preceded this admission.
118 passed &= !queue.failures && !release.failures;
119 queue.destroy();
120 }
121 {
122 Queue queue;
125 queue.initialise();
126 passed &= queue.publishPreallocated(blocker, 0, 1, reinterpret_cast<uintptr_t>(&blocker)) ==
127 Result::Accepted;
128 passed &= queue.entered.acquireForCompletion(1, 10);
129 passed &= queue.publishPreallocated(cancelled, 0, 0, reinterpret_cast<uintptr_t>(&cancelled)) ==
130 Result::Accepted;
131 Wait waiter{queue, cancelled};
132 Thread* waiting = worker(Wait::run, &waiter);
133 passed &= queued(waiting);
134 Destroy destroy{queue};
135 Thread* destroying = worker(Destroy::run, &destroy);
136 for (size_t attempts = 0;
137 attempts < 10000 && queue.getLifecycleState() != RequestQueue::LifecycleState::Stopping;
138 ++attempts)
140 passed &= queue.getLifecycleState() == RequestQueue::LifecycleState::Stopping;
141 queue.release.release();
142 passed &= destroy.done.acquireForCompletion(1, 10) && waiter.done.acquireForCompletion(1, 10);
143 passed &= destroying->joinForCompletion() && waiting->joinForCompletion();
144 passed &= waiter.result && cancelled.isAvailable() && blocker.isAvailable();
145 passed &= queue.cancellations == 1 && !queue.failures;
146 }
147 if (passed)
148 NOTICE("HOSTED-STORAGE-PAGING: PASS request-completion-retirement");
149 else
150 ERROR("HOSTED-STORAGE-PAGING: FAIL request-completion-retirement");
151 return passed;
152}
153#endif
MUST_USE_RESULT bool waitForPreallocated(PreallocatedRequest &request)
bool canWaitForCompletion()
virtual void destroy()
virtual void cancelRequest(const Request &request)
virtual uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5, uint64_t p6, uint64_t p7, uint64_t p8)=0
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
void release(size_t n=1)
Definition Semaphore.cc:546
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:369
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
bool joinForCompletion()
Definition Thread.cc:2771
int(* ThreadStartFunc)(void *)
Definition Thread.h:188