1#include "mem/slab/tests/test_internal.h"
2
3#define MT_ALLOC_TIMES 1024
4
5static atomic_int kmalloc_done = 0;
6
7static void mt_kmalloc_worker(void *) {
8 void *ptrs[MT_ALLOC_TIMES] = {0};
9
10 for (uint64_t i = 0; i < MT_ALLOC_TIMES; i++) {
11 ptrs[i] = kmalloc(64);
12 TEST_ASSERT_VOID_NONNULL(ptrs[i]);
13 }
14
15 for (uint64_t i = 0; i < MT_ALLOC_TIMES; i++) {
16 uint64_t idx = prng_next() % MT_ALLOC_TIMES;
17
18 if (ptrs[idx]) {
19 kfree(ptrs[idx]);
20 ptrs[idx] = NULL;
21 }
22 }
23
24 for (uint64_t i = 0; i < MT_ALLOC_TIMES; i++) {
25 if (ptrs[i]) {
26 kfree(ptrs[i]);
27 }
28 }
29
30 atomic_fetch_add(&kmalloc_done, 1);
31}
32
33TEST_DECLARE_INTEGRATION(slab, multithreaded_alloc_free,
34 TEST_INTENSITY_CORES(1, 2, 4, "threads/core")) {
35 ABORT_IF_RAM_LOW();
36
37 size_t nthreads = ctx->intensity_val ? ctx->intensity_val : 8;
38 struct thread **threads = kmalloc(sizeof(struct thread *) * nthreads);
39 TEST_ASSERT_NONNULL(threads);
40 atomic_store(&kmalloc_done, 0);
41
42 for (size_t i = 0; i < nthreads; i++) {
43 threads[i] = thread_spawn_joinable_custom_stack(
44 name: "mt_kmalloc_thread", entry: mt_kmalloc_worker, NULL, PAGE_SIZE * 16);
45 TEST_ASSERT_NONNULL(threads[i]);
46 }
47
48 for (size_t i = 0; i < nthreads; i++)
49 thread_join(t: threads[i]);
50
51 TEST_ASSERT_EQ(atomic_load(&kmalloc_done), (int) nthreads);
52 kfree(threads);
53 return TEST_SUCCESS;
54}
55
56#define STRESS_THREADS 7
57#define STRESS_ITERS 50000
58#define MAX_LIVE_ALLOCS 1024
59#define SHOULD_FREE true
60
61static atomic_bool all_ready = false;
62
63struct stress_arg {
64 int id;
65 volatile int *done_flag;
66 size_t iters;
67};
68
69static void stress_worker(void *) {
70 struct stress_arg *a = NULL;
71 /* wait until private field is visible */
72 while (!(a = thread_get_current()->private))
73 ;
74
75 while (!all_ready)
76 ;
77
78 /* allocate small tracking table dynamically */
79 void **live_ptrs = kmalloc(sizeof(void *) * MAX_LIVE_ALLOCS);
80 memset(live_ptrs, 0, sizeof(void *) * MAX_LIVE_ALLOCS);
81
82 for (size_t iter = 0; iter < a->iters; ++iter) {
83 /* 1 in 8 chance to free something early (chaotic order) */
84 if ((prng_next() & 7) == 0) {
85 int idx = prng_next() % MAX_LIVE_ALLOCS;
86 if (live_ptrs[idx]) {
87 kfree_new(ptr: live_ptrs[idx], behavior: ALLOC_BEHAVIOR_NORMAL);
88 live_ptrs[idx] = NULL;
89 }
90 }
91
92 /* Allocate with randomized size and flags */
93 size_t sz = 8 + (prng_next() % 512); /* small to moderate allocations */
94 enum alloc_flags flags = ALLOC_FLAGS_DEFAULT;
95
96 if (prng_next() & 1) {
97 flags |= ALLOC_FLAG_PREFER_CACHE_ALIGNED;
98 flags &= ~ALLOC_FLAG_NO_CACHE_ALIGN;
99 }
100 if (prng_next() & 2) {
101 flags |= ALLOC_FLAG_NONMOVABLE;
102 flags &= ~ALLOC_FLAG_MOVABLE;
103 } else {
104 flags |= ALLOC_FLAG_MOVABLE;
105 flags &= ~ALLOC_FLAG_NONMOVABLE;
106 }
107
108 enum alloc_behavior behavior = (prng_next() & 3)
109 ? ALLOC_BEHAVIOR_NORMAL
110 : ALLOC_BEHAVIOR_NO_RECLAIM;
111
112 void *p = kmalloc_new(size: sz, flags, behavior);
113 if (!p)
114 continue;
115
116 /* write simple pattern to verify memory */
117 ((uint8_t *) p)[0] = (uint8_t) (a->id + iter);
118 ((uint8_t *) p)[sz - 1] = (uint8_t) (a->id ^ iter);
119
120 /* randomly decide where to place it */
121 int idx = prng_next() % MAX_LIVE_ALLOCS;
122
123 if (live_ptrs[idx] && SHOULD_FREE)
124 kfree_new(ptr: live_ptrs[idx], behavior: ALLOC_BEHAVIOR_NORMAL);
125 live_ptrs[idx] = p;
126 }
127
128 /* Final cleanup */
129 for (int i = 0; i < MAX_LIVE_ALLOCS; ++i) {
130 if (live_ptrs[i])
131 kfree_new(ptr: live_ptrs[i], behavior: ALLOC_BEHAVIOR_NORMAL);
132 }
133
134 kfree(live_ptrs);
135 *a->done_flag = 1;
136}
137
138static volatile int done[STRESS_THREADS];
139static struct stress_arg args[STRESS_THREADS];
140static char msg[128];
141
142TEST_DECLARE_INTEGRATION(slab, concurrency_stress,
143 TEST_INTENSITY(5000, 50000, 200000)) {
144 memset((void *) done, 0, sizeof(done));
145 all_ready = false;
146
147 struct thread *workers[STRESS_THREADS];
148 size_t iters = ctx->intensity_val ? ctx->intensity_val : 50000;
149
150 enum irql irql = irql_raise(new_level: IRQL_DISPATCH_LEVEL);
151 for (int i = 0; i < STRESS_THREADS; ++i) {
152 args[i].id = i;
153 args[i].done_flag = &done[i];
154 args[i].iters = iters;
155 workers[i] = thread_spawn_joinable(name: "kmalloc_new_stress_worker",
156 entry: stress_worker, NULL);
157
158 workers[i]->private = &args[i];
159 }
160 irql_lower(old_level: irql);
161
162 all_ready = true;
163
164 /* the whole worker set shares one deadline */
165 const time_ms_t timeout_ms = 30 * 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(buffer: msg, buffer_len: sizeof(msg), format: "thread %d did not complete in time", i);
174 test_info(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(buffer: msg, buffer_len: sizeof(msg), format: "thread %d exited without finishing", i);
185 test_info(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