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 "gvisor.dev/gvisor/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 }
110