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 <[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
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
47// Push a request straight onto a channel's SQ, the way srn_reactor_submit will.
48// Spins if the SQ is momentarily full (the reactor thread is the consumer).
49static void
51 while (!srn_spsc_ring_push(r->channels[channel].sq, req)) {
52 // SQ full, the reactor thread will drain it; retry. Yield so valgrind's
53 // serial scheduler can run the reactor thread rather than spin here.
54 sched_yield();
55 }
56 r->backend->wake(r); // the reactor waits indefinitely; nudge it to drain
57}
58
59// Spin on reap until one completion arrives on `channel`.
62 while (srn_reactor_reap(r, channel, &c, 1) == 0) {
63 // Wait for the reactor thread to post the completion. Yield so valgrind's
64 // serial scheduler runs the reactor thread instead of spinning here (with
65 // the reactor's indefinite wait, nothing else hands it the CPU).
66 sched_yield();
67 }
68 return c;
69}
70
71// Activate the reactor with one channel per worker, wiring the scheduler's wake
72// seam -- the same activation srn_sched_run does for a real run.
73static void reactor_test_start(srn_reactor_t *r, size_t nworkers) {
75}
76
77// ---------------------------------------------------------------------------
78// public-API lifecycle and fresh-state.
79// ---------------------------------------------------------------------------
80
81// The engine owns a reactor; tearing the engine down without activating the
82// reactor is safe (shutdown returns early when it was never started).
84 MAKE_ENGINE(mm, engine);
85 TEST_ASSERT(engine->reactor != nullptr);
86 SHUTDOWN_ENGINE(mm, engine);
87}
88
89// Activating spawns the reactor thread and allocates one channel per worker;
90// engine shutdown stops and joins it cleanly.
92 MAKE_ENGINE(mm, engine);
93 reactor_test_start(engine->reactor, 4);
94 TEST_CHECK(engine->reactor->nchannels == 4);
95 SHUTDOWN_ENGINE(mm, engine); // joins the reactor thread
96}
97
98// A freshly activated reactor has nothing in flight and empty channels.
100 MAKE_ENGINE(mm, engine);
101 srn_reactor_t *r = engine->reactor;
102 reactor_test_start(r, 2);
103
107
109 TEST_CHECK(srn_reactor_reap(r, 0, &c, 1) == 0);
110
111 SHUTDOWN_ENGINE(mm, engine);
112}
113
114// ---------------------------------------------------------------------------
115// the submit -> reactor thread -> completion pipeline (white-box submit).
116// ---------------------------------------------------------------------------
117
118// A NOP round-trips, the reactor thread drains the SQ, completes it, and posts
119// to the CQ with the same fiber_data and a zero result.
121 MAKE_ENGINE(mm, engine);
122 srn_reactor_t *r = engine->reactor;
123 reactor_test_start(r, 1);
124
125 int marker = 0;
126 srn_reactor_io_request_t req = {.op = SRN_REACTOR_IO_NOP, .fiber_data = &marker};
127 reactor_test_submit(r, 0, &req);
128
130 TEST_CHECK(c.fiber_data == &marker);
131 TEST_CHECK(c.result == 0);
132
133 SHUTDOWN_ENGINE(mm, engine);
134}
135
136// A completion comes back on the channel it was submitted on, not another.
138 MAKE_ENGINE(mm, engine);
139 srn_reactor_t *r = engine->reactor;
140 reactor_test_start(r, 2);
141
142 int marker = 0;
143 srn_reactor_io_request_t req = {.op = SRN_REACTOR_IO_NOP, .fiber_data = &marker};
144 reactor_test_submit(r, 1, &req);
145
147 TEST_CHECK(c.fiber_data == &marker);
148 TEST_CHECK(!srn_reactor_channel_has_completions(r, 0)); // channel 0 untouched
149
150 SHUTDOWN_ENGINE(mm, engine);
151}
152
153// A SLEEP completes no earlier than its deadline, the reactor registers a timer
154// and posts the completion once the monotonic clock reaches it.
155static void test_reactor_sleep() {
156 MAKE_ENGINE(mm, engine);
157 srn_reactor_t *r = engine->reactor;
158 reactor_test_start(r, 1);
159
160 uint64_t before = srn_now_ns();
161 uint64_t delay = 50ULL * SRN_NS_PER_MS; // 50ms
162
163 int marker = 0;
165 .op = SRN_REACTOR_IO_SLEEP, .deadline_ns = before + delay, .fiber_data = &marker
166 };
167 reactor_test_submit(r, 0, &req);
168
170 TEST_CHECK(c.fiber_data == &marker);
171 TEST_CHECK(c.result == 0);
172
173 TEST_CHECK(srn_now_ns() - before >= delay); // fired no earlier than deadline
174
175 SHUTDOWN_ENGINE(mm, engine);
176}
177
178// Stress the channel rings, the test thread is the SQ producer and the CQ
179// consumer, the reactor thread is the SQ consumer and the CQ producer -- two
180// SPSC rings under contention. Every submission comes back exactly once, in
181// order (single channel drains FIFO). The case TSan exists for.
183 MAKE_ENGINE(mm, engine);
184 srn_reactor_t *r = engine->reactor;
185 reactor_test_start(r, 1);
186
187 enum { N = 100000 };
188 size_t sent = 0, got = 0;
189
190 // Submit and reap are interleaved through the public API: the channel's
191 // admission cap fills, so the producer must consume completions (and drop
192 // them via srn_reactor_op_done) to reopen the window and keep making
193 // progress. The test thread is both sides of channel 0.
194 while (got < N) {
195 bool progress = false;
196
197 while (sent < N) {
199 .op = SRN_REACTOR_IO_NOP, .fiber_data = (void *)(uintptr_t)sent
200 };
201 if (!srn_reactor_submit(r, 0, &req)) {
202 break; // channel at its in-flight cap; go drain the CQ
203 }
204 sent++;
205 progress = true;
206 }
207
209 while (srn_reactor_reap(r, 0, &c, 1) == 1) {
210 if ((uintptr_t)c.fiber_data != got || c.result != 0) {
211 TEST_CHECK(false); // out of order or bad result
212 goto done;
213 }
215 got++;
216 progress = true;
217 }
218
219 if (!progress) {
220 // The reactor thread holds the outstanding ops; yield so valgrind's
221 // serial scheduler can run it rather than spin here.
222 sched_yield();
223 }
224 }
225done:
226 TEST_CHECK(got == N);
228 SHUTDOWN_ENGINE(mm, engine);
229}
#define TEST_CHECK(cond)
Definition acutest.h:95
#define TEST_ASSERT(cond)
Definition acutest.h:117
Internal reactor backend interface.
#define SHUTDOWN_ENGINE(mm, engine)
Definition base.h:38
#define MAKE_ENGINE(mm, engine)
Definition base.h:32
AI Generated (🤦) Fiber subsystem overview.
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:390
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:162
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:220
bool srn_reactor_idle(srn_reactor_t *reactor)
Whether the reactor has no operations in flight.
Definition reactor.c:169
bool srn_reactor_channel_has_completions(srn_reactor_t *reactor, size_t channel)
Whether channel's completion queue has unconsumed completions.
Definition reactor.c:237
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:174
Reactor overview.
@ SRN_REACTOR_IO_SLEEP
Wait out a deadline.
Definition reactor.h:66
@ SRN_REACTOR_IO_NOP
Completes immediately with a zero result.
Definition reactor.h:64
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_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:1116
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.
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:156
void * fiber_data
The fiber_data of the submission this completes.
Definition reactor.h:159
size_t result
The result of the operation on success, bytes transferred for the transfers, the accepted descriptor ...
Definition reactor.h:163
A submission, one request a fiber places on its channel.
Definition reactor.h:113
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
#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:46