The Pedigree Project 0.1
rcu-regressions.cc
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#include "pedigree/kernel/Atomic.h"
3#include "pedigree/kernel/Log.h"
4#include "pedigree/kernel/process/Rcu.h"
5#include "pedigree/kernel/process/Scheduler.h"
6#include "pedigree/kernel/process/Thread.h"
7#include "pedigree/kernel/processor/Processor.h"
8#include "pedigree/kernel/time/Time.h"
9
10namespace {
11struct Value {
12 size_t value;
13 Atomic<size_t>* reclaimed;
14};
15
16void reclaim(void* pointer) {
17 auto* value = static_cast<Value*>(pointer);
18 *value->reclaimed += 1;
19 delete value;
20}
21
22struct Context {
23 RcuPointer<Value> publication;
24 Atomic<size_t> reclaimed{0}, ready{0}, release{0}, failed{0}, draining{0}, drained{0};
25 RcuRetireQueue retired;
26 Atomic<size_t> readerCpu{~size_t{0}};
27 size_t writerCpu = Processor::index();
28};
29
30int readRemotely(void* parameter) {
31 auto& context = *static_cast<Context*>(parameter);
32 const size_t cpu = Processor::index();
33 if (cpu == context.writerCpu || !context.readerCpu.compareAndSwap(~size_t{0}, cpu)) {
34 return 0;
35 }
36 {
37 RcuReadGuard outer;
38 const Value* value = context.publication.load(outer);
39 {
40 RcuReadGuard inner;
41 if (context.publication.load(inner) != value || Processor::getInterrupts()) {
42 context.failed = 1;
43 }
44 }
45 context.ready = 1;
46 const auto deadline = Time::getTicks() + 5 * Time::Multiplier::Second;
47 while (!context.release.value() && Time::getTicks() < deadline) {
49 }
50 if (!context.release.value() || !value || value->value != 17 || context.reclaimed.value()) {
51 context.failed = 1;
52 }
53 }
55 context.failed = 1;
56 }
57 return 0;
58}
59
60int drainRemotely(void* parameter) {
61 auto& context = *static_cast<Context*>(parameter);
62 context.draining = 1;
63 context.retired.drain();
64 context.drained = 1;
65 return 0;
66}
67
68bool waitFor(Atomic<size_t>& flag) {
69 const auto deadline = Time::getTicks() + 5 * Time::Multiplier::Second;
70 while (!flag.value() && Time::getTicks() < deadline) {
72 }
73 return flag.value() != 0;
74}
75} // namespace
76
77bool runRcuConcurrencyRegression() {
78 NOTICE("QEMU-CONCURRENCY-TEST: BEGIN rcu-publication-reclamation-smp");
79 const size_t cpus = Processor::getCount();
80 if (cpus < 4 || cpus > 64) {
81 return false;
82 }
83 Context context;
84 context.publication.exchange(new Value{17, &context.reclaimed});
85 Thread* readers[64] = {};
86 for (size_t i = 0; i < cpus; ++i) {
87 readers[i] = new Thread(Scheduler::instance().getKernelProcess(), readRemotely, &context,
88 nullptr, false, false, true);
89 if (!readers[i]->start()) {
90 FATAL("RCU smoke reader could not start");
91 }
92 }
93 if (!waitFor(context.ready)) {
94 FATAL("RCU smoke reader did not enter on another CPU");
95 }
96 Value* previous = context.publication.exchange(new Value{29, &context.reclaimed});
97 context.retired.retire(previous, reclaim);
98 ThreadPlacement placement;
99 placement.allowed.set(context.writerCpu);
100 Thread* drainer = new Thread(Scheduler::instance().getKernelProcess(), drainRemotely, &context,
101 nullptr, false, false, true, &placement);
102 if (!drainer->start() || !waitFor(context.draining)) {
103 FATAL("RCU smoke reclaimer could not start");
104 }
105 for (size_t i = 0; i < 64; ++i) {
107 }
108 if (context.drained.value() || context.reclaimed.value()) {
109 context.failed = 1;
110 }
111 context.release = 1;
112 bool joined = drainer->joinForCompletion();
113 for (size_t i = 0; i < cpus; ++i) {
114 joined = readers[i]->joinForCompletion() && joined;
115 }
116 if (!joined || context.failed.value() || context.reclaimed.value() != 1) {
117 return false;
118 }
119
120 previous = context.publication.exchange(nullptr);
121 context.retired.retire(previous, reclaim);
122 for (size_t i = 1; i <= RcuRetireQueue::Capacity; ++i) {
123 context.retired.retire(new Value{i, &context.reclaimed}, reclaim);
124 }
125 if (context.reclaimed.value() != 1 + RcuRetireQueue::Capacity) {
126 return false;
127 }
128 context.retired.drain();
129 if (context.reclaimed.value() != 2 + RcuRetireQueue::Capacity) {
130 return false;
131 }
132 NOTICE("QEMU-CONCURRENCY-TEST: RCU reader CPU=" << Dec << context.readerCpu.value()
133 << ", writer CPU=" << context.writerCpu);
134 NOTICE("QEMU-CONCURRENCY-TEST: PASS rcu-publication-reclamation-smp");
135 return true;
136}
static bool getInterrupts()
static size_t getCount()
static void pause()
static size_t index()
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:236
bool joinForCompletion()
Definition Thread.cc:2750
bool start()
Definition Thread.cc:751
@ Dec
Definition Log.h:126