|
| 1 | +// Concurrency + memory stress for BOTH ABIs of one library: |
| 2 | +// - native (pure C): EchoRequest struct in, typed EchoResponse* back |
| 3 | +// - CBOR: encoded request in, CBOR EchoResponse map back |
| 4 | +// |
| 5 | +// Many threads hammer a shared context with both call shapes, each verifying |
| 6 | +// the echoed message round-trips. Built to run under ASAN/TSAN to flush out |
| 7 | +// leaks, use-after-free in the POD deep-copy/free paths, and data races. |
| 8 | +// |
| 9 | +// make stress && ./stress # plain |
| 10 | +// make stress SAN=address # -fsanitize=address |
| 11 | +// make stress SAN=thread # -fsanitize=thread |
| 12 | +#include "my_timer.h" |
| 13 | +#include "my_timer_cbor.h" |
| 14 | +#include <pthread.h> |
| 15 | +#include <stdatomic.h> |
| 16 | +#include <stdio.h> |
| 17 | +#include <stdlib.h> |
| 18 | +#include <string.h> |
| 19 | + |
| 20 | +#ifndef STRESS_THREADS |
| 21 | +#define STRESS_THREADS 6 |
| 22 | +#endif |
| 23 | +#ifndef STRESS_ITERS |
| 24 | +#define STRESS_ITERS 1500 |
| 25 | +#endif |
| 26 | + |
| 27 | +static atomic_long g_ok; |
| 28 | +static atomic_long g_bad; |
| 29 | + |
| 30 | +// --- per-call blocking capture ---------------------------------------------- |
| 31 | +typedef struct { |
| 32 | + int ret, done; |
| 33 | + char echoed[128]; // copied out of the typed return / decoded from CBOR |
| 34 | + pthread_mutex_t mu; |
| 35 | + pthread_cond_t cv; |
| 36 | +} Cap; |
| 37 | +static void cap_init(Cap *c) { |
| 38 | + memset(c, 0, sizeof(*c)); |
| 39 | + pthread_mutex_init(&c->mu, NULL); |
| 40 | + pthread_cond_init(&c->cv, NULL); |
| 41 | +} |
| 42 | +static void cap_destroy(Cap *c) { |
| 43 | + pthread_mutex_destroy(&c->mu); |
| 44 | + pthread_cond_destroy(&c->cv); |
| 45 | +} |
| 46 | +static void cap_reset(Cap *c) { |
| 47 | + pthread_mutex_lock(&c->mu); |
| 48 | + c->done = 0; |
| 49 | + c->echoed[0] = 0; |
| 50 | + pthread_mutex_unlock(&c->mu); |
| 51 | +} |
| 52 | +static void cap_wait(Cap *c) { |
| 53 | + pthread_mutex_lock(&c->mu); |
| 54 | + while (!c->done) pthread_cond_wait(&c->cv, &c->mu); |
| 55 | + pthread_mutex_unlock(&c->mu); |
| 56 | +} |
| 57 | + |
| 58 | +// Native EchoResponse return: read the typed struct in-callback. |
| 59 | +static void native_cb(int ret, const char *msg, size_t len, void *ud) { |
| 60 | + Cap *c = ud; |
| 61 | + pthread_mutex_lock(&c->mu); |
| 62 | + c->ret = ret; |
| 63 | + if (ret == RET_OK) { |
| 64 | + const EchoResponse *r = (const EchoResponse *)msg; |
| 65 | + strncpy(c->echoed, r->echoed, sizeof(c->echoed) - 1); |
| 66 | + } |
| 67 | + c->done = 1; |
| 68 | + pthread_cond_signal(&c->cv); |
| 69 | + pthread_mutex_unlock(&c->mu); |
| 70 | + (void)len; |
| 71 | +} |
| 72 | + |
| 73 | +// --- minimal CBOR map text reader (for the CBOR EchoResponse) --------------- |
| 74 | +static size_t cbor_item_len(const uint8_t *p, size_t len) { |
| 75 | + if (len < 1) return 0; |
| 76 | + uint8_t major = p[0] >> 5, info = p[0] & 0x1f; |
| 77 | + size_t head = 1; |
| 78 | + uint64_t arg = info; |
| 79 | + if (info == 24) { if (len < 2) return 0; arg = p[1]; head = 2; } |
| 80 | + else if (info == 25) { if (len < 3) return 0; arg = ((uint64_t)p[1] << 8) | p[2]; head = 3; } |
| 81 | + else if (info == 26) { if (len < 5) return 0; arg = ((uint64_t)p[1] << 24)|((uint64_t)p[2] << 16)|((uint64_t)p[3] << 8)|p[4]; head = 5; } |
| 82 | + else if (info == 27) { if (len < 9) return 0; arg = 0; for (int i = 1; i <= 8; i++) arg = (arg << 8) | p[i]; head = 9; } |
| 83 | + else if (info >= 28) return 0; |
| 84 | + switch (major) { |
| 85 | + case 0: case 1: case 7: return head; |
| 86 | + case 2: case 3: return head + (size_t)arg; |
| 87 | + case 4: { size_t off = head; for (uint64_t i = 0; i < arg; i++) { size_t n = cbor_item_len(p+off, len-off); if (!n) return 0; off += n; } return off; } |
| 88 | + case 5: { size_t off = head; for (uint64_t i = 0; i < arg*2; i++) { size_t n = cbor_item_len(p+off, len-off); if (!n) return 0; off += n; } return off; } |
| 89 | + default: return 0; |
| 90 | + } |
| 91 | +} |
| 92 | +static int cbor_get_text(const uint8_t *p, size_t len, const char *key, char *out, size_t cap) { |
| 93 | + if (len < 1 || (p[0] >> 5) != 5) return 0; |
| 94 | + uint8_t info = p[0] & 0x1f; |
| 95 | + if (info >= 24) return 0; |
| 96 | + size_t off = 1, klen = strlen(key); |
| 97 | + for (uint64_t i = 0; i < info; i++) { |
| 98 | + const uint8_t *k = p + off; |
| 99 | + if ((k[0] >> 5) != 3) return 0; |
| 100 | + uint8_t ki = k[0] & 0x1f; |
| 101 | + if (ki >= 24) return 0; |
| 102 | + const uint8_t *v = p + off + 1 + ki; |
| 103 | + if ((size_t)ki == klen && memcmp(k + 1, key, klen) == 0 && (v[0] >> 5) == 3) { |
| 104 | + uint8_t vi = v[0] & 0x1f; |
| 105 | + if (vi >= 24) return 0; |
| 106 | + size_t n = vi < cap - 1 ? vi : cap - 1; |
| 107 | + memcpy(out, v + 1, n); |
| 108 | + out[n] = 0; |
| 109 | + return 1; |
| 110 | + } |
| 111 | + size_t vn = cbor_item_len(v, len - (off + 1 + ki)); |
| 112 | + if (!vn) return 0; |
| 113 | + off += 1 + ki + vn; |
| 114 | + } |
| 115 | + return 0; |
| 116 | +} |
| 117 | + |
| 118 | +// CBOR EchoResponse return: decode the "echoed" field in-callback. |
| 119 | +static void cbor_cb(int ret, const char *msg, size_t len, void *ud) { |
| 120 | + Cap *c = ud; |
| 121 | + pthread_mutex_lock(&c->mu); |
| 122 | + c->ret = ret; |
| 123 | + if (ret == RET_OK) |
| 124 | + cbor_get_text((const uint8_t *)msg, len, "echoed", c->echoed, sizeof(c->echoed)); |
| 125 | + c->done = 1; |
| 126 | + pthread_cond_signal(&c->cv); |
| 127 | + pthread_mutex_unlock(&c->mu); |
| 128 | +} |
| 129 | + |
| 130 | +struct Args { void *ctx; int id; }; |
| 131 | + |
| 132 | +static void *worker(void *p) { |
| 133 | + struct Args *a = p; |
| 134 | + Cap cap; |
| 135 | + cap_init(&cap); |
| 136 | + char want[128]; |
| 137 | + for (int i = 0; i < STRESS_ITERS; i++) { |
| 138 | + snprintf(want, sizeof(want), "t%d-i%d", a->id, i); |
| 139 | + if (i & 1) { |
| 140 | + // native: EchoRequest in, typed EchoResponse* back |
| 141 | + cap_reset(&cap); |
| 142 | + EchoRequest req = {.message = want, .delayMs = 0}; |
| 143 | + if (my_timer_echo(a->ctx, native_cb, &cap, req) == RET_OK) cap_wait(&cap); |
| 144 | + } else { |
| 145 | + // CBOR: { "req": { "message": want, "delayMs": 0 } } |
| 146 | + cap_reset(&cap); |
| 147 | + FfiCbor e = ffi_cbor_new(); |
| 148 | + ffi_cbor_map(&e, 1); |
| 149 | + ffi_cbor_text(&e, "req"); |
| 150 | + ffi_cbor_map(&e, 2); |
| 151 | + ffi_cbor_kv_text(&e, "message", want); |
| 152 | + ffi_cbor_kv_int(&e, "delayMs", 0); |
| 153 | + if (my_timer_echo_cbor(a->ctx, cbor_cb, &cap, e.buf, e.len) == RET_OK) |
| 154 | + cap_wait(&cap); |
| 155 | + ffi_cbor_free(&e); |
| 156 | + } |
| 157 | + if (cap.ret == RET_OK && strcmp(cap.echoed, want) == 0) |
| 158 | + atomic_fetch_add(&g_ok, 1); |
| 159 | + else |
| 160 | + atomic_fetch_add(&g_bad, 1); |
| 161 | + } |
| 162 | + cap_destroy(&cap); |
| 163 | + return NULL; |
| 164 | +} |
| 165 | + |
| 166 | +static void create_cb(int ret, const char *msg, size_t len, void *ud) { |
| 167 | + Cap *c = ud; |
| 168 | + pthread_mutex_lock(&c->mu); |
| 169 | + c->ret = ret; |
| 170 | + c->done = 1; |
| 171 | + pthread_cond_signal(&c->cv); |
| 172 | + pthread_mutex_unlock(&c->mu); |
| 173 | + (void)msg; (void)len; |
| 174 | +} |
| 175 | + |
| 176 | +int main(void) { |
| 177 | + Cap cc; |
| 178 | + cap_init(&cc); |
| 179 | + TimerConfig cfg = {.name = "stress"}; |
| 180 | + void *ctx = my_timer_create(cfg, create_cb, &cc); |
| 181 | + cap_wait(&cc); |
| 182 | + if (!ctx) { fprintf(stderr, "create failed\n"); return 1; } |
| 183 | + |
| 184 | + pthread_t th[STRESS_THREADS]; |
| 185 | + struct Args args[STRESS_THREADS]; |
| 186 | + for (int i = 0; i < STRESS_THREADS; i++) { |
| 187 | + args[i].ctx = ctx; |
| 188 | + args[i].id = i; |
| 189 | + pthread_create(&th[i], NULL, worker, &args[i]); |
| 190 | + } |
| 191 | + for (int i = 0; i < STRESS_THREADS; i++) pthread_join(th[i], NULL); |
| 192 | + |
| 193 | + my_timer_destroy(ctx); |
| 194 | + cap_destroy(&cc); |
| 195 | + |
| 196 | + long ok = atomic_load(&g_ok), bad = atomic_load(&g_bad); |
| 197 | + long total = (long)STRESS_THREADS * STRESS_ITERS; |
| 198 | + printf("native+cbor stress: %ld/%ld ok, %ld bad (%d threads x %d iters)\n", ok, |
| 199 | + total, bad, STRESS_THREADS, STRESS_ITERS); |
| 200 | + printf(bad == 0 && ok == total ? "PASSED\n" : "FAILED\n"); |
| 201 | + return (bad == 0 && ok == total) ? 0 : 1; |
| 202 | +} |
0 commit comments