shithub: mc

ref: d7740736da8f2ca830d558cdedd89266b9019375
dir: /lib/thread/mutex+futex.myr/

View raw version
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()
}