github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sync/checklocks_on_unsafe.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 //go:build checklocks 7 // +build checklocks 8 9 package sync 10 11 import ( 12 "fmt" 13 "strings" 14 "sync" 15 "unsafe" 16 17 "github.com/nicocha30/gvisor-ligolo/pkg/goid" 18 ) 19 20 // gLocks contains metadata about the locks held by a goroutine. 21 type gLocks struct { 22 locksHeld []unsafe.Pointer 23 } 24 25 // map[goid int]*gLocks 26 // 27 // Each key may only be written by the G with the goid it refers to. 28 // 29 // Note that entries are not evicted when a G exit, causing unbounded growth 30 // with new G creation / destruction. If this proves problematic, entries could 31 // be evicted when no locks are held at the expense of more allocations when 32 // taking top-level locks. 33 var locksHeld sync.Map 34 35 func getGLocks() *gLocks { 36 id := goid.Get() 37 38 var locks *gLocks 39 if l, ok := locksHeld.Load(id); ok { 40 locks = l.(*gLocks) 41 } else { 42 locks = &gLocks{ 43 // Initialize space for a few locks. 44 locksHeld: make([]unsafe.Pointer, 0, 8), 45 } 46 locksHeld.Store(id, locks) 47 } 48 49 return locks 50 } 51 52 func noteLock(l unsafe.Pointer) { 53 locks := getGLocks() 54 55 for _, lock := range locks.locksHeld { 56 if lock == l { 57 panic(fmt.Sprintf("Deadlock on goroutine %d! Double lock of %p: %+v", goid.Get(), l, locks)) 58 } 59 } 60 61 // Commit only after checking for panic conditions so that this lock 62 // isn't on the list if the above panic is recovered. 63 locks.locksHeld = append(locks.locksHeld, l) 64 } 65 66 func noteUnlock(l unsafe.Pointer) { 67 locks := getGLocks() 68 69 if len(locks.locksHeld) == 0 { 70 panic(fmt.Sprintf("Unlock of %p on goroutine %d without any locks held! All locks:\n%s", l, goid.Get(), dumpLocks())) 71 } 72 73 // Search backwards since callers are most likely to unlock in LIFO order. 74 length := len(locks.locksHeld) 75 for i := length - 1; i >= 0; i-- { 76 if l == locks.locksHeld[i] { 77 copy(locks.locksHeld[i:length-1], locks.locksHeld[i+1:length]) 78 // Clear last entry to ensure addr can be GC'd. 79 locks.locksHeld[length-1] = nil 80 locks.locksHeld = locks.locksHeld[:length-1] 81 return 82 } 83 } 84 85 panic(fmt.Sprintf("Unlock of %p on goroutine %d without matching lock! All locks:\n%s", l, goid.Get(), dumpLocks())) 86 } 87 88 func dumpLocks() string { 89 var s strings.Builder 90 locksHeld.Range(func(key, value any) bool { 91 goid := key.(int64) 92 locks := value.(*gLocks) 93 94 // N.B. accessing gLocks of another G is fundamentally racy. 95 96 fmt.Fprintf(&s, "goroutine %d:\n", goid) 97 if len(locks.locksHeld) == 0 { 98 fmt.Fprintf(&s, "\t<none>\n") 99 } 100 for _, lock := range locks.locksHeld { 101 fmt.Fprintf(&s, "\t%p\n", lock) 102 } 103 fmt.Fprintf(&s, "\n") 104 105 return true 106 }) 107 108 return s.String() 109 }