ref: fbd0a767de45e5c5d9dfe6dc4a86c95a328bd22b
dir: /lib/thread/mutex+futex.myr/
use std use "atomic" use "futex" use "tls" use "types" pkg thread = type mutex = struct _state : ftxtag _owner : tid ;; const mkmtx : (-> mutex) const mtxlock : (mtx : mutex# -> void) const mtxtrylock : (mtx : mutex# -> bool) const mtxunlock : (mtx : mutex# -> void) pkglocal const mtxcontended : (mtx : mutex# -> void) ;; const Unlocked = 0 const Locked = 1 const Contended = 2 var nspin = 10 /* FIXME: pick a sane number, based on CPU count */ const mkmtx = { -> [._state = Unlocked, ._owner = -1] } const mtxlock = {mtx var c if mtx._owner == tid() std.fput(std.Err, "error: thread {} attempted to relock a mutex it already holds\n", tid()) std.suicide() ;; /* Uncontended case: we get an unlocked mutex, and we lock it. */ c = Locked for var i = 0; i < nspin; i++ c = xcas(&mtx._state, Unlocked, Locked) if c == Unlocked mtx._owner = tid() -> void ;; ;; /* Contended case: we set the lock state to Contended. This indicates that the lock is locked, and we potentially have threads waiting on it, which means that we will need to wake them up. */ if c == Locked c = xchg(&mtx._state, Contended) ;; while c != Unlocked ftxwait(&mtx._state, Contended, -1) c = xchg(&mtx._state, Contended) ;; mtx._owner = tid() } const mtxtrylock = {mtx if xcas(&mtx._state, Unlocked, Locked) == Unlocked mtx._owner = tid() -> true ;; -> false } const mtxunlock = {mtx /* Nonatomically loading mtx._owner may produce false negatives on weakly-ordered architectures but having to atomically store and load mtx._owner doesn't seem worth it. */ if mtx._owner != tid() std.fput(std.Err, "error: thread {} attempted to unlock a mutex last held by {}\n", tid(), mtx._owner) std.suicide() ;; mtx._owner = -1 /* Either the lock is contended or it's uncontended. Any other state is a bug. Uncontended case: If the mutex state is not contended, and we still are uncontended by the xchg() call, then it's safe to simply return; nobody was waiting for us. */ if xchg(&mtx._state, Unlocked) == Contended ftxwake(&mtx._state) ;; } const mtxcontended = {mtx if mtx._owner == tid() std.fput(std.Err, "error: thread {} attempted to relock a mutex it already holds\n", tid()) std.suicide() ;; while xchg(&mtx._state, Contended) != Unlocked ftxwait(&mtx._state, Contended, -1) ;; mtx._owner = tid() }