Serene Runtime 1.0.0-dev
C runtime for the Serene programming language
Loading...
Searching...
No Matches
mutex.h File Reference

A fiber blocking mutex. More...

#include <stdatomic.h>
#include "serene/rt/fiber.h"
Include dependency graph for mutex.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  srn_fiber_mutex_t

Typedefs

typedef struct srn_fiber_mutex_t srn_fiber_mutex_t

Functions

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.

Detailed Description

A fiber blocking mutex.

This lock suspends the calling fiber while the lock is held elsewhere and lets the worker run other fibers meanwhile. It is therefore only valid to call from INSIDE A RUNNING FIBER. Calling it from the worker thread, a signal handler, or any other non-fiber context is an error, since there would be no fiber to suspend.

The lock is not recursive, a fiber that locks a mutex it already holds deadlocks against itself. state carries no owner identity to detect it.

Waiters are served in FIFO order, so no waiter starves under sustained contention. We achieve that by having the state of a like to act as a stack and unlucky fibers add themselves to the stack and use srn_fiber_t.link to point to the next fiber in the stack. Then a lucky fiber, on the unlocking time, will walk the stack, reverse the order to make it a FIFO queue and populates the waiters_head of the lock. This way the next time a fiber will unlock the lock, it will hand the lock to the oldest waiting fiber.

Definition in file mutex.h.

Typedef Documentation

◆ srn_fiber_mutex_t

typedef struct srn_fiber_mutex_t srn_fiber_mutex_t

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