1#include "thread/tests/test_internal.h"
2
3TEST_GROUP_DEFINE(apc, .intensity_desc = {
4 .curve = SCALE_PIECEWISE_LOG,
5 .unit = "iters",
6 });
7
8static atomic_bool apc_ran = false;
9static atomic_uint apc_destroyed = 0;
10
11static void the_apc(struct apc_context *ac, void *arg) {
12 cc_ignore(ac);
13 cc_ignore(arg);
14 atomic_store(&apc_ran, true);
15}
16
17static void the_apc_destroy(struct apc *apc) {
18 atomic_inc(&apc_destroyed);
19 vfree(apc);
20}
21
22static void apc_thread(void *arg) {
23 cc_ignore(arg);
24 while (!atomic_load(&apc_ran))
25 cpu_pause();
26}
27
28static struct thread *ted = NULL;
29TEST_DEFINE_INTEGRATION(apc, kernel_apc_runs_then_destroys) {
30 atomic_store(&apc_ran, false);
31 atomic_store(&apc_destroyed, 0);
32 ted = thread_spawn("apc_test_thread", apc_thread, .joinable = true);
33 struct apc *a = vnew(a, ALLOC_ZERO);
34 if (!a || !ted) {
35 if (a)
36 vfree(a);
37 if (ted)
38 thread_detach(t: ted);
39
40 return TEST_FAIL("allocation failed");
41 }
42
43 apc_init(a, fn: the_apc, NULL, destroy: the_apc_destroy);
44
45 TEST_ASSERT(thread_getref(ted));
46 TEST_ASSERT(apc_enqueue(ted, a, APC_TYPE_KERNEL));
47 thread_putref(t: ted);
48 apc_putref(a);
49
50 /* the thread only returns once it has seen the APC run */
51 thread_join(t: ted);
52 TEST_ASSERT(atomic_load(&apc_ran));
53 TEST_ASSERT_EQ(atomic_load(&apc_destroyed), 1);
54
55 return TEST_SUCCESS;
56}
57
58static atomic_uint apc_ref_destroyed = 0;
59
60static void apc_ref_destroy(struct apc *apc) {
61 cc_ignore(apc);
62 atomic_inc(&apc_ref_destroyed);
63}
64
65TEST_DEFINE_INTEGRATION(apc, refcount_finalizes_at_zero) {
66 struct apc apc;
67 atomic_store(&apc_ref_destroyed, 0);
68 apc_init(a: &apc, fn: the_apc, NULL, destroy: apc_ref_destroy);
69
70 TEST_ASSERT(apc_getref(&apc));
71 apc_putref(a: &apc);
72 TEST_ASSERT_EQ(atomic_load(&apc_ref_destroyed), 0);
73 apc_putref(a: &apc);
74 TEST_ASSERT_EQ(atomic_load(&apc_ref_destroyed), 1);
75 return TEST_SUCCESS;
76}
77
78TEST_DEFINE_INTEGRATION(apc, null_destroy_valid) {
79 struct apc apc;
80 apc_init(a: &apc, fn: the_apc, NULL, NULL);
81 apc_putref(a: &apc);
82 return TEST_SUCCESS;
83}
84
85static atomic_bool apc_cancel_ready = false;
86static atomic_bool apc_cancel_release = false;
87static atomic_bool apc_cancel_ran = false;
88static atomic_uint apc_cancel_destroyed = 0;
89
90static void cancelled_apc(struct apc_context *ac, void *arg) {
91 cc_ignore(ac);
92 cc_ignore(arg);
93 atomic_store(&apc_cancel_ran, true);
94}
95
96static void cancelled_apc_destroy(struct apc *apc) {
97 atomic_inc(&apc_cancel_destroyed);
98 vfree(apc);
99}
100
101static void apc_cancel_target(void *arg) {
102 cc_ignore(arg);
103 apc_disable_kernel();
104 atomic_store(&apc_cancel_ready, true);
105 while (!atomic_load(&apc_cancel_release))
106 scheduler_yield();
107}
108
109TEST_DEFINE_INTEGRATION(apc, cancel_releases_queue_ref) {
110 atomic_store(&apc_cancel_ready, false);
111 atomic_store(&apc_cancel_release, false);
112 atomic_store(&apc_cancel_ran, false);
113 atomic_store(&apc_cancel_destroyed, 0);
114
115 struct thread *target =
116 thread_spawn("apc_cancel_target", apc_cancel_target, .joinable = true);
117 TEST_ASSERT_NONNULL(target);
118 while (!atomic_load(&apc_cancel_ready))
119 scheduler_yield();
120
121 struct apc *apc = apc_create();
122 TEST_ASSERT_NONNULL(apc);
123 apc_init(a: apc, fn: cancelled_apc, NULL, destroy: cancelled_apc_destroy);
124
125 TEST_ASSERT(thread_getref(target));
126 TEST_ASSERT(apc_enqueue(target, apc, APC_TYPE_KERNEL));
127 TEST_ASSERT(!apc_enqueue(target, apc, APC_TYPE_KERNEL));
128 TEST_ASSERT(apc_cancel(target, apc));
129 thread_putref(t: target);
130
131 apc_putref(a: apc);
132 TEST_ASSERT_EQ(atomic_load(&apc_cancel_destroyed), 1);
133 TEST_ASSERT(!atomic_load(&apc_cancel_ran));
134
135 atomic_store(&apc_cancel_release, true);
136 thread_join(t: target);
137 return TEST_SUCCESS;
138}
139
140static atomic_bool apc_rundown_ready = false;
141static atomic_bool apc_rundown_release = false;
142static atomic_bool apc_rundown_ran = false;
143static atomic_uint apc_rundown_destroyed = 0;
144
145static void rundown_apc(struct apc_context *ac, void *arg) {
146 cc_ignore(ac);
147 cc_ignore(arg);
148 atomic_store(&apc_rundown_ran, true);
149}
150
151static void rundown_apc_destroy(struct apc *apc) {
152 atomic_inc(&apc_rundown_destroyed);
153 vfree(apc);
154}
155
156static void apc_rundown_target(void *arg) {
157 cc_ignore(arg);
158 apc_disable_kernel();
159 atomic_store(&apc_rundown_ready, true);
160 while (!atomic_load(&apc_rundown_release))
161 scheduler_yield();
162}
163
164TEST_DEFINE_INTEGRATION(apc, thread_rundown_releases_queue_ref) {
165 atomic_store(&apc_rundown_ready, false);
166 atomic_store(&apc_rundown_release, false);
167 atomic_store(&apc_rundown_ran, false);
168 atomic_store(&apc_rundown_destroyed, 0);
169
170 struct thread *target = thread_spawn("apc_rundown_target",
171 apc_rundown_target, .joinable = true);
172 TEST_ASSERT_NONNULL(target);
173 while (!atomic_load(&apc_rundown_ready))
174 scheduler_yield();
175
176 struct apc *apc = apc_create();
177 TEST_ASSERT_NONNULL(apc);
178 apc_init(a: apc, fn: rundown_apc, NULL, destroy: rundown_apc_destroy);
179
180 TEST_ASSERT(thread_getref(target));
181 TEST_ASSERT(apc_enqueue(target, apc, APC_TYPE_KERNEL));
182 thread_putref(t: target);
183 apc_putref(a: apc);
184
185 atomic_store(&apc_rundown_release, true);
186 thread_join(t: target);
187
188 TEST_ASSERT(!atomic_load(&apc_rundown_ran));
189 TEST_ASSERT_EQ(atomic_load(&apc_rundown_destroyed), 1);
190 return TEST_SUCCESS;
191}
192
193static atomic_uint apc_reuse_ran = 0;
194static atomic_uint apc_reuse_destroyed = 0;
195
196static void reused_apc(struct apc_context *ac, void *arg) {
197 cc_ignore(ac);
198 cc_ignore(arg);
199 atomic_inc(&apc_reuse_ran);
200}
201
202static void reused_apc_destroy(struct apc *apc) {
203 cc_ignore(apc);
204 atomic_inc(&apc_reuse_destroyed);
205}
206
207static void apc_reuse_target(void *arg) {
208 cc_ignore(arg);
209 while (atomic_load(&apc_reuse_ran) < 2)
210 scheduler_yield();
211}
212
213TEST_DEFINE_INTEGRATION(apc, caller_ref_allows_reuse) {
214 atomic_store(&apc_reuse_ran, 0);
215 atomic_store(&apc_reuse_destroyed, 0);
216
217 struct thread *target =
218 thread_spawn("apc_reuse_target", apc_reuse_target, .joinable = true);
219 TEST_ASSERT_NONNULL(target);
220
221 struct apc apc;
222 apc_init(a: &apc, fn: reused_apc, NULL, destroy: reused_apc_destroy);
223 for (size_t expected = 1; expected <= 2; expected++) {
224 TEST_ASSERT(thread_getref(target));
225 TEST_ASSERT(apc_enqueue(target, &apc, APC_TYPE_KERNEL));
226 thread_putref(t: target);
227 while (atomic_load(&apc_reuse_ran) < expected)
228 scheduler_yield();
229 while (atomic_load_acq(&apc.state) != APC_STATE_IDLE)
230 scheduler_yield();
231 }
232
233 thread_join(t: target);
234 TEST_ASSERT_EQ(atomic_load(&apc_reuse_ran), 2);
235 TEST_ASSERT_EQ(atomic_load(&apc_reuse_destroyed), 0);
236 apc_putref(a: &apc);
237 TEST_ASSERT_EQ(atomic_load(&apc_reuse_destroyed), 1);
238 return TEST_SUCCESS;
239}
240
241static atomic_bool apc_race_ready = false;
242static atomic_bool apc_race_release = false;
243static atomic_bool apc_race_settled = false;
244static atomic_uint apc_race_ran = 0;
245static atomic_uint apc_race_destroyed = 0;
246
247static void raced_apc(struct apc_context *ac, void *arg) {
248 cc_ignore(ac);
249 cc_ignore(arg);
250 atomic_inc(&apc_race_ran);
251}
252
253static void raced_apc_destroy(struct apc *apc) {
254 atomic_inc(&apc_race_destroyed);
255 vfree(apc);
256}
257
258static void apc_race_target(void *arg) {
259 cc_ignore(arg);
260 apc_disable_kernel();
261 atomic_store(&apc_race_ready, true);
262 while (!atomic_load(&apc_race_release))
263 scheduler_yield();
264 apc_enable_kernel();
265 while (!atomic_load(&apc_race_settled))
266 scheduler_yield();
267}
268
269TEST_DEFINE_INTEGRATION(apc, cancel_races_delivery) {
270 atomic_store(&apc_race_ready, false);
271 atomic_store(&apc_race_release, false);
272 atomic_store(&apc_race_settled, false);
273 atomic_store(&apc_race_ran, 0);
274 atomic_store(&apc_race_destroyed, 0);
275
276 struct thread *target =
277 thread_spawn("apc_race_target", apc_race_target, .joinable = true);
278 TEST_ASSERT_NONNULL(target);
279 while (!atomic_load(&apc_race_ready))
280 scheduler_yield();
281
282 struct apc *apc = apc_create();
283 TEST_ASSERT_NONNULL(apc);
284 apc_init(a: apc, fn: raced_apc, NULL, destroy: raced_apc_destroy);
285 TEST_ASSERT(thread_getref(target));
286 TEST_ASSERT(apc_enqueue(target, apc, APC_TYPE_KERNEL));
287
288 atomic_store(&apc_race_release, true);
289 bool cancelled = apc_cancel(t: target, a: apc);
290 thread_putref(t: target);
291 atomic_store(&apc_race_settled, true);
292 apc_putref(a: apc);
293 thread_join(t: target);
294
295 TEST_ASSERT_EQ(atomic_load(&apc_race_ran), (cancelled ? 0 : 1));
296 TEST_ASSERT_EQ(atomic_load(&apc_race_destroyed), 1);
297 return TEST_SUCCESS;
298}
299