1#include "mem/slab/tests/test_internal.h"
2#include <mem/nofail.h>
3
4#define MT_ALLOC_TIMES 1024
5
6static atomic_int valloc_done = 0;
7
8static void mt_valloc_worker(void *arg) {
9 cc_ignore(arg);
10 void **ptrs = vnews(ptrs, MT_ALLOC_TIMES, ALLOC_ZERO);
11 TEST_ASSERT_VOID_NONNULL(ptrs);
12
13 for (uint64_t i = 0; i < MT_ALLOC_TIMES; i++) {
14 ptrs[i] = valloc(64);
15 TEST_ASSERT_VOID_NONNULL(ptrs[i]);
16 }
17
18 for (uint64_t i = 0; i < MT_ALLOC_TIMES; i++) {
19 uint64_t idx = prng_next() % MT_ALLOC_TIMES;
20
21 if (ptrs[idx]) {
22 vfree(ptrs[idx]);
23 ptrs[idx] = NULL;
24 }
25 }
26
27 for (uint64_t i = 0; i < MT_ALLOC_TIMES; i++) {
28 if (ptrs[i]) {
29 vfree(ptrs[i]);
30 }
31 }
32
33 vfree(ptrs);
34 atomic_inc(&valloc_done);
35}
36
37TEST_DEFINE_INTEGRATION(slab, mt_alloc_free,
38 TEST_INTENSITY_CORES(1, 2, 4, "threads/core"),
39 .min_ram_mib = 8) {
40 size_t nthreads = ctx->intensity_val ? ctx->intensity_val : 8;
41 struct thread **threads = vnews(threads, nthreads);
42 TEST_ASSERT_NONNULL(threads);
43 atomic_store(&valloc_done, 0);
44
45 for (size_t i = 0; i < nthreads; i++) {
46 threads[i] = thread_spawn("mt_valloc_thread", mt_valloc_worker,
47 .stack_pages = 16, .joinable = true);
48 TEST_ASSERT_NONNULL(threads[i]);
49 }
50
51 for (size_t i = 0; i < nthreads; i++)
52 thread_join(t: threads[i]);
53
54 TEST_ASSERT_EQ(atomic_load(&valloc_done), (int) nthreads);
55 vfree(threads);
56 return TEST_SUCCESS;
57}
58
59#define STRESS_THREADS 7
60#define STRESS_ITERS 50000
61#define MAX_LIVE_ALLOCS 1024
62#define SHOULD_FREE true
63
64static atomic_bool all_ready = false;
65
66struct stress_arg {
67 int id;
68 volatile int *done_flag;
69 size_t iters;
70};
71
72static void stress_worker(void *arg) {
73 cc_ignore(arg);
74 struct stress_arg *a = NULL;
75 /* wait until private field is visible */
76 while (!(a = thread_get_current()->private))
77 ;
78
79 while (!atomic_load_acq(&all_ready))
80 ;
81
82 /* allocate small tracking table dynamically */
83 void **live_ptrs = nofail_vnews(live_ptrs, MAX_LIVE_ALLOCS);
84 memset(live_ptrs, 0, sizeof(void *) * MAX_LIVE_ALLOCS);
85
86 for (size_t iter = 0; iter < a->iters; ++iter) {
87 /* 1 in 8 chance to free something early (chaotic order) */
88 if ((prng_next() & 7) == 0) {
89 int idx = prng_next() % MAX_LIVE_ALLOCS;
90 if (live_ptrs[idx]) {
91 vfree_new(ptr: live_ptrs[idx], behavior: ALLOC_BEHAVIOR_NORMAL);
92 live_ptrs[idx] = NULL;
93 }
94 }
95
96 /* Allocate with randomized size and flags */
97 size_t sz = 8 + (prng_next() % 512); /* small to moderate allocations */
98 enum alloc_flags flags = ALLOC_FLAGS_DEFAULT;
99
100 if (prng_next() & 1) {
101 flags |= ALLOC_FLAG_PREFER_CACHE_ALIGNED;
102 flags &= ~ALLOC_FLAG_NO_CACHE_ALIGN;
103 }
104 if (prng_next() & 2) {
105 flags |= ALLOC_FLAG_NONMOVABLE;
106 flags &= ~ALLOC_FLAG_MOVABLE;
107 } else {
108 flags |= ALLOC_FLAG_MOVABLE;
109 flags &= ~ALLOC_FLAG_NONMOVABLE;
110 }
111
112 enum alloc_behavior behavior = ALLOC_BEHAVIOR_NORMAL;
113
114 void *p = valloc_new(
115 size: sz, params: (struct alloc_params){.flags = flags, .behavior = behavior});
116 if (!p)
117 continue;
118
119 /* write simple pattern to verify memory */
120 ((uint8_t *) p)[0] = (uint8_t) (a->id + iter);
121 ((uint8_t *) p)[sz - 1] = (uint8_t) (a->id ^ iter);
122
123 /* randomly decide where to place it */
124 int idx = prng_next() % MAX_LIVE_ALLOCS;
125
126 if (live_ptrs[idx] && SHOULD_FREE)
127 vfree_new(ptr: live_ptrs[idx], behavior: ALLOC_BEHAVIOR_NORMAL);
128 live_ptrs[idx] = p;
129 }
130
131 /* Final cleanup */
132 for (int i = 0; i < MAX_LIVE_ALLOCS; ++i) {
133 if (live_ptrs[i])
134 vfree_new(ptr: live_ptrs[i], behavior: ALLOC_BEHAVIOR_NORMAL);
135 }
136
137 vfree(live_ptrs);
138 *a->done_flag = 1;
139}
140
141static volatile int done[STRESS_THREADS];
142static struct stress_arg args[STRESS_THREADS];
143static char msg[128];
144
145TEST_DEFINE_INTEGRATION(slab, mt_alloc_free_stress,
146 TEST_INTENSITY(5000, 50000, 200000)) {
147 memset((void *) done, 0, sizeof(done));
148 atomic_store_rlx(&all_ready, false);
149
150 struct thread *workers[STRESS_THREADS];
151 size_t iters = ctx->intensity_val ? ctx->intensity_val : 50000;
152
153 enum irql irql = irql_raise(new_level: IRQL_DISPATCH_LEVEL);
154 for (int i = 0; i < STRESS_THREADS; ++i) {
155 args[i].id = i;
156 args[i].done_flag = &done[i];
157 args[i].iters = iters;
158 workers[i] = thread_spawn("valloc_new_stress_worker", stress_worker,
159 .private = &args[i], .joinable = true);
160 }
161 irql_lower(old_level: irql);
162
163 atomic_store_release(&all_ready, true);
164
165 const time_ms_t timeout_ms = 90 * 1000;
166 time_ms_t start = time_get_ms();
167
168 for (int i = 0; i < STRESS_THREADS; ++i) {
169 time_ms_t elapsed = time_get_ms() - start;
170 time_ms_t left = elapsed >= timeout_ms ? 1 : timeout_ms - elapsed;
171
172 if (!thread_join_timeout(t: workers[i], timeout_ms: left, NULL)) {
173 snprintf(msg, sizeof(msg), "thread %d did not complete in time", i);
174 test_info("%s", msg);
175
176 /* still running, and we are done waiting on it */
177 for (int j = i; j < STRESS_THREADS; ++j)
178 thread_detach(t: workers[j]);
179
180 return TEST_FAIL(msg);
181 }
182
183 if (!done[i]) {
184 snprintf(msg, sizeof(msg), "thread %d exited without finishing", i);
185 test_info("%s", msg);
186
187 for (int j = i + 1; j < STRESS_THREADS; ++j)
188 thread_detach(t: workers[j]);
189
190 return TEST_FAIL(msg);
191 }
192 }
193
194 test_info("aggressive concurrency stress test completed");
195 return TEST_SUCCESS;
196}
197