std.Thread.Mutex: restore the "Held" API

so that std.Thread.Mutex.Dummy can be used as a drop in replacement.
This commit is contained in:
Andrew Kelley 2021-01-14 21:28:22 -07:00
parent a9667b5a85
commit 9698ea3173
2 changed files with 98 additions and 82 deletions

View File

@ -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)
@ -82,25 +68,42 @@ 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;
}
pub fn acquire(self: *AtomicMutex) void {
switch (@atomicRmw(State, &self.state, .Xchg, .locked, .Acquire)) {
.unlocked => {},
else => |s| self.lockSlow(s),
) == null) {
return Held{ .mutex = m };
} else {
return null;
}
}
fn lockSlow(self: *AtomicMutex, current_state: State) void {
pub fn acquire(m: *AtomicMutex) Held {
switch (@atomicRmw(State, &m.state, .Xchg, .locked, .Acquire)) {
.unlocked => {},
else => |s| m.lockSlow(s),
}
return Held{ .mutex = m };
}
fn lockSlow(m: *AtomicMutex, current_state: State) void {
@setCold(true);
var new_state = current_state;
@ -108,7 +111,7 @@ pub const AtomicMutex = struct {
while (spin < 100) : (spin += 1) {
const state = @cmpxchgWeak(
State,
&self.state,
&m.state,
.unlocked,
new_state,
.Acquire,
@ -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,
@ -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,
))) {
@ -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,
@ -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
@ -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 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 acquire(self: *WindowsMutex) void {
AcquireSRWLockExclusive(&self.srwlock);
}
pub fn release(self: *WindowsMutex) void {
ReleaseSRWLockExclusive(&self.srwlock);
pub fn acquire(m: *WindowsMutex) Held {
windows.AcquireSRWLockExclusive(&m.srwlock);
return Held{ .mutex = m };
}
};

View File

@ -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
@ -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;