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"
14#include "modules/system/network-stack/Filter.h"
17constexpr size_t Attempts = 10000;
18constexpr bool PinTestThreads = HOSTED;
20struct FilterLifetimeContext {
21 FilterLifetimeContext()
23 callbackId(static_cast<size_t>(-1)),
26 callbackAfterRemoval(0),
41FilterLifetimeContext* g_FilterLifetimeContext =
nullptr;
43bool lifetimeFilter(uintptr_t,
size_t) {
44 FilterLifetimeContext* context = g_FilterLifetimeContext;
45 context->callbackCalls += 1;
46 if (context->removalReturned) {
47 context->callbackAfterRemoval += 1;
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)) {
59 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
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;
71 if (!context->observedDrain) {
72 context->failures += 1;
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);
83 if (context->phase !=
static_cast<size_t>(1)) {
84 context->failures += 1;
89 context->failures += 1;
91 context->removalReturned += 1;
95struct SelfRemovalContext {
96 SelfRemovalContext() : callbackId(static_cast<size_t>(-1)), calls(0), deferred(0) {}
103SelfRemovalContext* g_SelfRemovalContext =
nullptr;
105bool selfRemovingFilter(uintptr_t,
size_t) {
106 SelfRemovalContext* context = g_SelfRemovalContext;
109 context->deferred += 1;
114bool check(
bool condition,
const char* test,
const char* detail) {
118#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
119 ERROR(
"QEMU-CONCURRENCY-TEST: FAIL " << test <<
": " << detail);
121 ERROR(
"HOSTED-WAIT-TEST: FAIL " << test <<
": " << detail);
126bool filterCallbackLifetimeBarrier() {
128 FilterLifetimeContext context;
129 g_FilterLifetimeContext = &context;
132 &context,
nullptr,
false, PinTestThreads);
133 context.remover->setName(
"hosted network-filter remover");
135 NetworkFilter::setCallbackPinHook(filterPinHook);
137 const bool joined = context.remover->join();
138 NetworkFilter::setCallbackPinHook(
nullptr);
140 const size_t callsAfterRemoval = context.callbackCalls;
142 g_FilterLifetimeContext =
nullptr;
144 SelfRemovalContext selfRemoval;
145 g_SelfRemovalContext = &selfRemoval;
146 selfRemoval.callbackId = filter.
installCallback(1, selfRemovingFilter);
148 const size_t callsAfterSelfRemoval = selfRemoval.calls;
150 g_SelfRemovalContext =
nullptr;
151 const bool selfRemovalRetired = filter.
removeCallback(1, selfRemoval.callbackId);
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");
172#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
173 NOTICE(
"QEMU-CONCURRENCY-TEST: PASS network-filter-callback-lifetime");
175 NOTICE(
"HOSTED-WAIT-TEST: PASS network-filter-callback-lifetime");
181constexpr uintptr_t ReciprocalPacketA = 1;
182constexpr uintptr_t ReciprocalPacketB = 2;
184struct ReciprocalRemovalContext {
185 ReciprocalRemovalContext()
186 : callbackA(static_cast<size_t>(-1)),
187 callbackB(static_cast<size_t>(-1)),
194 processorA(static_cast<size_t>(-1)),
195 processorB(static_cast<size_t>(-1)),
215ReciprocalRemovalContext* g_ReciprocalRemovalContext =
nullptr;
217bool reciprocalFilterA(uintptr_t packet,
size_t) {
218 ReciprocalRemovalContext* context = g_ReciprocalRemovalContext;
219 context->callsA += 1;
220 if (packet != ReciprocalPacketA) {
225 context->enteredA = 1;
226 for (
size_t attempt = 0; attempt < Attempts && !context->enteredB; ++attempt) {
229 if (!context->enteredB) {
230 context->failures += 1;
235 context->deferredA = 1;
237 context->returnedA = 1;
241bool reciprocalFilterB(uintptr_t packet,
size_t) {
242 ReciprocalRemovalContext* context = g_ReciprocalRemovalContext;
244 context->callsB += 1;
245 if (packet != ReciprocalPacketB) {
249 context->enteredB = 1;
250 for (
size_t attempt = 0; attempt < Attempts && !context->enteredA; ++attempt) {
253 if (!context->enteredA) {
254 context->failures += 1;
259 context->deferredB = 1;
261 context->returnedB = 1;
265int runReciprocalFilterA(
void*) {
270int runReciprocalFilterB(
void*) {
275bool reciprocalCallbackRemoval() {
277 ReciprocalRemovalContext context;
278 g_ReciprocalRemovalContext = &context;
284 new Thread(process, runReciprocalFilterA,
nullptr,
nullptr,
false, PinTestThreads);
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");
292 const size_t callsA = context.callsA;
293 const size_t callsB = context.callsB;
295 g_ReciprocalRemovalContext =
nullptr;
296 const bool retiredA = filter.
removeCallback(1, context.callbackA);
297 const bool retiredB = filter.
removeCallback(1, context.callbackB);
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));
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)) &&
317#
if PEDIGREE_CONCURRENCY_SMOKE_TESTS
318 && context.processorA != context.processorB
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");
327#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
328 NOTICE(
"QEMU-CONCURRENCY-TEST: PASS network-filter-reciprocal-removal-smp");
330 NOTICE(
"HOSTED-WAIT-TEST: PASS network-filter-reciprocal-removal");
337bool runNetworkFilterConcurrencyRegressions() {
338 return filterCallbackLifetimeBarrier() && reciprocalCallbackRemoval();
size_t installCallback(size_t level, bool(*callback)(uintptr_t, size_t))
static NetworkFilter & instance()
bool filter(size_t level, uintptr_t packet, size_t sz)
bool removeCallback(size_t level, size_t id)
static Scheduler & instance()