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