Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
reactor_tests.h
Go to the documentation of this file.
1/* -*- C -*-
2 * Serene programming language
3 * Copyright (C) 2019-2026 Sameer Rahmani <lxsameer@lxsameer.com>
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
24#include <serene/rt/engine.h>
25#include <serene/rt/fiber.h>
26#include <serene/rt/reactor.h>
27
28// Mostly white-box, some tests reach into the channel rings directly, below
29// srn_reactor_submit, to exercise ring-level behavior in isolation. Raw
30// pushes bypass the admission accounting, so a test using them must keep its
31// outstanding ops under the ring capacity.
32#include "acutest.h"
33#include "base.h"
34#include "rt/reactor/backend.h"
35#include "rt/reactor/internal.h"
37
38#define REACTOR_TESTS(X) \
39 X("reactor::lifecycle", test_reactor_lifecycle), \
40 X("reactor::activate_shutdown", test_reactor_activate_shutdown), \
41 X("reactor::fresh_state", test_reactor_fresh_state), \
42 X("reactor::nop_roundtrip", test_reactor_nop_roundtrip), \
43 X("reactor::channel_routing", test_reactor_channel_routing), \
44 X("reactor::cq_spsc_stress", test_reactor_cq_spsc_stress), \
45 X("reactor::sleep", test_reactor_sleep), \
46 X("reactor::static_errors_stay_immutable", test_reactor_static_errors_stay_immutable)
47
48// The static IO errors are shared by every failure with the same tag, so
49// taking a trace on one has to go through a copy and leave the shared error
50// untouched.
52 MAKE_ENGINE(mm, engine);
53 MAKE_CONTEXT(engine, ctx);
54
56 ASSERT_NOT_NULL(shared);
57 TEST_CHECK(shared->as.frames == nullptr);
58
59 srn_error_t *owned = ERR_FROM(ctx, shared);
60 ASSERT_NOT_NULL(owned);
61 TEST_CHECK(owned != shared);
62 TEST_CHECK(owned->tag == NOT_FOUND);
63 TEST_CHECK(owned->msg == shared->msg);
65 TEST_CHECK(owned->as.frames->count == 1);
66 TEST_CHECK(shared->as.frames == nullptr);
67
68 RELEASE_CONTEXT(ctx);
69 SHUTDOWN_ENGINE(mm, engine);
70}
71
72// Push a request straight onto a channel's SQ, the way srn_reactor_submit will.
73// Spins if the SQ is momentarily full (the reactor thread is the consumer).
74static void
76 while (!srn_spsc_ring_push(r->channels[channel].sq, req)) {
77 // SQ full, the reactor thread will drain it; retry. Yield so valgrind's
78 // serial scheduler can run the reactor thread rather than spin here.
79 sched_yield();
80 }
81 r->backend->wake(r); // the reactor waits indefinitely; nudge it to drain
82}
83
84// Spin on reap until one completion arrives on `channel`.
87 while (srn_reactor_reap(r, channel, &c, 1) == 0) {
88 // Wait for the reactor thread to post the completion. Yield so valgrind's
89 // serial scheduler runs the reactor thread instead of spinning here (with
90 // the reactor's indefinite wait, nothing else hands it the CPU).
91 sched_yield();
92 }
93 return c;
94}
95
96// Activate the reactor with one channel per worker, wiring the scheduler's wake
97// seam -- the same activation srn_sched_run does for a real run.
98static void reactor_test_start(srn_reactor_t *r, size_t nworkers) {
100}
101
102// ---------------------------------------------------------------------------
103// public-API lifecycle and fresh-state.
104// ---------------------------------------------------------------------------
105
106// The engine owns a reactor; tearing the engine down without activating the
107// reactor is safe (shutdown returns early when it was never started).
109 MAKE_ENGINE(mm, engine);
110 TEST_ASSERT(engine->reactor != nullptr);
111 SHUTDOWN_ENGINE(mm, engine);
112}
113
114// Activating spawns the reactor thread and allocates one channel per worker;
115// engine shutdown stops and joins it cleanly.
117 MAKE_ENGINE(mm, engine);
118 reactor_test_start(engine->reactor, 4);
119 TEST_CHECK(engine->reactor->nchannels == 4);
120 SHUTDOWN_ENGINE(mm, engine); // joins the reactor thread
121}
122
123// A freshly activated reactor has nothing in flight and empty channels.
125 MAKE_ENGINE(mm, engine);
126 srn_reactor_t *r = engine->reactor;
127 reactor_test_start(r, 2);
128
132
134 TEST_CHECK(srn_reactor_reap(r, 0, &c, 1) == 0);
135
136 SHUTDOWN_ENGINE(mm, engine);
137}
138
139// ---------------------------------------------------------------------------
140// the submit -> reactor thread -> completion pipeline (white-box submit).
141// ---------------------------------------------------------------------------
142
143// A NOP round-trips, the reactor thread drains the SQ, completes it, and posts
144// to the CQ with the same fiber_data and a zero result.
146 MAKE_ENGINE(mm, engine);
147 srn_reactor_t *r = engine->reactor;
148 reactor_test_start(r, 1);
149
150 int marker = 0;
151 srn_reactor_io_request_t req = {.op = SRN_REACTOR_IO_NOP, .fiber_data = &marker};
152 reactor_test_submit(r, 0, &req);
153
155 TEST_CHECK(c.fiber_data == &marker);
156 TEST_CHECK(c.result == 0);
157
158 SHUTDOWN_ENGINE(mm, engine);
159}
160
161// A completion comes back on the channel it was submitted on, not another.
163 MAKE_ENGINE(mm, engine);
164 srn_reactor_t *r = engine->reactor;
165 reactor_test_start(r, 2);
166
167 int marker = 0;
168 srn_reactor_io_request_t req = {.op = SRN_REACTOR_IO_NOP, .fiber_data = &marker};
169 reactor_test_submit(r, 1, &req);
170
172 TEST_CHECK(c.fiber_data == &marker);
173 TEST_CHECK(!srn_reactor_channel_has_completions(r, 0)); // channel 0 untouched
174
175 SHUTDOWN_ENGINE(mm, engine);
176}
177
178// A SLEEP completes no earlier than its deadline, the reactor registers a timer
179// and posts the completion once the monotonic clock reaches it.
180static void test_reactor_sleep() {
181 MAKE_ENGINE(mm, engine);
182 srn_reactor_t *r = engine->reactor;
183 reactor_test_start(r, 1);
184
185 uint64_t before = srn_now_ns();
186 uint64_t delay = 50ULL * SRN_NS_PER_MS; // 50ms
187
188 int marker = 0;
190 .op = SRN_REACTOR_IO_SLEEP, .deadline_ns = before + delay, .fiber_data = &marker
191 };
192 reactor_test_submit(r, 0, &req);
193
195 TEST_CHECK(c.fiber_data == &marker);
196 TEST_CHECK(c.result == 0);
197
198 TEST_CHECK(srn_now_ns() - before >= delay); // fired no earlier than deadline
199
200 SHUTDOWN_ENGINE(mm, engine);
201}
202
203// Stress the channel rings, the test thread is the SQ producer and the CQ
204// consumer, the reactor thread is the SQ consumer and the CQ producer -- two
205// SPSC rings under contention. Every submission comes back exactly once, in
206// order (single channel drains FIFO). The case TSan exists for.
208 MAKE_ENGINE(mm, engine);
209 srn_reactor_t *r = engine->reactor;
210 reactor_test_start(r, 1);
211
212 enum { N = 100000 };
213 size_t sent = 0, got = 0;
214
215 // Submit and reap are interleaved through the public API: the channel's
216 // admission cap fills, so the producer must consume completions (and drop
217 // them via srn_reactor_op_done) to reopen the window and keep making
218 // progress. The test thread is both sides of channel 0.
219 while (got < N) {
220 bool progress = false;
221
222 while (sent < N) {
224 .op = SRN_REACTOR_IO_NOP, .fiber_data = (void *)(uintptr_t)sent
225 };
226 if (!srn_reactor_submit(r, 0, &req)) {
227 break; // channel at its in-flight cap; go drain the CQ
228 }
229 sent++;
230 progress = true;
231 }
232
234 while (srn_reactor_reap(r, 0, &c, 1) == 1) {
235 if ((uintptr_t)c.fiber_data != got || c.result != 0) {
236 TEST_CHECK(false); // out of order or bad result
237 goto done;
238 }
240 got++;
241 progress = true;
242 }
243
244 if (!progress) {
245 // The reactor thread holds the outstanding ops; yield so valgrind's
246 // serial scheduler can run it rather than spin here.
247 sched_yield();
248 }
249 }
250done:
251 TEST_CHECK(got == N);
253 SHUTDOWN_ENGINE(mm, engine);
254}
#define TEST_CHECK(cond)
Definition acutest.h:95
#define TEST_ASSERT(cond)
Definition acutest.h:117
Internal reactor backend interface.
#define RELEASE_CONTEXT(x)
Definition base.h:48
#define ASSERT_NOT_NULL(x)
Definition base.h:30
#define SHUTDOWN_ENGINE(mm, engine)
Definition base.h:40
#define MAKE_ENGINE(mm, engine)
Definition base.h:34
#define MAKE_CONTEXT(engine, x)
Definition base.h:44
#define ERR_FROM(ctx, src)
Definition errors.h:224
@ NOT_FOUND
Definition errors.h:133
AI Generated (🤦) Fiber subsystem overview.
srn_error_t * srn_reactor_errors_static(srn_error_tag_t tag)
Return the shared, immutable error for an IO failure tag.
Definition reactor.c:194
void srn_reactor_activate(srn_reactor_t *reactor, size_t nchannels, srn_reactor_notify_fn notify)
Bring the reactor up, allocate nchannels channels (one per worker) and start the reactor thread.
Definition reactor.c:470
void srn_reactor_op_done(srn_reactor_t *reactor, size_t channel)
Drop channel's and the reactor's in-flight counts by one.
Definition reactor.c:236
size_t srn_reactor_reap(srn_reactor_t *reactor, size_t channel, srn_reactor_io_completion_t *out, size_t max)
Drain up to max completions from channel's completion queue into out, returning how many were taken.
Definition reactor.c:298
bool srn_reactor_idle(srn_reactor_t *reactor)
Whether the reactor has no operations in flight.
Definition reactor.c:244
bool srn_reactor_channel_has_completions(srn_reactor_t *reactor, size_t channel)
Whether channel's completion queue has unconsumed completions.
Definition reactor.c:315
bool srn_reactor_submit(srn_reactor_t *reactor, size_t channel, const srn_reactor_io_request_t *request)
Place a request on channel's submission queue and rouse the reactor.
Definition reactor.c:249
Reactor overview.
@ SRN_REACTOR_IO_SLEEP
Wait out a deadline.
Definition reactor.h:69
@ SRN_REACTOR_IO_NOP
Completes immediately with a zero result.
Definition reactor.h:67
static srn_reactor_io_completion_t reactor_test_await(srn_reactor_t *r, size_t channel)
static void reactor_test_start(srn_reactor_t *r, size_t nworkers)
static void test_reactor_lifecycle()
static void test_reactor_static_errors_stay_immutable()
static void test_reactor_channel_routing()
static void test_reactor_sleep()
static void test_reactor_activate_shutdown()
static void test_reactor_fresh_state()
static void test_reactor_nop_roundtrip()
static void test_reactor_cq_spsc_stress()
static void reactor_test_submit(srn_reactor_t *r, size_t channel, const srn_reactor_io_request_t *req)
void srn_sched_wake_worker(srn_scheduler_t *sched, size_t channel)
Rouse parked workers so the owner of channel consumes its completions.
Definition scheduler.c:1171
bool srn_spsc_ring_push(srn_spsc_ring_t *r, const void *elem)
Producer side.
Definition spsc_ring.c:61
A mutable single producer, single consumer ring of fixed size elements.
A runtime error, a tag classifying the failure and a human-readable message.
Definition errors.h:173
union srn_error_t::@110141362101221075206211275212023153042233307220 as
char * msg
Definition errors.h:176
srn_error_tag_t tag
Definition errors.h:175
srn_trace_frames_t * frames
Definition errors.h:181
void(* wake)(srn_reactor_t *reactor)
Definition backend.h:77
srn_spsc_ring_t * sq
Definition internal.h:32
A completion, the result of one submission, echoed back on the channel.
Definition reactor.h:159
void * fiber_data
The fiber_data of the submission this completes.
Definition reactor.h:162
size_t result
The result of the operation on success, bytes transferred for the transfers, the accepted descriptor ...
Definition reactor.h:166
A submission, one request a fiber places on its channel.
Definition reactor.h:116
srn_reactor_io_channel_t * channels
One channel per worker, there is a 1to1 mapping between worker ids and channel ids.
Definition internal.h:56
const srn_reactor_backend_t * backend
The chosen kernel mechanism and its private state, set at activation.
Definition internal.h:47
uint16_t count
Definition errors.h:166
#define SRN_NS_PER_MS
nanoseconds in one millisecond
Definition utils.h:308
uint64_t srn_now_ns(void)
The current time on the monotonic clock, in nanoseconds.
Definition utils.c:45