The Pedigree Project 0.1
buffer-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#include "pedigree/kernel/utilities/Buffer.h"
14
15namespace {
16enum class BufferOperation {
17 Read,
18 Write,
19 WriteAvailable,
20 CanRead,
21 CanWrite,
22};
23
24struct BufferWaitContext {
25 BufferWaitContext(Buffer<uint8_t>* buffer, BufferOperation operation, uint8_t value)
26 : buffer(buffer), operation(operation), entered(0), returned(0), result(1), value(value) {}
27
28 Buffer<uint8_t>* buffer;
29 BufferOperation operation;
30 Atomic<size_t> entered;
31 Atomic<size_t> returned;
32 Atomic<size_t> result;
33 uint8_t value;
34};
35
36int waitOnBuffer(void* parameter) {
37 BufferWaitContext* context = reinterpret_cast<BufferWaitContext*>(parameter);
38 context->entered += 1;
39 switch (context->operation) {
40 case BufferOperation::Read:
41 context->result = context->buffer->read(&context->value, 1, true);
42 break;
43 case BufferOperation::Write:
44 context->result = context->buffer->write(&context->value, 1, true);
45 break;
46 case BufferOperation::WriteAvailable:
47 context->result = context->buffer->writeAvailable(&context->value, 1);
48 break;
49 case BufferOperation::CanRead:
50 context->result = context->buffer->canRead(true) ? 1 : 0;
51 break;
52 case BufferOperation::CanWrite:
53 context->result = context->buffer->canWrite(true) ? 1 : 0;
54 break;
55 }
56 context->returned += 1;
57 return 0;
58}
59
60bool waitUntilBlocked(Thread* thread, BufferWaitContext& context,
61 Thread::DebugState state = Thread::CondWait) {
62 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
63 while (Time::getTicks() < deadline) {
64 Thread::WaitDebugInfo wait = {};
65 uintptr_t address = 0;
66 if (context.entered == 1 && context.returned == 0 && thread->getWaitDebugInfo(wait) &&
67 wait.queued && thread->getDebugState(address) == state) {
68 return true;
69 }
70 if (thread->getStatus() == Thread::AwaitingJoin) {
71 return false;
72 }
74 }
75 return false;
76}
77
78bool waitUntilReturned(BufferWaitContext& first, BufferWaitContext& second) {
79 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
80 while ((first.returned == 0 || second.returned == 0) && Time::getTicks() < deadline) {
82 }
83 return first.returned == 1 && second.returned == 1;
84}
85
86bool availableWriteWaitsForLockOwnership() {
87 Buffer<uint8_t> buffer(1);
88 BufferWaitContext context(&buffer, BufferOperation::WriteAvailable, 0x92);
89 Thread* writer = new Thread(Scheduler::instance().getKernelProcess(), waitOnBuffer, &context,
90 nullptr, false, true, true);
91 writer->setName("hosted Buffer available-write contention");
92
93 buffer.acquireHostedOperationLock();
94 const bool started = writer->start();
95 const bool blocked = started && waitUntilBlocked(writer, context, Thread::SemWait);
96 buffer.releaseHostedOperationLock();
97 const bool joined = started && writer->joinForCompletion();
98 if (!started) {
99 delete writer;
100 }
101
102 uint8_t observed = 0;
103 const bool delivered = buffer.read(&observed, 1, false) == 1 && observed == context.value;
104 return started && blocked && joined && context.returned == 1 && context.result == 1 &&
105 delivered && buffer.getHostedActiveOperationCount() == 0;
106}
107
108bool blockedReaderReturnsWhenReadsAreDisabled() {
109 Buffer<uint8_t> buffer(1);
110 BufferWaitContext firstContext(&buffer, BufferOperation::Read, 0);
111 BufferWaitContext secondContext(&buffer, BufferOperation::Read, 0);
112 Thread* first = new Thread(Scheduler::instance().getKernelProcess(), waitOnBuffer, &firstContext,
113 nullptr, false, true, true);
114 Thread* second = new Thread(Scheduler::instance().getKernelProcess(), waitOnBuffer,
115 &secondContext, nullptr, false, true, true);
116 first->setName("hosted disabled-buffer reader one");
117 second->setName("hosted disabled-buffer reader two");
118
119 const bool firstStarted = first->start();
120 const bool secondStarted = second->start();
121 const bool started = firstStarted && secondStarted;
122 const bool blocked =
123 started && waitUntilBlocked(first, firstContext) && waitUntilBlocked(second, secondContext);
124 buffer.disableReads();
125 const bool returned = blocked && waitUntilReturned(firstContext, secondContext);
126
127 // Keep the fixture finite against the historical one-sided wakeup.
128 if ((firstStarted || secondStarted) && !returned) {
129 buffer.disableWrites();
130 }
131 const bool firstJoined = firstStarted && first->joinForCompletion();
132 const bool secondJoined = secondStarted && second->joinForCompletion();
133 if (!firstStarted) {
134 delete first;
135 }
136 if (!secondStarted) {
137 delete second;
138 }
139
140 return started && blocked && returned && firstJoined && secondJoined &&
141 firstContext.entered == 1 && firstContext.returned == 1 && firstContext.result == 0 &&
142 secondContext.entered == 1 && secondContext.returned == 1 && secondContext.result == 0;
143}
144
145bool blockedWriterReturnsWhenWritesAreDisabled() {
146 Buffer<uint8_t> buffer(1);
147 const uint8_t firstValue = 0x51;
148 const bool filled = buffer.write(&firstValue, 1, false) == 1;
149 BufferWaitContext firstContext(&buffer, BufferOperation::Write, 0x7a);
150 BufferWaitContext secondContext(&buffer, BufferOperation::Write, 0x39);
151 Thread* first = new Thread(Scheduler::instance().getKernelProcess(), waitOnBuffer, &firstContext,
152 nullptr, false, true, true);
153 Thread* second = new Thread(Scheduler::instance().getKernelProcess(), waitOnBuffer,
154 &secondContext, nullptr, false, true, true);
155 first->setName("hosted disabled-buffer writer one");
156 second->setName("hosted disabled-buffer writer two");
157
158 const bool firstStarted = filled && first->start();
159 const bool secondStarted = filled && second->start();
160 const bool started = firstStarted && secondStarted;
161 const bool blocked =
162 started && waitUntilBlocked(first, firstContext) && waitUntilBlocked(second, secondContext);
163 buffer.disableWrites();
164 const bool returned = blocked && waitUntilReturned(firstContext, secondContext);
165
166 // Keep the fixture finite against the historical one-sided wakeup.
167 if ((firstStarted || secondStarted) && !returned) {
168 buffer.disableReads();
169 }
170 const bool firstJoined = firstStarted && first->joinForCompletion();
171 const bool secondJoined = secondStarted && second->joinForCompletion();
172 if (!firstStarted) {
173 delete first;
174 }
175 if (!secondStarted) {
176 delete second;
177 }
178
179 uint8_t observed = 0;
180 const bool preserved =
181 returned && buffer.read(&observed, 1, false) == 1 && observed == firstValue;
182 return filled && started && blocked && returned && firstJoined && secondJoined &&
183 firstContext.entered == 1 && firstContext.returned == 1 && firstContext.result == 0 &&
184 secondContext.entered == 1 && secondContext.returned == 1 && secondContext.result == 0 &&
185 preserved;
186}
187
188bool blockedCanReadReturnsWhenWritesAreDisabled() {
189 Buffer<uint8_t> buffer(1);
190 BufferWaitContext firstContext(&buffer, BufferOperation::CanRead, 0);
191 BufferWaitContext secondContext(&buffer, BufferOperation::CanRead, 0);
192 Thread* first = new Thread(Scheduler::instance().getKernelProcess(), waitOnBuffer, &firstContext,
193 nullptr, false, true, true);
194 Thread* second = new Thread(Scheduler::instance().getKernelProcess(), waitOnBuffer,
195 &secondContext, nullptr, false, true, true);
196 first->setName("hosted closed-writer readiness waiter one");
197 second->setName("hosted closed-writer readiness waiter two");
198
199 const bool firstStarted = first->start();
200 const bool secondStarted = second->start();
201 const bool started = firstStarted && secondStarted;
202 const bool blocked =
203 started && waitUntilBlocked(first, firstContext) && waitUntilBlocked(second, secondContext);
204 buffer.disableWrites();
205 const bool returned = blocked && waitUntilReturned(firstContext, secondContext);
206
207 // Keep the fixture finite if readiness ignores the peer-side terminal state.
208 if ((firstStarted || secondStarted) && !returned) {
209 buffer.disableReads();
210 }
211 const bool firstJoined = firstStarted && first->joinForCompletion();
212 const bool secondJoined = secondStarted && second->joinForCompletion();
213 if (!firstStarted) {
214 delete first;
215 }
216 if (!secondStarted) {
217 delete second;
218 }
219
220 return started && blocked && returned && firstJoined && secondJoined &&
221 firstContext.entered == 1 && firstContext.returned == 1 && firstContext.result == 0 &&
222 secondContext.entered == 1 && secondContext.returned == 1 && secondContext.result == 0;
223}
224
225bool blockedCanWriteReturnsWhenReadsAreDisabled() {
226 Buffer<uint8_t> buffer(1);
227 const uint8_t value = 0x24;
228 const bool filled = buffer.write(&value, 1, false) == 1;
229 BufferWaitContext firstContext(&buffer, BufferOperation::CanWrite, 0);
230 BufferWaitContext secondContext(&buffer, BufferOperation::CanWrite, 0);
231 Thread* first = new Thread(Scheduler::instance().getKernelProcess(), waitOnBuffer, &firstContext,
232 nullptr, false, true, true);
233 Thread* second = new Thread(Scheduler::instance().getKernelProcess(), waitOnBuffer,
234 &secondContext, nullptr, false, true, true);
235 first->setName("hosted closed-reader readiness waiter one");
236 second->setName("hosted closed-reader readiness waiter two");
237
238 const bool firstStarted = filled && first->start();
239 const bool secondStarted = filled && second->start();
240 const bool started = firstStarted && secondStarted;
241 const bool blocked =
242 started && waitUntilBlocked(first, firstContext) && waitUntilBlocked(second, secondContext);
243 buffer.disableReads();
244 const bool returned = blocked && waitUntilReturned(firstContext, secondContext);
245
246 // Keep the fixture finite if readiness ignores the peer-side terminal state.
247 if ((firstStarted || secondStarted) && !returned) {
248 buffer.disableWrites();
249 }
250 const bool firstJoined = firstStarted && first->joinForCompletion();
251 const bool secondJoined = secondStarted && second->joinForCompletion();
252 if (!firstStarted) {
253 delete first;
254 }
255 if (!secondStarted) {
256 delete second;
257 }
258
259 return filled && started && blocked && returned && firstJoined && secondJoined &&
260 firstContext.entered == 1 && firstContext.returned == 1 && firstContext.result == 0 &&
261 secondContext.entered == 1 && secondContext.returned == 1 && secondContext.result == 0 &&
262 buffer.getDataSize() == 1;
263}
264} // namespace
265
266bool runHostedBufferRegressions() {
267 const bool availableWritePassed = availableWriteWaitsForLockOwnership();
268 if (availableWritePassed) {
269 NOTICE("HOSTED-WAIT-TEST: PASS buffer-write-available-contention");
270 } else {
271 ERROR("HOSTED-WAIT-TEST: FAIL buffer-write-available-contention: contended byte was lost");
272 }
273
274 const bool readerPassed = blockedReaderReturnsWhenReadsAreDisabled();
275 if (readerPassed) {
276 NOTICE("HOSTED-WAIT-TEST: PASS buffer-disable-reads-wake");
277 } else {
278 ERROR("HOSTED-WAIT-TEST: FAIL buffer-disable-reads-wake: a blocked reader was not released");
279 }
280
281 const bool writerPassed = blockedWriterReturnsWhenWritesAreDisabled();
282 if (writerPassed) {
283 NOTICE("HOSTED-WAIT-TEST: PASS buffer-disable-writes-wake");
284 } else {
285 ERROR("HOSTED-WAIT-TEST: FAIL buffer-disable-writes-wake: a blocked writer was not released");
286 }
287
288 const bool canReadPassed = blockedCanReadReturnsWhenWritesAreDisabled();
289 if (canReadPassed) {
290 NOTICE("HOSTED-WAIT-TEST: PASS buffer-can-read-peer-close");
291 } else {
292 ERROR(
293 "HOSTED-WAIT-TEST: FAIL buffer-can-read-peer-close: empty readiness requeued after "
294 "writes closed");
295 }
296
297 const bool canWritePassed = blockedCanWriteReturnsWhenReadsAreDisabled();
298 if (canWritePassed) {
299 NOTICE("HOSTED-WAIT-TEST: PASS buffer-can-write-peer-close");
300 } else {
301 ERROR(
302 "HOSTED-WAIT-TEST: FAIL buffer-can-write-peer-close: full readiness requeued after reads "
303 "closed");
304 }
305
306 return availableWritePassed && readerPassed && writerPassed && canReadPassed && canWritePassed;
307}
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
DebugState
Definition Thread.h:176
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
bool joinForCompletion()
Definition Thread.cc:2771
Status getStatus() const
Definition Thread.h:431
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
bool start()
Definition Thread.cc:794