1
0
mirror of https://github.com/golang/go synced 2024-11-17 13:54:46 -07:00

runtime: check held locks with staticlockranking

When lock ranking is enabled, we can now assert that lock preconditions
are met by checking that the caller holds required locks on function
entry.

This change adds the infrastructure to add assertions. Actual assertions
will be added for various locks in subsequent changes.

Some functions are protected by locks that are not directly accessible
in the function. In that case, we can use assertRankHeld to check that
any lock with the rank is held. This is less precise, but it avoids
requiring passing the lock into the functions.

Updates #40677

Change-Id: I843c6874867f975e90a063f087b6e2ffc147877b
Reviewed-on: https://go-review.googlesource.com/c/go/+/245484
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Michael Pratt <mpratt@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
Michael Pratt 2020-08-21 11:49:56 -04:00
parent 0d83fe68a8
commit 53c9b9588a
2 changed files with 75 additions and 3 deletions

View File

@ -44,3 +44,11 @@ func releaseLockRank(rank lockRank) {
//go:nosplit
func lockWithRankMayAcquire(l *mutex, rank lockRank) {
}
//go:nosplit
func assertLockHeld(l *mutex) {
}
//go:nosplit
func assertRankHeld(r lockRank) {
}

View File

@ -86,6 +86,18 @@ func lockWithRank(l *mutex, rank lockRank) {
})
}
//go:systemstack
func printHeldLocks(gp *g) {
if gp.m.locksHeldLen == 0 {
println("<none>")
return
}
for j, held := range gp.m.locksHeld[:gp.m.locksHeldLen] {
println(j, ":", held.rank.String(), held.rank, unsafe.Pointer(gp.m.locksHeld[j].lockAddr))
}
}
// acquireLockRank acquires a rank which is not associated with a mutex lock
//go:nosplit
func acquireLockRank(rank lockRank) {
@ -109,6 +121,8 @@ func acquireLockRank(rank lockRank) {
// checkRanks checks if goroutine g, which has mostly recently acquired a lock
// with rank 'prevRank', can now acquire a lock with rank 'rank'.
//
//go:systemstack
func checkRanks(gp *g, prevRank, rank lockRank) {
rankOK := false
if rank < prevRank {
@ -135,9 +149,7 @@ func checkRanks(gp *g, prevRank, rank lockRank) {
if !rankOK {
printlock()
println(gp.m.procid, " ======")
for j, held := range gp.m.locksHeld[:gp.m.locksHeldLen] {
println(j, ":", held.rank.String(), held.rank, unsafe.Pointer(gp.m.locksHeld[j].lockAddr))
}
printHeldLocks(gp)
throw("lock ordering problem")
}
}
@ -212,3 +224,55 @@ func lockWithRankMayAcquire(l *mutex, rank lockRank) {
gp.m.locksHeldLen--
})
}
//go:systemstack
func checkLockHeld(gp *g, l *mutex) bool {
for i := gp.m.locksHeldLen - 1; i >= 0; i-- {
if gp.m.locksHeld[i].lockAddr == uintptr(unsafe.Pointer(l)) {
return true
}
}
return false
}
// assertLockHeld throws if l is not held by the caller.
//
// nosplit to ensure it can be called in as many contexts as possible.
//go:nosplit
func assertLockHeld(l *mutex) {
gp := getg()
systemstack(func() {
held := checkLockHeld(gp, l)
if !held {
printlock()
print("caller requires lock ", l, " (rank ", l.rank.String(), "), holding:\n")
printHeldLocks(gp)
throw("not holding required lock!")
}
})
}
// assertRankHeld throws if a mutex with rank r is not held by the caller.
//
// This is less precise than assertLockHeld, but can be used in places where a
// pointer to the exact mutex is not available.
//
// nosplit to ensure it can be called in as many contexts as possible.
//go:nosplit
func assertRankHeld(r lockRank) {
gp := getg()
systemstack(func() {
for i := gp.m.locksHeldLen - 1; i >= 0; i-- {
if gp.m.locksHeld[i].rank == r {
return
}
}
printlock()
print("caller requires lock with rank ", r.String(), "), holding:\n")
printHeldLocks(gp)
throw("not holding required lock!")
})
}