1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
// Copyright 2022 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build lockdep
// +build lockdep
package locking
import (
"fmt"
"reflect"
"strings"
"gvisor.dev/gvisor/pkg/goid"
"gvisor.dev/gvisor/pkg/log"
)
// NewMutexClass allocates a new mutex class.
func NewMutexClass(t reflect.Type, lockNames []string) *MutexClass {
c := &MutexClass{
typ: t,
nestedLockNames: lockNames,
nestedLockClasses: make([]*MutexClass, len(lockNames)),
}
for i := range lockNames {
c.nestedLockClasses[i] = NewMutexClass(t, nil)
c.nestedLockClasses[i].lockName = lockNames[i]
}
return c
}
// MutexClass describes dependencies of a specific class.
type MutexClass struct {
// The type of the mutex.
typ reflect.Type
// Name of the nested lock of the above type.
lockName string
// ancestors are locks that are locked before the current class.
ancestors ancestorsAtomicPtrMap
// nestedLockNames is a list of names for nested locks which are considered difference instances
// of the same lock class.
nestedLockNames []string
// namedLockClasses is a list of MutexClass instances of the same mutex class, but that are
// considered OK to lock simultaneously with each other, as well as with this mutex class.
// This is used for nested locking, where multiple instances of the same lock class are used
// simultaneously.
// Maps one-to-one with nestedLockNames.
nestedLockClasses []*MutexClass
}
func (m *MutexClass) String() string {
if m.lockName == "" {
return m.typ.String()
}
return fmt.Sprintf("%s[%s]", m.typ.String(), m.lockName)
}
type goroutineLocks map[*MutexClass]bool
var routineLocks goroutineLocksAtomicPtrMap
// maxChainLen is the maximum length of a lock chain.
const maxChainLen = 32
// checkLock checks that class isn't in the ancestors of prevClass.
func checkLock(class *MutexClass, prevClass *MutexClass, chain []*MutexClass) {
chain = append(chain, prevClass)
if len(chain) >= maxChainLen {
// It can be a race condition with another thread that added
// the lock to the graph but don't complete the validation.
var b strings.Builder
fmt.Fprintf(&b, "WARNING: The maximum lock depth has been reached: %s", chain[0])
for i := 1; i < len(chain); i++ {
fmt.Fprintf(&b, "-> %s", chain[i])
}
log.Warningf("%s", b.String())
return
}
if c := prevClass.ancestors.Load(class); c != nil {
var b strings.Builder
fmt.Fprintf(&b, "WARNING: circular locking detected: %s -> %s:\n%s\n",
chain[0], class, log.LocalStack(3))
fmt.Fprintf(&b, "known lock chain: ")
c := class
for i := len(chain) - 1; i >= 0; i-- {
fmt.Fprintf(&b, "%s -> ", c)
c = chain[i]
}
fmt.Fprintf(&b, "%s\n", chain[0])
c = class
for i := len(chain) - 1; i >= 0; i-- {
fmt.Fprintf(&b, "\n====== %s -> %s =====\n%s",
c, chain[i], *chain[i].ancestors.Load(c))
c = chain[i]
}
panic(b.String())
}
prevClass.ancestors.RangeRepeatable(func(parentClass *MutexClass, stacks *string) bool {
// The recursion is fine here. If it fails, you need to reduce
// a number of nested locks.
checkLock(class, parentClass, chain)
return true
})
}
// AddGLock records a lock to the current goroutine and updates dependencies.
func AddGLock(class *MutexClass, lockNameIndex int) {
gid := goid.Get()
if lockNameIndex != -1 {
class = class.nestedLockClasses[lockNameIndex]
}
currentLocks := routineLocks.Load(gid)
if currentLocks == nil {
locks := goroutineLocks(make(map[*MutexClass]bool))
locks[class] = true
routineLocks.Store(gid, &locks)
return
}
if (*currentLocks)[class] {
panic(fmt.Sprintf("nested locking: %s:\n%s", class, log.LocalStack(2)))
}
(*currentLocks)[class] = true
// Check dependencies and add locked mutexes to the ancestors list.
for prevClass := range *currentLocks {
if prevClass == class {
continue
}
checkLock(class, prevClass, nil)
if c := class.ancestors.Load(prevClass); c == nil {
stacks := string(log.LocalStack(2))
class.ancestors.Store(prevClass, &stacks)
}
}
}
// DelGLock deletes a lock from the current goroutine.
func DelGLock(class *MutexClass, lockNameIndex int) {
if lockNameIndex != -1 {
class = class.nestedLockClasses[lockNameIndex]
}
gid := goid.Get()
currentLocks := routineLocks.Load(gid)
if currentLocks == nil {
panic("the current goroutine doesn't have locks")
}
if _, ok := (*currentLocks)[class]; !ok {
var b strings.Builder
fmt.Fprintf(&b, "Lock not held: %s:\n", class)
fmt.Fprintf(&b, "Current stack:\n%s\n", string(log.LocalStack(2)))
fmt.Fprintf(&b, "Current locks:\n")
for c := range *currentLocks {
heldToClass := class.ancestors.Load(c)
classToHeld := c.ancestors.Load(class)
if heldToClass == nil && classToHeld == nil {
fmt.Fprintf(&b, "\t- Holding lock: %s (no dependency to/from %s found)\n", c, class)
} else if heldToClass != nil && classToHeld != nil {
fmt.Fprintf(&b, "\t- Holding lock: %s (mutual dependency with %s found, this should never happen)\n", c, class)
} else if heldToClass != nil && classToHeld == nil {
fmt.Fprintf(&b, "\t- Holding lock: %s (dependency: %s -> %s)\n", c, c, class)
fmt.Fprintf(&b, "%s\n\n", *heldToClass)
} else if heldToClass == nil && classToHeld != nil {
fmt.Fprintf(&b, "\t- Holding lock: %s (dependency: %s -> %s)\n", c, class, c)
fmt.Fprintf(&b, "%s\n\n", *classToHeld)
}
}
fmt.Fprintf(&b, "** End of locks held **\n")
panic(b.String())
}
delete(*currentLocks, class)
if len(*currentLocks) == 0 {
routineLocks.Store(gid, nil)
}
}
|