Skip to content

Commit d66d561

Browse files
authored
More grate concurrency tests (#999)
* threaded grate test * threaded grate test * update name * skip list
1 parent 1bed551 commit d66d561

File tree

3 files changed

+184
-0
lines changed

3 files changed

+184
-0
lines changed

skip_test_cases.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ signal_tests/deterministic/signal_int_thread.c
33
signal_tests/deterministic/signal_longjmp.c
44
signal_tests/deterministic/signal_nodefer.c
55
ci/deterministic/ci_intentional_failure_tmp.c
6+
concurrent-request/thread_race_grate.c
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* thread_race.c — Cage-side test for concurrent grate Store access (#961).
2+
*
3+
* Spawns NUM_THREADS threads that each call geteuid() (interposed by the
4+
* grate) CALLS_PER_THREAD times. This forces concurrent
5+
* grate_callback_trampoline invocations from different host threads into
6+
* the same Wasmtime Store, reproducing the Store concurrency bug.
7+
*
8+
* The grate handler does heap allocations, shared state mutation, and
9+
* pointer chasing to exercise the same memory patterns as a real grate
10+
* (e.g. fdtables/DashMap).
11+
*
12+
* Pair with: thread_race_grate.c
13+
* Run: lind-wasm thread_race_grate.cwasm thread_race.cwasm
14+
*/
15+
#include <stdio.h>
16+
#include <stdlib.h>
17+
#include <unistd.h>
18+
#include <pthread.h>
19+
#include <assert.h>
20+
21+
#define NUM_THREADS 20
22+
#define CALLS_PER_THREAD 100000
23+
24+
static void *thread_fn(void *arg) {
25+
int tid = (int)(long)arg;
26+
for (int i = 0; i < CALLS_PER_THREAD; i++) {
27+
int ret = geteuid();
28+
if (ret != 10) {
29+
fprintf(stderr, "[thread %d] FAIL: iteration %d, expected 10, got %d\n",
30+
tid, i, ret);
31+
assert(0);
32+
}
33+
}
34+
return NULL;
35+
}
36+
37+
int main(void) {
38+
pthread_t threads[NUM_THREADS];
39+
40+
for (int i = 0; i < NUM_THREADS; i++) {
41+
int ret = pthread_create(&threads[i], NULL, thread_fn, (void *)(long)i);
42+
if (ret != 0) {
43+
fprintf(stderr, "pthread_create failed: %d\n", ret);
44+
exit(1);
45+
}
46+
}
47+
48+
for (int i = 0; i < NUM_THREADS; i++)
49+
pthread_join(threads[i], NULL);
50+
51+
printf("[thread_race] PASS: %d threads x %d calls returned 10\n",
52+
NUM_THREADS, CALLS_PER_THREAD);
53+
return 0;
54+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/* thread_race_grate.c — Grate for concurrent Store access test (#961).
2+
*
3+
* Interposes on geteuid (syscall 107) and does heap-heavy work in the
4+
* handler: lazy-init shared structures, realloc under contention,
5+
* ring-buffer malloc/free churn. This exercises the same patterns as a
6+
* real Rust grate (fdtables/DashMap) under concurrent access.
7+
*
8+
* Without the fix for #961 (separate Store per pool entry), this test
9+
* should crash with index-out-of-bounds in func.rs, memory faults, or
10+
* other Store corruption symptoms.
11+
*/
12+
#include <errno.h>
13+
#include <lind_syscall.h>
14+
#include <stdio.h>
15+
#include <stdlib.h>
16+
#include <sys/types.h>
17+
#include <sys/wait.h>
18+
#include <unistd.h>
19+
#include <assert.h>
20+
21+
int pass_fptr_to_wt(uint64_t fn_ptr_uint, uint64_t cageid, uint64_t arg1,
22+
uint64_t arg1cage, uint64_t arg2, uint64_t arg2cage,
23+
uint64_t arg3, uint64_t arg3cage, uint64_t arg4,
24+
uint64_t arg4cage, uint64_t arg5, uint64_t arg5cage,
25+
uint64_t arg6, uint64_t arg6cage) {
26+
if (fn_ptr_uint == 0) {
27+
fprintf(stderr, "[thread_race] Invalid function ptr\n");
28+
assert(0);
29+
}
30+
31+
int (*fn)(uint64_t) = (int (*)(uint64_t))(uintptr_t)fn_ptr_uint;
32+
return fn(cageid);
33+
}
34+
35+
/* Persistent shared state — simulates what a real grate maintains
36+
* across calls (e.g. fdtables DashMap, logging buffers, counters). */
37+
static int *call_counts = NULL; /* per-cage call counter array */
38+
static int call_counts_cap = 0;
39+
static char **log_ring = NULL; /* circular log buffer */
40+
static int log_ring_size = 64;
41+
static int log_next = 0;
42+
43+
/* Simulate what a real grate handler does: persistent heap state,
44+
* growing data structures, lookups, and writes to shared memory.
45+
* This exercises dlmalloc contention, shared global mutation, and
46+
* pointer chasing — the same patterns as fdtables/DashMap in Rust. */
47+
int thread_race_handler(uint64_t cageid) {
48+
int id = (int)cageid;
49+
50+
/* Lazy init of shared state — races here mirror DashMap lazy init */
51+
if (!call_counts) {
52+
call_counts_cap = 64;
53+
call_counts = calloc(call_counts_cap, sizeof(int));
54+
if (!call_counts) return -1;
55+
}
56+
57+
if (!log_ring) {
58+
log_ring = calloc(log_ring_size, sizeof(char *));
59+
if (!log_ring) return -1;
60+
}
61+
62+
/* Grow the counter array if needed — realloc under contention */
63+
if (id >= call_counts_cap) {
64+
int new_cap = call_counts_cap * 2;
65+
while (id >= new_cap) new_cap *= 2;
66+
int *new_counts = realloc(call_counts, new_cap * sizeof(int));
67+
if (!new_counts) return -1;
68+
for (int i = call_counts_cap; i < new_cap; i++)
69+
new_counts[i] = 0;
70+
call_counts = new_counts;
71+
call_counts_cap = new_cap;
72+
}
73+
74+
/* Increment per-cage counter — shared write */
75+
call_counts[id]++;
76+
77+
/* Allocate a log entry, write to it, store in ring buffer, free old */
78+
int slot = log_next % log_ring_size;
79+
log_next++;
80+
81+
char *entry = malloc(128);
82+
if (!entry) return -1;
83+
snprintf(entry, 128, "cage=%d call=%d", id, call_counts[id]);
84+
85+
char *old = log_ring[slot];
86+
log_ring[slot] = entry;
87+
free(old); /* free previous entry in this slot */
88+
89+
return 10;
90+
}
91+
92+
int main(int argc, char *argv[]) {
93+
if (argc < 2) {
94+
fprintf(stderr, "Usage: %s <cage_binary> [args...]\n", argv[0]);
95+
assert(0);
96+
}
97+
98+
int grateid = getpid();
99+
100+
pid_t pid = fork();
101+
if (pid < 0) {
102+
perror("fork failed");
103+
assert(0);
104+
} else if (pid == 0) {
105+
int cageid = getpid();
106+
uint64_t fn_ptr_addr = (uint64_t)(uintptr_t)&thread_race_handler;
107+
printf("[thread_race] Registering handler for cage %d "
108+
"in grate %d with fn ptr addr: %llu\n",
109+
cageid, grateid, fn_ptr_addr);
110+
register_handler(cageid, 107, grateid, fn_ptr_addr);
111+
112+
if (execv(argv[1], &argv[1]) == -1) {
113+
perror("execv failed");
114+
assert(0);
115+
}
116+
}
117+
118+
int status;
119+
while (wait(&status) > 0) {
120+
if (status != 0) {
121+
fprintf(stderr, "[thread_race] FAIL: child exited with status %d\n",
122+
status);
123+
assert(0);
124+
}
125+
}
126+
127+
printf("[thread_race] PASS\n");
128+
return 0;
129+
}

0 commit comments

Comments
 (0)