blob: 24f933ed110efca6255fdb85d254924e8d1551a6 [file] [log] [blame]
// 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()
}