Skip to content

Commit

Permalink
std.Thread.Mutex: restore the "Held" API
Browse files Browse the repository at this point in the history
so that std.Thread.Mutex.Dummy can be used as a drop in replacement.
  • Loading branch information
andrewrk committed Jan 15, 2021
1 parent a9667b5 commit 9698ea3
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 78 deletions.
166 changes: 91 additions & 75 deletions lib/std/Thread/Mutex.zig
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,16 @@ const linux = os.linux;
const testing = std.testing;
const StaticResetEvent = std.thread.StaticResetEvent;

pub const Held = struct {
impl: *Impl,

pub fn release(held: Held) void {
held.impl.release();
}
};

/// Try to acquire the mutex without blocking. Returns null if
/// the mutex is unavailable. Otherwise returns Held. Call
/// release on Held.
pub fn tryAcquire(m: *Mutex) ?Held {
if (m.impl.tryAcquire()) {
return Held{ .impl = &m.impl };
} else {
return null;
}
/// Try to acquire the mutex without blocking. Returns `null` if the mutex is
/// unavailable. Otherwise returns `Held`. Call `release` on `Held`.
pub fn tryAcquire(m: *Mutex) ?Impl.Held {
return m.impl.tryAcquire();
}

/// Acquire the mutex. Deadlocks if the mutex is already
/// held by the calling thread.
pub fn acquire(m: *Mutex) Held {
m.impl.acquire();
return .{ .impl = &m.impl };
pub fn acquire(m: *Mutex) Impl.Held {
return m.impl.acquire();
}

const Impl = if (builtin.single_threaded)
Expand All @@ -82,33 +68,50 @@ pub const AtomicMutex = struct {
waiting,
};

pub fn tryAcquire(self: *AtomicMutex) bool {
return @cmpxchgStrong(
pub const Held = struct {
mutex: *AtomicMutex,

pub fn release(held: Held) void {
switch (@atomicRmw(State, &held.mutex.state, .Xchg, .unlocked, .Release)) {
.unlocked => unreachable,
.locked => {},
.waiting => held.mutex.unlockSlow(),
}
}
};

pub fn tryAcquire(m: *AtomicMutex) ?Held {
if (@cmpxchgStrong(
State,
&self.state,
&m.state,
.unlocked,
.locked,
.Acquire,
.Monotonic,
) == null;
) == null) {
return Held{ .mutex = m };
} else {
return null;
}
}

pub fn acquire(self: *AtomicMutex) void {
switch (@atomicRmw(State, &self.state, .Xchg, .locked, .Acquire)) {
pub fn acquire(m: *AtomicMutex) Held {
switch (@atomicRmw(State, &m.state, .Xchg, .locked, .Acquire)) {
.unlocked => {},
else => |s| self.lockSlow(s),
else => |s| m.lockSlow(s),
}
return Held{ .mutex = m };
}

fn lockSlow(self: *AtomicMutex, current_state: State) void {
fn lockSlow(m: *AtomicMutex, current_state: State) void {
@setCold(true);
var new_state = current_state;

var spin: u8 = 0;
while (spin < 100) : (spin += 1) {
const state = @cmpxchgWeak(
State,
&self.state,
&m.state,
.unlocked,
new_state,
.Acquire,
Expand All @@ -128,14 +131,14 @@ pub const AtomicMutex = struct {

new_state = .waiting;
while (true) {
switch (@atomicRmw(State, &self.state, .Xchg, new_state, .Acquire)) {
switch (@atomicRmw(State, &m.state, .Xchg, new_state, .Acquire)) {
.unlocked => return,
else => {},
}
switch (std.Target.current.os.tag) {
.linux => {
switch (linux.getErrno(linux.futex_wait(
@ptrCast(*const i32, &self.state),
@ptrCast(*const i32, &m.state),
linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAIT,
@enumToInt(new_state),
null,
Expand All @@ -151,21 +154,13 @@ pub const AtomicMutex = struct {
}
}

pub fn release(self: *AtomicMutex) void {
switch (@atomicRmw(State, &self.state, .Xchg, .unlocked, .Release)) {
.unlocked => unreachable,
.locked => {},
.waiting => self.unlockSlow(),
}
}

fn unlockSlow(self: *AtomicMutex) void {
fn unlockSlow(m: *AtomicMutex) void {
@setCold(true);

switch (std.Target.current.os.tag) {
.linux => {
switch (linux.getErrno(linux.futex_wake(
@ptrCast(*const i32, &self.state),
@ptrCast(*const i32, &m.state),
linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAKE,
1,
))) {
Expand All @@ -182,18 +177,36 @@ pub const AtomicMutex = struct {
pub const PthreadMutex = struct {
pthread_mutex: std.c.pthread_mutex_t = .{},

pub const Held = struct {
mutex: *PthreadMutex,

pub fn release(held: Held) void {
switch (std.c.pthread_mutex_unlock(&held.mutex.pthread_mutex)) {
0 => return,
std.c.EINVAL => unreachable,
std.c.EAGAIN => unreachable,
std.c.EPERM => unreachable,
else => unreachable,
}
}
};

/// Try to acquire the mutex without blocking. Returns null if
/// the mutex is unavailable. Otherwise returns Held. Call
/// release on Held.
pub fn tryAcquire(self: *PthreadMutex) bool {
return std.c.pthread_mutex_trylock(&self.pthread_mutex) == 0;
pub fn tryAcquire(m: *PthreadMutex) ?Held {
if (std.c.pthread_mutex_trylock(&m.pthread_mutex) == 0) {
return Held{ .mutex = m };
} else {
return null;
}
}

/// Acquire the mutex. Will deadlock if the mutex is already
/// held by the calling thread.
pub fn acquire(self: *PthreadMutex) void {
switch (std.c.pthread_mutex_lock(&self.pthread_mutex)) {
0 => return,
pub fn acquire(m: *PthreadMutex) Held {
switch (std.c.pthread_mutex_lock(&m.pthread_mutex)) {
0 => return Held{ .mutex = m },
std.c.EINVAL => unreachable,
std.c.EBUSY => unreachable,
std.c.EAGAIN => unreachable,
Expand All @@ -202,16 +215,6 @@ pub const PthreadMutex = struct {
else => unreachable,
}
}

pub fn release(self: *PthreadMutex) void {
switch (std.c.pthread_mutex_unlock(&self.pthread_mutex)) {
0 => return,
std.c.EINVAL => unreachable,
std.c.EAGAIN => unreachable,
std.c.EPERM => unreachable,
else => unreachable,
}
}
};

/// This has the sematics as `Mutex`, however it does not actually do any
Expand All @@ -221,43 +224,56 @@ pub const Dummy = struct {

const lock_init = if (std.debug.runtime_safety) false else {};

pub const Held = struct {
mutex: *Dummy,

pub fn release(held: Held) void {
if (std.debug.runtime_safety) {
held.mutex.lock = false;
}
}
};

/// Try to acquire the mutex without blocking. Returns null if
/// the mutex is unavailable. Otherwise returns Held. Call
/// release on Held.
pub fn tryAcquire(self: *Dummy) bool {
pub fn tryAcquire(m: *Dummy) ?Held {
if (std.debug.runtime_safety) {
if (self.lock) return false;
self.lock = true;
if (m.lock) return null;
m.lock = true;
}
return true;
return Held{ .mutex = m };
}

/// Acquire the mutex. Will deadlock if the mutex is already
/// held by the calling thread.
pub fn acquire(self: *Dummy) void {
return self.tryAcquire() orelse @panic("deadlock detected");
}

pub fn release(self: *Dummy) void {
if (std.debug.runtime_safety) {
self.mutex.lock = false;
}
pub fn acquire(m: *Dummy) Held {
return m.tryAcquire() orelse @panic("deadlock detected");
}
};

const WindowsMutex = struct {
srwlock: windows.SRWLOCK = windows.SRWLOCK_INIT,

pub fn tryAcquire(self: *WindowsMutex) bool {
return TryAcquireSRWLockExclusive(&self.srwlock) != system.FALSE;
}
pub const Held = struct {
mutex: *WindowsMutex,

pub fn acquire(self: *WindowsMutex) void {
AcquireSRWLockExclusive(&self.srwlock);
pub fn release(held: Held) void {
windows.ReleaseSRWLockExclusive(&held.mutex.srwlock);
}
};

pub fn tryAcquire(m: *WindowsMutex) ?Held {
if (windows.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE) {
return Held{ .mutex = m };
} else {
return null;
}
}

pub fn release(self: *WindowsMutex) void {
ReleaseSRWLockExclusive(&self.srwlock);
pub fn acquire(m: *WindowsMutex) Held {
windows.AcquireSRWLockExclusive(&m.srwlock);
return Held{ .mutex = m };
}
};

Expand Down
6 changes: 3 additions & 3 deletions lib/std/heap/general_purpose_allocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,12 @@ pub const Config = struct {

/// What type of mutex you'd like to use, for thread safety.
/// when specfied, the mutex type must have the same shape as `std.Thread.Mutex` and
/// `std.mutex.Dummy`, and have no required fields. Specifying this field causes
/// `std.Thread.Mutex.Dummy`, and have no required fields. Specifying this field causes
/// the `thread_safe` field to be ignored.
///
/// when null (default):
/// * the mutex type defaults to `std.Thread.Mutex` when thread_safe is enabled.
/// * the mutex type defaults to `std.mutex.Dummy` otherwise.
/// * the mutex type defaults to `std.Thread.Mutex.Dummy` otherwise.
MutexType: ?type = null,

/// This is a temporary debugging trick you can use to turn segfaults into more helpful
Expand Down Expand Up @@ -189,7 +189,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
else if (config.thread_safe)
std.Thread.Mutex{}
else
std.mutex.Dummy{};
std.Thread.Mutex.Dummy{};

const stack_n = config.stack_trace_frames;
const one_trace_size = @sizeOf(usize) * stack_n;
Expand Down

0 comments on commit 9698ea3

Please sign in to comment.