Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
mutex.c
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 library is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser 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 library 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 Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <https://www.gnu.org/licenses/>.
17 */
18
20
21#include "serene/rt/fiber.h"
22
23/**
24 * Marker stored in srn_fiber_mutex_t.state to mean the lock is held with no waiter queued. Only its
25 * address matters, its fields are never touched, so it only has to be a non-null `srn_fiber_t`
26 * pointer distinct from every real fiber. It also sits at the bottom of the incoming stack and
27 * terminates it, so a waiter push and the drain walk need no null special case.
28 */
30#define SRN_MUTEX_HELD_WITH_NO_WAITER (&srn_mutex_held_marker)
31
32/**
33 * Suspend commit function for a contended `srn_fiber_mutex_lock`. Since the worker routine runs it
34 * once `self` is off its stack (see `srn_fiber_suspend`), so pushing `self` onto the waiter stack
35 * cannot race the running fiber.
36 *
37 * If the lock has gone free since the fast path failed, it takes the lock and returns false to
38 * resume `self` at once. Otherwise it pushes `self` onto the incoming stack and returns true to
39 * stay suspended.
40 */
41static bool srn_fiber_mutex_suspend(srn_fiber_t *self, void *arg) {
42 srn_fiber_mutex_t *m = arg;
43 srn_fiber_t *cur = atomic_load_explicit(&m->state, memory_order_acquire);
44 for (;;) {
45 // This check is what closes the lost wakeup window between the failed fast path and the push, a
46 // concurrent unlock can never leave `self` parked on a lock nobody holds.
47 if (cur == nullptr) {
48
49 if (
50 atomic_compare_exchange_weak_explicit(
51 &m->state, &cur, SRN_MUTEX_HELD_WITH_NO_WAITER, memory_order_acquire, memory_order_acquire
52 )
53 ) {
54 // We got lucky. The lock is ours, don't suspend the fiber and run with the lock
55 return false;
56 }
57 // Somebody else change the state before us, `cur` is now loaded with the new value,
58 // retry is likely to fail and we will land on the locked branch.
59 continue;
60 }
61
62 // Locked, push self as the new stack top. `cur` is the held marker or a fiber, so the chain
63 // always bottoms out at the marker.
64 self->link = cur;
65 if (
66 atomic_compare_exchange_weak_explicit(
67 &m->state, &cur, self, memory_order_release, memory_order_acquire
68 )
69 ) {
70 // stay suspended a future unlock hands the lock to us
71 return true;
72 }
73 }
74}
75
77 atomic_init(&m->state, nullptr);
78 m->waiters_head = nullptr;
79}
80
82 srn_fiber_t *expected = nullptr;
83
84 if (
85 atomic_compare_exchange_strong_explicit(
86 &m->state, &expected, SRN_MUTEX_HELD_WITH_NO_WAITER, memory_order_acquire,
87 memory_order_relaxed
88 )
89 ) {
90 // Yay, uncontended
91 return;
92 }
93
94 // Contended: suspend. The reattempt lives in the commit, and on resume we own the lock, so there
95 // is nothing to recheck here.
97}
98
100 // Serve the oldest waiters.
101 if (m->waiters_head != nullptr) {
102 srn_fiber_t *next = m->waiters_head;
103 m->waiters_head = next->link;
104 srn_fiber_ready(next);
105 return;
106 }
107
108 // The FIFO queue is drained. Detach the incoming stack in one swap, or release the
109 // lock if no waiter is queued.
110 srn_fiber_t *cur = atomic_load_explicit(&m->state, memory_order_relaxed);
111 for (;;) {
113 // Held with nothing queued, drop the lock.
114 if (
115 atomic_compare_exchange_weak_explicit(
116 &m->state, &cur, nullptr, memory_order_release, memory_order_relaxed
117 )
118 ) {
119 return;
120 }
121
122 // Somebody just added itself to the waiters list by moving the state stack
123 continue;
124 }
125
126 // Take the whole batch, leaving the lock held with an empty incoming stack.
127 if (
128 atomic_compare_exchange_weak_explicit(
129 &m->state, &cur, SRN_MUTEX_HELD_WITH_NO_WAITER, memory_order_acquire, memory_order_relaxed
130 )
131 ) {
132 // Reverse the batch (newest first) into oldest first order, stopping at the marker (no waiter
133 // marker) that terminates the stack, store it in the waiters list, and ready the oldest
134 // fiber.
135 //
136 // The next call to unlock on the same lock will go through the waiters_head tha we prepared
137 srn_fiber_t *fifo = nullptr;
138 for (srn_fiber_t *top = cur; top != SRN_MUTEX_HELD_WITH_NO_WAITER;) {
139 srn_fiber_t *nxt = top->link;
140 top->link = fifo;
141 fifo = top;
142 top = nxt;
143 }
144 // Hand off to the oldest; keep the rest queued for later unlocks.
145 m->waiters_head = fifo->link;
146 srn_fiber_ready(fifo);
147 return;
148 }
149 }
150}
AI Generated (🤦) Fiber subsystem overview.
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
static bool srn_fiber_mutex_suspend(srn_fiber_t *self, void *arg)
Suspend commit function for a contended srn_fiber_mutex_lock.
Definition mutex.c:41
static srn_fiber_t srn_mutex_held_marker
Marker stored in srn_fiber_mutex_t.state to mean the lock is held with no waiter queued.
Definition mutex.c:29
void srn_fiber_mutex_lock(srn_fiber_mutex_t *m)
Acquire m.
Definition mutex.c:81
#define SRN_MUTEX_HELD_WITH_NO_WAITER
Definition mutex.c:30
A fiber blocking mutex.
void srn_fiber_ready(srn_fiber_t *fiber)
Mark a suspended fiber runnable again, waking it when the event it awaited occurs.
Definition scheduler.c:1083
void srn_fiber_suspend(srn_fiber_park_fn commit, void *arg)
A suspended fiber is on no scheduler queue, and the scheduler does not track what it waits on – whoev...
Definition scheduler.c:1063
srn_fiber_t * waiters_head
A private (In the sense that it only touched only by the current lock holder during unlock) pointer t...
Definition mutex.h:54
srn_fiber_t * link
Intrusive link threading this fiber onto one of the scheduler's singly-linked lists (the ready run qu...
Definition fiber.h:292