Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
mutex_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 <stdatomic.h>
22#include <stdint.h>
23
24#include "base.h"
25#include "serene/rt/fiber.h"
27
28#define MUTEX_TESTS(X) \
29 X("mutex::init", test_mutex_init), X("mutex::uncontended", test_mutex_uncontended), \
30 X("mutex::mutual_exclusion", test_mutex_mutual_exclusion), \
31 X("mutex::fifo_order", test_mutex_fifo_order), \
32 X("mutex::mt_contention", test_mutex_mt_contention)
33
36
37static inline srn_fiber_t *test_mtx_state(void) {
38 return atomic_load_explicit(&test_mtx.state, memory_order_relaxed);
39}
40
41// A freshly initialised lock is unlocked and has no waiters
42static void test_mutex_init() {
44 TEST_CHECK(test_mtx_state() == nullptr);
45 TEST_CHECK(test_mtx.waiters_head == nullptr);
46}
47
49 UNUSED(ctx);
50 UNUSED(arg);
52
55 // Re-acquiring a just-released lock must succeed.
59 return nullptr;
60}
61
63 MAKE_ENGINE(mm, engine);
64 MAKE_CONTEXT(engine, ctx);
65 srn_scheduler_t *sched = engine->scheduler;
66 ASSERT_NOT_NULL(sched);
67
69
71 srn_sched_run(sched, 1);
72
74 // The lock is released once the fiber is done.
75 TEST_CHECK(test_mtx_state() == nullptr);
76
77 RELEASE_CONTEXT(ctx);
78 SHUTDOWN_ENGINE(mm, engine);
79}
80
81// TL;DR: Holding a lock and yielding, is safe and protects the critical path.
82// N fibers on one worker each read a shared counter, yield while holding the lock, then write the
83// incremented value back. The yield inside the critical section is the trap, it hands the worker to
84// another fiber mid-update. Without the lock that peer would read the same stale value and an
85// update would be lost, leaving the total below N. The lock forces the peer to suspend at its own
86// lock() until the holder unlocks, so every increment lands.
87#define MTX_MUTEX_N 8
89
91 UNUSED(ctx);
92 UNUSED(arg);
94 int seen = test_mtx_me_counter;
95 // Yield the execution and let other fibers run
97 test_mtx_me_counter = seen + 1;
99 return nullptr;
100}
101
103 MAKE_ENGINE(mm, engine);
104 MAKE_CONTEXT(engine, ctx);
105 srn_scheduler_t *sched = engine->scheduler;
106 ASSERT_NOT_NULL(sched);
107
110
111 for (int i = 0; i < MTX_MUTEX_N; i++) {
113 }
114 srn_sched_run(sched, 4);
115
116 // No update was lost despite each holder yielding in the middle of the criticalsection.
118 TEST_CHECK(test_mtx_state() == nullptr);
119
120 RELEASE_CONTEXT(ctx);
121 SHUTDOWN_ENGINE(mm, engine);
122}
123
124// Waiters are served oldest first. On one worker a holder takes the lock, then yields once. Then
125// Round robin runs the three waiters in arrival order and each blocks on lock(), so they queue as
126// 1, 2, 3. When the holder unlocks, the reversal in unlock hands the lock back in that same
127// order, so the recorded acquisition order is {1, 2, 3}. A LIFO stack would record {3, 2, 1}.
128#define MTX_FIFO_N 3
131
133 UNUSED(ctx);
134 int id = (int)(intptr_t)arg;
138 return nullptr;
139}
140
142 UNUSED(ctx);
143 UNUSED(arg);
145 // One yield is enough: the single worker runs all three waiters (each parking
146 // on lock) before this holder is scheduled again.
149 return nullptr;
150}
151
153 MAKE_ENGINE(mm, engine);
154 MAKE_CONTEXT(engine, ctx);
155 srn_scheduler_t *sched = engine->scheduler;
156 ASSERT_NOT_NULL(sched);
157
159 test_mtx_fifo_n = 0;
160
161 // Holder first so it owns the lock before any waiter runs, then the waiters
162 // in the order whose service order we assert.
163 (void)srn_fiber_spawn(ctx, test_mtx_fifo_holder_entry, nullptr);
164 for (int i = 0; i < MTX_FIFO_N; i++) {
165 (void)srn_fiber_spawn(ctx, test_mtx_fifo_waiter_entry, (void *)(intptr_t)(i + 1));
166 }
167 srn_sched_run(sched, 1);
168
170 for (int i = 0; i < MTX_FIFO_N; i++) {
171 TEST_CHECK(test_mtx_fifo_order[i] == i + 1);
172 TEST_MSG(
174 );
175 }
176 TEST_CHECK(test_mtx_state() == nullptr);
177
178 RELEASE_CONTEXT(ctx);
179 SHUTDOWN_ENGINE(mm, engine);
180}
181
182// Real parallelism. Many fibers across several worker threads each hammer the lock, doing a plain
183// (nonatomic) read, modify, write of a shared counter inside the critical section. The count is
184// only exact if the lock both excludes concurrent holders and publishes the previous holder's write
185// to the next one. Any missed exclusion or ordering shows up as a total. It is also the case TSan
186// watches for a data race on the plain counter.
187#define MTX_MT_N 64
188#define MTX_MT_ITERS 100
190
192 UNUSED(ctx);
193 UNUSED(arg);
194 for (int i = 0; i < MTX_MT_ITERS; i++) {
196 // plain RMW, guarded solely by the lock
199 }
200 return nullptr;
201}
202
204 MAKE_ENGINE(mm, engine);
205 MAKE_CONTEXT(engine, ctx);
206 srn_scheduler_t *sched = engine->scheduler;
207 ASSERT_NOT_NULL(sched);
208
211
212 for (int i = 0; i < MTX_MT_N; i++) {
213 (void)srn_fiber_spawn(ctx, test_mtx_mt_entry, nullptr);
214 }
215 srn_sched_run(sched, 4);
216
218 TEST_CHECK(test_mtx_state() == nullptr);
219
220 RELEASE_CONTEXT(ctx);
221 SHUTDOWN_ENGINE(mm, engine);
222}
#define TEST_CHECK(cond)
Definition acutest.h:95
#define TEST_MSG(...)
Definition acutest.h:223
#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
srn_fiber_t * srn_fiber_spawn(srn_context_t *ctx, srn_fiber_entry_t entry, void *arg)
Make and schedule a fiber with every default, the engine's scheduler, the configured stack size,...
Definition fiber.c:250
AI Generated (🤦) Fiber subsystem overview.
void * srn_fiber_result_t
What a fiber's entry produces, type-erased.
Definition fiber.h:161
void srn_fiber_mutex_init(srn_fiber_mutex_t *m)
Initialise m to the unlocked state.
Definition mutex.c:76
void srn_fiber_mutex_unlock(srn_fiber_mutex_t *m)
Release m.
Definition mutex.c:99
void srn_fiber_mutex_lock(srn_fiber_mutex_t *m)
Acquire m.
Definition mutex.c:81
A fiber blocking mutex.
static srn_fiber_result_t test_mtx_mt_entry(srn_context_t *ctx, void *arg)
static srn_fiber_result_t test_mtx_fifo_waiter_entry(srn_context_t *ctx, void *arg)
static int test_mtx_fifo_n
static srn_fiber_mutex_t test_mtx
Definition mutex_tests.h:34
static long test_mtx_mt_counter
static void test_mutex_init()
Definition mutex_tests.h:42
#define MTX_MT_ITERS
static int test_mtx_me_counter
Definition mutex_tests.h:88
#define MTX_FIFO_N
static srn_fiber_result_t test_mtx_me_entry(srn_context_t *ctx, void *arg)
Definition mutex_tests.h:90
static void test_mutex_mt_contention()
static srn_fiber_result_t test_mtx_fifo_holder_entry(srn_context_t *ctx, void *arg)
static void test_mutex_uncontended()
Definition mutex_tests.h:62
static srn_fiber_t * test_mtx_state(void)
Definition mutex_tests.h:37
static srn_fiber_result_t test_mtx_uncontended_entry(srn_context_t *ctx, void *arg)
Definition mutex_tests.h:48
#define MTX_MUTEX_N
Definition mutex_tests.h:87
static void test_mutex_mutual_exclusion()
static int test_mtx_fifo_order[MTX_FIFO_N]
#define MTX_MT_N
static bool test_mtx_uncontended_ran
Definition mutex_tests.h:35
static void test_mutex_fifo_order()
void srn_sched_run(srn_scheduler_t *sched, size_t nworkers)
Run the scheduler with nworkers os threads draining it, returning once the pool goes quiescent (every...
Definition scheduler.c:875
void srn_fiber_yield(void)
Yield cooperatively, re-enqueue the running fiber and run the next ready one.
Definition scheduler.c:1039
#define UNUSED(x)
Definition utils.h:45