Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
spsc_ring_tests.h
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <[email protected]>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19#pragma once
20
21#include <sched.h>
22#include <stdint.h>
23
26
27#include "acutest.h"
28#include "base.h"
29
30#define SPSC_RING_TESTS(X) \
31 X("spsc_ring::make_valid", test_spsc_ring_make_valid), \
32 X("spsc_ring::make_out_of_range", test_spsc_ring_make_oor), \
33 X("spsc_ring::fill_drain_wrap", test_spsc_ring_fill_drain), \
34 X("spsc_ring::elem_bytes", test_spsc_ring_elem_bytes), \
35 X("spsc_ring::spsc_stress", test_spsc_ring_spsc_stress)
36
37// A valid magnitude yields a power-of-two capacity, no error, and an empty
38// ring.
40 MAKE_ENGINE(mm, engine);
41 MAKE_CONTEXT(engine, ctx);
42
43 srn_spsc_ring_t *r = srn_spsc_ring_make(ctx, 3, sizeof(uint64_t)); // cap 8
44 TEST_ASSERT(r != nullptr);
46 TEST_CHECK(r->cap == 8);
47 TEST_CHECK(r->mask == 7);
48 TEST_CHECK(r->elem_size == sizeof(uint64_t));
50
51 RELEASE_CONTEXT(ctx);
52 SHUTDOWN_ENGINE(mm, engine);
53}
54
55// A magnitude below MIN_MAG or above MAX_MAG returns a value carrying an error,
56// not an aborting failure.
58 MAKE_ENGINE(mm, engine);
59 MAKE_CONTEXT(engine, ctx);
60
61 srn_spsc_ring_t *too_small = srn_spsc_ring_make(ctx, 1, sizeof(int));
62 TEST_ASSERT(too_small != nullptr);
63 TEST_CHECK(HAS_ERROR(*too_small));
64 TEST_ASSERT(too_small->maybe_error->tag == WRONG_RANGE);
65
66 srn_spsc_ring_t *too_big = srn_spsc_ring_make(ctx, 64, sizeof(int));
67 TEST_ASSERT(too_big != nullptr);
68 TEST_CHECK(HAS_ERROR(*too_big));
69 TEST_ASSERT(too_big->maybe_error->tag == WRONG_RANGE);
70
71 RELEASE_CONTEXT(ctx);
72 SHUTDOWN_ENGINE(mm, engine);
73}
74
75// Fill to capacity (push past it fails), drain, and refill so the indices wrap;
76// elements still come out in order across the wrap point.
78 MAKE_ENGINE(mm, engine);
79 MAKE_CONTEXT(engine, ctx);
80
81 srn_spsc_ring_t *r = srn_spsc_ring_make(ctx, 2, sizeof(uint64_t)); // cap 4
82 TEST_ASSERT(r != nullptr && !HAS_ERROR(*r));
83
85
86 for (uint64_t i = 0; i < 4; i++) {
88 }
89
90 uint64_t overflow = 99;
91 TEST_CHECK(!srn_spsc_ring_push(r, &overflow)); // full
93
94 uint64_t v = 0;
95 TEST_CHECK(srn_spsc_ring_pop(r, &v) && v == 0);
96 TEST_CHECK(srn_spsc_ring_pop(r, &v) && v == 1);
97
98 uint64_t a = 4, b = 5; // these wrap into the freed slots
101
102 for (uint64_t expect = 2; expect <= 5; expect++) {
103 TEST_CHECK(srn_spsc_ring_pop(r, &v) && v == expect);
104 }
106 TEST_CHECK(!srn_spsc_ring_pop(r, &v)); // empty
107
108 RELEASE_CONTEXT(ctx);
109 SHUTDOWN_ENGINE(mm, engine);
110}
111
112typedef struct spsc_pair {
113 uint32_t a;
114 uint32_t b;
116
117// A struct element round trips field for field, confirming push/pop copy the
118// whole element via elem_size, not just scalar values.
120 MAKE_ENGINE(mm, engine);
121 MAKE_CONTEXT(engine, ctx);
122
123 srn_spsc_ring_t *r = srn_spsc_ring_make(ctx, 3, sizeof(spsc_pair));
124 TEST_ASSERT(r != nullptr && !HAS_ERROR(*r));
125
126 spsc_pair in = {.a = 0xDEADBEEFU, .b = 0x12345678U};
128
129 spsc_pair out = {0};
131 TEST_CHECK(out.a == 0xDEADBEEFU && out.b == 0x12345678U);
132
133 RELEASE_CONTEXT(ctx);
134 SHUTDOWN_ENGINE(mm, engine);
135}
136
141
142static void spsc_producer(void *p) {
144 for (uint64_t i = 0; i < a->count; i++) {
145 while (!srn_spsc_ring_push(a->ring, &i)) {
146 // Ring full, yield so the consumer gets the CPU. Without this a tight
147 // spin starves the other thread under valgrind's serialized scheduler.
148 sched_yield();
149 }
150 }
151}
152
153// One producer thread, one consumer (this one), a small ring forcing constant
154// full/empty contention and wraparound. Every value arrives exactly once and in
155// order. This is the case the memory ordering exists for; run it under TSan.
157 MAKE_ENGINE(mm, engine);
158 MAKE_CONTEXT(engine, ctx);
159
160 srn_spsc_ring_t *r = srn_spsc_ring_make(ctx, 6, sizeof(uint64_t)); // cap 64
161 TEST_ASSERT(r != nullptr && !HAS_ERROR(*r));
162
163 constexpr uint64_t n = 100000;
164 spsc_producer_arg arg = {.ring = r, .count = n};
167
168 bool in_order = true;
169 for (uint64_t i = 0; i < n; i++) {
170 uint64_t v;
171 while (!srn_spsc_ring_pop(r, &v)) {
172 // Ring empty, yield so the producer gets the CPU. Without this a tight
173 // spin starves the producer under valgrind's serialized scheduler.
174 sched_yield();
175 }
176 if (v != i) {
177 in_order = false;
178 break;
179 }
180 }
181 TEST_CHECK(in_order);
183
184 RELEASE_CONTEXT(ctx);
185 SHUTDOWN_ENGINE(mm, engine);
186}
static srn_fiber_result_t producer(srn_context_t *ctx, void *arg)
#define TEST_CHECK(cond)
Definition acutest.h:95
int n
Definition acutest.h:525
#define TEST_ASSERT(cond)
Definition acutest.h:117
#define RELEASE_CONTEXT(x)
Definition base.h:46
#define SHUTDOWN_ENGINE(mm, engine)
Definition base.h:38
#define MAKE_ENGINE(mm, engine)
Definition base.h:32
#define MAKE_CONTEXT(engine, x)
Definition base.h:42
#define HAS_ERROR(x)
True when x – a value carrying an ERROR_HEADER – has an error attached.
Definition errors.h:63
@ WRONG_RANGE
Definition errors.h:84
bool srn_spsc_ring_push(srn_spsc_ring_t *r, const void *elem)
Producer side.
Definition spsc_ring.c:61
bool srn_spsc_ring_empty(const srn_spsc_ring_t *r)
Whether the ring currently holds no elements.
Definition spsc_ring.c:103
srn_spsc_ring_t * srn_spsc_ring_make(srn_context_t *ctx, size_t magnitude, size_t elem_size)
Set up a ring by allocating the buf to be the size of cap * elem_size.
Definition spsc_ring.c:27
bool srn_spsc_ring_pop(srn_spsc_ring_t *r, void *out)
Consumer side.
Definition spsc_ring.c:83
A mutable single producer, single consumer ring of fixed size elements.
static void test_spsc_ring_make_valid()
static void test_spsc_ring_make_oor()
static void test_spsc_ring_elem_bytes()
static void test_spsc_ring_spsc_stress()
static void test_spsc_ring_fill_drain()
static void spsc_producer(void *p)
srn_spsc_ring_t * ring
size_t elem_size
Definition spsc_ring.h:56
SRN_SPSC_RING_CAP_TYPE cap
Definition spsc_ring.h:54
SRN_SPSC_RING_CAP_TYPE mask
Definition spsc_ring.h:55
srn_thread_t, srn_mutex_t, and srn_cond_t model the thread-level operations the runtime needs,...
srn_thread_status_t srn_thread_join(srn_thread_t *t)
Block until the thread started for t returns.
@ SRN_THREAD_OK
Definition thread.h:62
srn_thread_status_t srn_thread_spawn(srn_thread_t *t, void(*fn)(void *), void *arg)
Run fn(arg) on a new OS thread.