TL;DR: Can we check if a mutex is locked in Go? Yes, but not with a mutex API. Here’s a solution for use in debug builds.
Although you can Lock()
or Unlock()
a mutex, you can’t check whether it’s locked. While it is a reasonable omission (e.g., due to possible race conditions; see also Why can’t I check whether a mutex is locked?), having such functionality can still be useful for testing whether the software does what it is supposed to do.
In other words, it would be nice to have an AssertMutexLocked
function solely for debug builds, which could be used like this:
// this method should always be called with o.lock locked func (Object* o) someMethodImpl() { AssertMutexLocked(&o.lock) // (...) }
Having such a function would allow us to confirm the assumption that a given mutex is locked and find potential bugs when it’s added into an existing codebase. In fact, there was a GitHub issue about adding this exact functionality in the official Go repository (golang/go#1366), but it was closed with a WontFix status.
I also learned via the great grep.app project that many projects have similar preconditions about mutexes, such as google/gvisor, ghettovoice/gossip, vitessio/vitess, and others.
Now let’s implement the MutexLocked
(and other) functions.
Checking if a mutex is locked
To check whether a mutex is locked, we have to read its state. The sync.Mutex
structure contains two fields:
type Mutex struct { state int32 sema uint32 }
The state field’s bits correspond to the following flags (source):
const ( mutexLocked = 1 << iota // mutex is locked mutexWoken mutexStarving mutexWaiterShift = iota // (...)
So if a mutex is locked, its state field has the mutexLocked
(1) bit set. However, we can’t just access the state field directly from a Go program, because this field is not exported (its name does not start with a capital letter). Luckily, the field can still be accessed with Go reflection, which I used in the code below when I implemented the functions that allow us to check if a given sync.Mutex
or sync.RWMutex
is locked.
package main import ( "fmt" "reflect" "sync" ) const mutexLocked = 1 func MutexLocked(m *sync.Mutex) bool { state := reflect.ValueOf(m).Elem().FieldByName("state") return state.Int()&mutexLocked == mutexLocked } func RWMutexWriteLocked(rw *sync.RWMutex) bool { // RWMutex has a "w" sync.Mutex field for write lock state := reflect.ValueOf(rw).Elem().FieldByName("w").FieldByName("state") return state.Int()&mutexLocked == mutexLocked } func RWMutexReadLocked(rw *sync.RWMutex) bool { return reflect.ValueOf(rw).Elem().FieldByName("readerCount").Int() > 0 } func main() { m := sync.Mutex{} fmt.Println("m locked =", MutexLocked(&m)) m.Lock() fmt.Println("m locked =", MutexLocked(&m)) m.Unlock() fmt.Println("m locked =", MutexLocked(&m)) rw := sync.RWMutex{} fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw)) rw.Lock() fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw)) rw.Unlock() fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw)) rw.RLock() fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw)) rw.RLock() fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw)) rw.RUnlock() fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw)) rw.RUnlock() fmt.Println("rw write locked =", RWMutexWriteLocked(&rw), " read locked =", RWMutexReadLocked(&rw)) }
We can see this program’s output below:
m locked = false m locked = true m locked = false rw write locked = false read locked = false rw write locked = true read locked = false rw write locked = false read locked = false rw write locked = false read locked = true rw write locked = false read locked = true rw write locked = false read locked = true rw write locked = false read locked = false
And this can later be used to create AssertMutexLocked
and other functions. To that end, I’ve created a small library with these functions at trailofbits/go-mutexasserts—which enables the assertion checks only in builds with a debug
tag.
Note: Although there are other tools for detecting race conditions in Go, such as Go’s race detector or OnEdge from Trail of Bits, these tools will detect problematic situations only once they occur, and won’t allow you to assert whether the mutex precondition holds.
We’re always developing tools to help you work faster and smarter. Need help with your next project? Contact us!