109 lines
2.6 KiB
Go
109 lines
2.6 KiB
Go
// Copyright 2020 The gVisor Authors.
|
|
//
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build checklocks
|
|
|
|
package sync
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"gvisor.dev/gvisor/pkg/goid"
|
|
)
|
|
|
|
// gLocks contains metadata about the locks held by a goroutine.
|
|
type gLocks struct {
|
|
locksHeld []unsafe.Pointer
|
|
}
|
|
|
|
// map[goid int]*gLocks
|
|
//
|
|
// Each key may only be written by the G with the goid it refers to.
|
|
//
|
|
// Note that entries are not evicted when a G exit, causing unbounded growth
|
|
// with new G creation / destruction. If this proves problematic, entries could
|
|
// be evicted when no locks are held at the expense of more allocations when
|
|
// taking top-level locks.
|
|
var locksHeld sync.Map
|
|
|
|
func getGLocks() *gLocks {
|
|
id := goid.Get()
|
|
|
|
var locks *gLocks
|
|
if l, ok := locksHeld.Load(id); ok {
|
|
locks = l.(*gLocks)
|
|
} else {
|
|
locks = &gLocks{
|
|
// Initialize space for a few locks.
|
|
locksHeld: make([]unsafe.Pointer, 0, 8),
|
|
}
|
|
locksHeld.Store(id, locks)
|
|
}
|
|
|
|
return locks
|
|
}
|
|
|
|
func noteLock(l unsafe.Pointer) {
|
|
locks := getGLocks()
|
|
|
|
for _, lock := range locks.locksHeld {
|
|
if lock == l {
|
|
panic(fmt.Sprintf("Deadlock on goroutine %d! Double lock of %p: %+v", goid.Get(), l, locks))
|
|
}
|
|
}
|
|
|
|
// Commit only after checking for panic conditions so that this lock
|
|
// isn't on the list if the above panic is recovered.
|
|
locks.locksHeld = append(locks.locksHeld, l)
|
|
}
|
|
|
|
func noteUnlock(l unsafe.Pointer) {
|
|
locks := getGLocks()
|
|
|
|
if len(locks.locksHeld) == 0 {
|
|
panic(fmt.Sprintf("Unlock of %p on goroutine %d without any locks held! All locks:\n%s", l, goid.Get(), dumpLocks()))
|
|
}
|
|
|
|
// Search backwards since callers are most likely to unlock in LIFO order.
|
|
length := len(locks.locksHeld)
|
|
for i := length - 1; i >= 0; i-- {
|
|
if l == locks.locksHeld[i] {
|
|
copy(locks.locksHeld[i:length-1], locks.locksHeld[i+1:length])
|
|
// Clear last entry to ensure addr can be GC'd.
|
|
locks.locksHeld[length-1] = nil
|
|
locks.locksHeld = locks.locksHeld[:length-1]
|
|
return
|
|
}
|
|
}
|
|
|
|
panic(fmt.Sprintf("Unlock of %p on goroutine %d without matching lock! All locks:\n%s", l, goid.Get(), dumpLocks()))
|
|
}
|
|
|
|
func dumpLocks() string {
|
|
var s strings.Builder
|
|
locksHeld.Range(func(key, value interface{}) bool {
|
|
goid := key.(int64)
|
|
locks := value.(*gLocks)
|
|
|
|
// N.B. accessing gLocks of another G is fundamentally racy.
|
|
|
|
fmt.Fprintf(&s, "goroutine %d:\n", goid)
|
|
if len(locks.locksHeld) == 0 {
|
|
fmt.Fprintf(&s, "\t<none>\n")
|
|
}
|
|
for _, lock := range locks.locksHeld {
|
|
fmt.Fprintf(&s, "\t%p\n", lock)
|
|
}
|
|
fmt.Fprintf(&s, "\n")
|
|
|
|
return true
|
|
})
|
|
|
|
return s.String()
|
|
}
|