The Pedigree Project 0.1
network-filter-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/processor/Processor.h"
13
14#include "modules/system/network-stack/Filter.h"
15
16namespace {
17constexpr size_t Attempts = 10000;
18constexpr bool PinTestThreads = HOSTED;
19
20struct FilterLifetimeContext {
21 FilterLifetimeContext()
22 : remover(nullptr),
23 callbackId(static_cast<size_t>(-1)),
24 phase(0),
25 callbackCalls(0),
26 callbackAfterRemoval(0),
27 removalReturned(0),
28 observedDrain(0),
29 failures(0) {}
30
31 Thread* remover;
32 size_t callbackId;
33 Atomic<size_t> phase;
34 Atomic<size_t> callbackCalls;
35 Atomic<size_t> callbackAfterRemoval;
36 Atomic<size_t> removalReturned;
37 Atomic<size_t> observedDrain;
38 Atomic<size_t> failures;
39};
40
41FilterLifetimeContext* g_FilterLifetimeContext = nullptr;
42
43bool lifetimeFilter(uintptr_t, size_t) {
44 FilterLifetimeContext* context = g_FilterLifetimeContext;
45 context->callbackCalls += 1;
46 if (context->removalReturned) {
47 context->callbackAfterRemoval += 1;
48 }
49 return true;
50}
51
52void filterPinHook(bool (*callback)(uintptr_t, size_t), size_t id) {
53 FilterLifetimeContext* context = g_FilterLifetimeContext;
54 if (!context || callback != lifetimeFilter || id != context->callbackId ||
55 !context->phase.compareAndSwap(0, 1)) {
56 return;
57 }
58
59 for (size_t attempt = 0; attempt < Attempts; ++attempt) {
60 Thread::WaitDebugInfo info = {};
61 uintptr_t debugAddress = 0;
62 if (context->remover->getWaitDebugInfo(info) && info.queue && info.channelOwner &&
63 info.queued && context->remover->getDebugState(debugAddress) == Thread::CallbackDrain &&
64 debugAddress == reinterpret_cast<uintptr_t>(lifetimeFilter)) {
65 context->observedDrain += 1;
66 break;
67 }
69 }
70
71 if (!context->observedDrain) {
72 context->failures += 1;
73 }
74 context->phase = 2;
75}
76
77int removePinnedFilter(void* parameter) {
78 FilterLifetimeContext* context = reinterpret_cast<FilterLifetimeContext*>(parameter);
79 for (size_t attempt = 0; attempt < Attempts && context->phase != static_cast<size_t>(1);
80 ++attempt) {
82 }
83 if (context->phase != static_cast<size_t>(1)) {
84 context->failures += 1;
85 return 1;
86 }
87
88 if (!NetworkFilter::instance().removeCallback(1, context->callbackId)) {
89 context->failures += 1;
90 }
91 context->removalReturned += 1;
92 return 0;
93}
94
95struct SelfRemovalContext {
96 SelfRemovalContext() : callbackId(static_cast<size_t>(-1)), calls(0), deferred(0) {}
97
98 size_t callbackId;
99 Atomic<size_t> calls;
100 Atomic<size_t> deferred;
101};
102
103SelfRemovalContext* g_SelfRemovalContext = nullptr;
104
105bool selfRemovingFilter(uintptr_t, size_t) {
106 SelfRemovalContext* context = g_SelfRemovalContext;
107 context->calls += 1;
108 if (!NetworkFilter::instance().removeCallback(1, context->callbackId)) {
109 context->deferred += 1;
110 }
111 return true;
112}
113
114bool check(bool condition, const char* test, const char* detail) {
115 if (condition) {
116 return true;
117 }
118#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
119 ERROR("QEMU-CONCURRENCY-TEST: FAIL " << test << ": " << detail);
120#else
121 ERROR("HOSTED-WAIT-TEST: FAIL " << test << ": " << detail);
122#endif
123 return false;
124}
125
126bool filterCallbackLifetimeBarrier() {
128 FilterLifetimeContext context;
129 g_FilterLifetimeContext = &context;
130 context.callbackId = filter.installCallback(1, lifetimeFilter);
131 context.remover = new Thread(Scheduler::instance().getKernelProcess(), removePinnedFilter,
132 &context, nullptr, false, PinTestThreads);
133 context.remover->setName("hosted network-filter remover");
134
135 NetworkFilter::setCallbackPinHook(filterPinHook);
136 filter.filter(1, 0, 0);
137 const bool joined = context.remover->join();
138 NetworkFilter::setCallbackPinHook(nullptr);
139
140 const size_t callsAfterRemoval = context.callbackCalls;
141 filter.filter(1, 0, 0);
142 g_FilterLifetimeContext = nullptr;
143
144 SelfRemovalContext selfRemoval;
145 g_SelfRemovalContext = &selfRemoval;
146 selfRemoval.callbackId = filter.installCallback(1, selfRemovingFilter);
147 filter.filter(1, 0, 0);
148 const size_t callsAfterSelfRemoval = selfRemoval.calls;
149 filter.filter(1, 0, 0);
150 g_SelfRemovalContext = nullptr;
151 const bool selfRemovalRetired = filter.removeCallback(1, selfRemoval.callbackId);
152
153 bool passed = true;
154 passed &= check(context.callbackId != static_cast<size_t>(-1) && joined && !context.failures,
155 "network-filter-callback-lifetime",
156 "the concurrent removal worker did not complete cleanly");
157 passed &= check(context.observedDrain == static_cast<size_t>(1) &&
158 context.removalReturned == static_cast<size_t>(1),
159 "network-filter-callback-lifetime",
160 "removeCallback did not wait for the committed filter invocation");
161 passed &= check(context.callbackCalls == static_cast<size_t>(1) &&
162 context.callbackCalls == callsAfterRemoval && !context.callbackAfterRemoval,
163 "network-filter-callback-lifetime",
164 "a filter callback began after removeCallback returned");
165 passed &= check(selfRemoval.callbackId != static_cast<size_t>(-1) && callsAfterSelfRemoval == 1 &&
166 selfRemoval.calls == static_cast<size_t>(1) &&
167 selfRemoval.deferred == static_cast<size_t>(1) && selfRemovalRetired,
168 "network-filter-callback-lifetime",
169 "self-removal did not close admission for external retirement");
170
171 if (passed) {
172#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
173 NOTICE("QEMU-CONCURRENCY-TEST: PASS network-filter-callback-lifetime");
174#else
175 NOTICE("HOSTED-WAIT-TEST: PASS network-filter-callback-lifetime");
176#endif
177 }
178 return passed;
179}
180
181constexpr uintptr_t ReciprocalPacketA = 1;
182constexpr uintptr_t ReciprocalPacketB = 2;
183
184struct ReciprocalRemovalContext {
185 ReciprocalRemovalContext()
186 : callbackA(static_cast<size_t>(-1)),
187 callbackB(static_cast<size_t>(-1)),
188 enteredA(0),
189 enteredB(0),
190 returnedA(0),
191 returnedB(0),
192 callsA(0),
193 callsB(0),
194 processorA(static_cast<size_t>(-1)),
195 processorB(static_cast<size_t>(-1)),
196 deferredA(0),
197 deferredB(0),
198 failures(0) {}
199
200 size_t callbackA;
201 size_t callbackB;
202 Atomic<size_t> enteredA;
203 Atomic<size_t> enteredB;
204 Atomic<size_t> returnedA;
205 Atomic<size_t> returnedB;
206 Atomic<size_t> callsA;
207 Atomic<size_t> callsB;
208 Atomic<size_t> processorA;
209 Atomic<size_t> processorB;
210 Atomic<size_t> deferredA;
211 Atomic<size_t> deferredB;
212 Atomic<size_t> failures;
213};
214
215ReciprocalRemovalContext* g_ReciprocalRemovalContext = nullptr;
216
217bool reciprocalFilterA(uintptr_t packet, size_t) {
218 ReciprocalRemovalContext* context = g_ReciprocalRemovalContext;
219 context->callsA += 1;
220 if (packet != ReciprocalPacketA) {
221 return true;
222 }
223
224 context->processorA = Processor::id();
225 context->enteredA = 1;
226 for (size_t attempt = 0; attempt < Attempts && !context->enteredB; ++attempt) {
228 }
229 if (!context->enteredB) {
230 context->failures += 1;
231 return false;
232 }
233
234 if (!NetworkFilter::instance().removeCallback(1, context->callbackB)) {
235 context->deferredA = 1;
236 }
237 context->returnedA = 1;
238 return false;
239}
240
241bool reciprocalFilterB(uintptr_t packet, size_t) {
242 ReciprocalRemovalContext* context = g_ReciprocalRemovalContext;
243 context->processorB = Processor::id();
244 context->callsB += 1;
245 if (packet != ReciprocalPacketB) {
246 return true;
247 }
248
249 context->enteredB = 1;
250 for (size_t attempt = 0; attempt < Attempts && !context->enteredA; ++attempt) {
252 }
253 if (!context->enteredA) {
254 context->failures += 1;
255 return false;
256 }
257
258 if (!NetworkFilter::instance().removeCallback(1, context->callbackA)) {
259 context->deferredB = 1;
260 }
261 context->returnedB = 1;
262 return false;
263}
264
265int runReciprocalFilterA(void*) {
266 NetworkFilter::instance().filter(1, ReciprocalPacketA, 0);
267 return 0;
268}
269
270int runReciprocalFilterB(void*) {
271 NetworkFilter::instance().filter(1, ReciprocalPacketB, 0);
272 return 0;
273}
274
275bool reciprocalCallbackRemoval() {
277 ReciprocalRemovalContext context;
278 g_ReciprocalRemovalContext = &context;
279 context.callbackA = filter.installCallback(1, reciprocalFilterA);
280 context.callbackB = filter.installCallback(1, reciprocalFilterB);
281
282 Process* process = Scheduler::instance().getKernelProcess();
283 Thread* first =
284 new Thread(process, runReciprocalFilterA, nullptr, nullptr, false, PinTestThreads);
285 Thread* second =
286 new Thread(process, runReciprocalFilterB, nullptr, nullptr, false, PinTestThreads);
287 first->setName("reciprocal network-filter callback A");
288 second->setName("reciprocal network-filter callback B");
289 const bool firstJoined = first->joinForCompletion();
290 const bool secondJoined = second->joinForCompletion();
291
292 const size_t callsA = context.callsA;
293 const size_t callsB = context.callsB;
294 filter.filter(1, 0, 0);
295 g_ReciprocalRemovalContext = nullptr;
296 const bool retiredA = filter.removeCallback(1, context.callbackA);
297 const bool retiredB = filter.removeCallback(1, context.callbackB);
298
299#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
300 NOTICE("QEMU-CONCURRENCY-TEST: network-filter reciprocal cpus="
301 << Dec << static_cast<size_t>(context.processorA) << "/"
302 << static_cast<size_t>(context.processorB)
303 << " incomplete=" << static_cast<size_t>(context.deferredA) << "/"
304 << static_cast<size_t>(context.deferredB));
305#endif
306
307 const bool passed =
308 check(context.callbackA != static_cast<size_t>(-1) &&
309 context.callbackB != static_cast<size_t>(-1) && firstJoined && secondJoined &&
310 !context.failures && context.enteredA == static_cast<size_t>(1) &&
311 context.enteredB == static_cast<size_t>(1) &&
312 context.returnedA == static_cast<size_t>(1) &&
313 context.returnedB == static_cast<size_t>(1) &&
314 (context.deferredA == static_cast<size_t>(1) ||
315 context.deferredB == static_cast<size_t>(1)) &&
316 retiredA && retiredB
317#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
318 && context.processorA != context.processorB
319#endif
320 ,
321 "network-filter-reciprocal-removal",
322 "reciprocal in-callback removals did not return on separate test CPUs") &&
323 check(callsA == 2 && callsB == 1 && context.callsA == callsA && context.callsB == callsB,
324 "network-filter-reciprocal-removal",
325 "a reciprocally removed callback remained published");
326 if (passed) {
327#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
328 NOTICE("QEMU-CONCURRENCY-TEST: PASS network-filter-reciprocal-removal-smp");
329#else
330 NOTICE("HOSTED-WAIT-TEST: PASS network-filter-reciprocal-removal");
331#endif
332 }
333 return passed;
334}
335} // namespace
336
337bool runNetworkFilterConcurrencyRegressions() {
338 return filterCallbackLifetimeBarrier() && reciprocalCallbackRemoval();
339}
size_t installCallback(size_t level, bool(*callback)(uintptr_t, size_t))
Definition Filter.cc:141
static NetworkFilter & instance()
Definition Filter.h:43
bool filter(size_t level, uintptr_t packet, size_t sz)
Definition Filter.cc:60
bool removeCallback(size_t level, size_t id)
Definition Filter.cc:165
static ProcessorId id()
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
bool joinForCompletion()
Definition Thread.cc:2771
@ Dec
Definition Log.h:144