The Pedigree Project 0.1
pipe-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/Scheduler.h"
11#include "pedigree/kernel/process/Thread.h"
12#include "pedigree/kernel/time/Time.h"
13
14#include "modules/system/vfs/Pipe.h"
15
16namespace {
17class EndpointLockedPipe : public Pipe {
18 public:
19 explicit EndpointLockedPipe(const char* name, uintptr_t inode)
20 : Pipe(String(name), 0, 0, 0, inode, nullptr, 0, nullptr, false) {}
21
22 void lockEndpointState() {
23 m_Lock.acquire();
24 }
25
26 void unlockEndpointState() {
27 m_Lock.release();
28 }
29};
30
31struct PipeWaitContext {
32 explicit PipeWaitContext(Pipe* pipe) : pipe(pipe), entered(0), returned(0), observedReader(0) {}
33
34 Pipe* pipe;
35 Atomic<size_t> entered;
36 Atomic<size_t> returned;
37 Atomic<size_t> observedReader;
38};
39
40struct PipeIoContext {
41 enum Operation {
42 Read,
43 Write,
44 };
45
46 PipeIoContext(Pipe* pipe, Operation operation)
47 : pipe(pipe), operation(operation), entered(0), returned(0), result(1), value('p') {}
48
49 Pipe* pipe;
50 Operation operation;
51 Atomic<size_t> entered;
52 Atomic<size_t> returned;
53 Atomic<size_t> result;
54 char value;
55};
56
57int accessPipeEndpointState(void* parameter) {
58 PipeIoContext* context = reinterpret_cast<PipeIoContext*>(parameter);
59 context->entered += 1;
60 if (context->operation == PipeIoContext::Read) {
61 context->result =
62 context->pipe->readBytewise(0, 1, reinterpret_cast<uintptr_t>(&context->value), false);
63 } else {
64 context->result =
65 context->pipe->writeBytewise(0, 1, reinterpret_cast<uintptr_t>(&context->value), false);
66 }
67 context->returned += 1;
68 return 0;
69}
70
71int waitForPipeReader(void* parameter) {
72 PipeWaitContext* context = reinterpret_cast<PipeWaitContext*>(parameter);
73 context->entered += 1;
74 if (context->pipe->waitForReader(true)) {
75 context->observedReader += 1;
76 }
77 context->returned += 1;
78 return 0;
79}
80
81Thread* startPipeWaiter(PipeWaitContext& context, const char* name) {
82 Thread* thread = new Thread(Scheduler::instance().getKernelProcess(), waitForPipeReader, &context,
83 nullptr, false, true);
84 thread->setName(String(name));
85 return thread;
86}
87
88bool waitForCount(Atomic<size_t>& value, size_t expected) {
89 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
90 while (value < expected && Time::getTicks() < deadline) {
92 }
93 return value == expected;
94}
95
96bool waitUntilBlocked(Thread* thread) {
97 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
98 while (Time::getTicks() < deadline) {
99 Thread::WaitDebugInfo wait = {};
100 uintptr_t address = 0;
101 if (thread->getWaitDebugInfo(wait) && wait.queued &&
102 thread->getDebugState(address) == Thread::CondWait) {
103 return true;
104 }
105 if (thread->getStatus() == Thread::AwaitingJoin) {
106 return false;
107 }
109 }
110 return false;
111}
112
113bool waitUntilEndpointLockBlocked(Thread* thread) {
114 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
115 while (Time::getTicks() < deadline) {
116 uintptr_t address = 0;
117 if (thread->getDebugState(address) == Thread::SemWait) {
118 return true;
119 }
120 if (thread->getStatus() == Thread::AwaitingJoin) {
121 return false;
122 }
124 }
125 return false;
126}
127
128bool runEndpointLockCase(PipeIoContext::Operation operation, const char* name, uintptr_t inode) {
129 EndpointLockedPipe pipe(name, inode);
130 PipeIoContext context(&pipe, operation);
131 pipe.lockEndpointState();
132 Thread* worker = new Thread(Scheduler::instance().getKernelProcess(), accessPipeEndpointState,
133 &context, nullptr, false, true);
134 worker->setName("hosted pipe endpoint snapshot");
135
136 const bool blocked =
137 waitUntilEndpointLockBlocked(worker) && context.entered == 1 && context.returned == 0;
138 pipe.unlockEndpointState();
139 const bool joined = worker->join();
140 return blocked && joined && context.returned == 1 && context.result == 0;
141}
142
143bool endpointCountsUseFileMutex() {
144 return runEndpointLockCase(PipeIoContext::Read, "hosted-locked-reader-fifo", 3) &&
145 runEndpointLockCase(PipeIoContext::Write, "hosted-locked-writer-fifo", 4);
146}
147
148bool staleReaderDoesNotSatisfyOpen() {
149 Pipe pipe(String("hosted-stale-reader-fifo"), 0, 0, 0, 1, nullptr, 0, nullptr, false);
150
151 // A reader which has already departed is not the FIFO-open predicate.
152 pipe.increaseRefCount(false);
153 pipe.decreaseRefCount(false);
154
155 PipeWaitContext context(&pipe);
156 Thread* waiter = startPipeWaiter(context, "hosted stale FIFO reader waiter");
157 const bool blocked = waitUntilBlocked(waiter) && context.returned == 0;
158
159 pipe.increaseRefCount(false);
160 const bool returned = waitForCount(context.returned, 1);
161 const bool joined = waiter->join();
162 pipe.decreaseRefCount(false);
163
164 return blocked && returned && joined && context.observedReader == 1;
165}
166
167bool oneReaderWakesEveryWriter() {
168 Pipe pipe(String("hosted-broadcast-reader-fifo"), 0, 0, 0, 2, nullptr, 0, nullptr, false);
169 PipeWaitContext firstContext(&pipe);
170 PipeWaitContext secondContext(&pipe);
171 Thread* first = startPipeWaiter(firstContext, "hosted FIFO writer waiter one");
172 Thread* second = startPipeWaiter(secondContext, "hosted FIFO writer waiter two");
173
174 const bool bothBlocked = waitUntilBlocked(first) && waitUntilBlocked(second) &&
175 firstContext.returned == 0 && secondContext.returned == 0;
176
177 pipe.increaseRefCount(false);
178 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
179 while ((firstContext.returned != 1 || secondContext.returned != 1) &&
180 Time::getTicks() < deadline) {
182 }
183 const bool bothReturned = firstContext.returned == 1 && secondContext.returned == 1;
184
185 // Keep cleanup finite even when this regression detects the historical
186 // one-token/one-writer behavior.
187 if (!bothReturned) {
188 pipe.increaseRefCount(false);
189 }
190 const bool firstJoined = first->join();
191 const bool secondJoined = second->join();
192 pipe.decreaseRefCount(false);
193 if (!bothReturned) {
194 pipe.decreaseRefCount(false);
195 }
196
197 return bothBlocked && bothReturned && firstJoined && secondJoined &&
198 firstContext.observedReader == 1 && secondContext.observedReader == 1;
199}
200} // namespace
201
202bool runHostedPipeRegressions() {
203 const bool passed = staleReaderDoesNotSatisfyOpen() && oneReaderWakesEveryWriter() &&
204 endpointCountsUseFileMutex();
205 if (passed) {
206 NOTICE("HOSTED-WAIT-TEST: PASS fifo-reader-predicate");
207 } else {
208 ERROR(
209 "HOSTED-WAIT-TEST: FAIL fifo-reader-predicate: FIFO open did not "
210 "use one locked reader predicate");
211 }
212 return passed;
213}
Definition Pipe.h:36
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
bool join()
Definition Thread.cc:2767
Status getStatus() const
Definition Thread.h:431
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570