// 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() }