Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
mutex.c File Reference
Include dependency graph for mutex.c:

Go to the source code of this file.

Macros

#define SRN_MUTEX_HELD_WITH_NO_WAITER   (&srn_mutex_held_marker)

Functions

static bool srn_fiber_mutex_suspend (srn_fiber_t *self, void *arg)
 Suspend commit function for a contended srn_fiber_mutex_lock.
void srn_fiber_mutex_init (srn_fiber_mutex_t *m)
 Initialise m to the unlocked state.
void srn_fiber_mutex_lock (srn_fiber_mutex_t *m)
 Acquire m.
void srn_fiber_mutex_unlock (srn_fiber_mutex_t *m)
 Release m.

Variables

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.

Macro Definition Documentation

◆ SRN_MUTEX_HELD_WITH_NO_WAITER

#define SRN_MUTEX_HELD_WITH_NO_WAITER   (&srn_mutex_held_marker)

Definition at line 30 of file mutex.c.

Function Documentation

◆ srn_fiber_mutex_init()

void srn_fiber_mutex_init ( srn_fiber_mutex_t * m)

Initialise m to the unlocked state.

Definition at line 76 of file mutex.c.

76 {
77 atomic_init(&m->state, nullptr);
78 m->waiters_head = nullptr;
79}
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
Here is the caller graph for this function:

◆ srn_fiber_mutex_lock()

void srn_fiber_mutex_lock ( srn_fiber_mutex_t * m)

Acquire m.

If the lock is free it is taken at once, otherwise the calling fiber is suspended and resumes owning the lock, handed off directly by the unlocking fiber. Must be called from a running fiber.

Definition at line 81 of file mutex.c.

81 {
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}
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
#define SRN_MUTEX_HELD_WITH_NO_WAITER
Definition mutex.c:30
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
Here is the call graph for this function:
Here is the caller graph for this function:

◆ srn_fiber_mutex_suspend()

bool srn_fiber_mutex_suspend ( srn_fiber_t * self,
void * arg )
static

Suspend commit function for a contended srn_fiber_mutex_lock.

Since the worker routine runs it once self is off its stack (see srn_fiber_suspend), so pushing self onto the waiter stack cannot race the running fiber.

If the lock has gone free since the fast path failed, it takes the lock and returns false to resume self at once. Otherwise it pushes self onto the incoming stack and returns true to stay suspended.

Definition at line 41 of file mutex.c.

41 {
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}
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
Here is the caller graph for this function:

◆ srn_fiber_mutex_unlock()

void srn_fiber_mutex_unlock ( srn_fiber_mutex_t * m)

Release m.

If any fibers are waiting, the longest waiting one is handed the lock and made ready, otherwise the lock is dropped. Must be called by the fiber that currently holds m.

Definition at line 99 of file mutex.c.

99 {
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}
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
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ srn_mutex_held_marker

srn_fiber_t srn_mutex_held_marker
static

Marker stored in srn_fiber_mutex_t.state to mean the lock is held with no waiter queued.

Only its address matters, its fields are never touched, so it only has to be a non-null srn_fiber_t pointer distinct from every real fiber. It also sits at the bottom of the incoming stack and terminates it, so a waiter push and the drain walk need no null special case.

Definition at line 29 of file mutex.c.