github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sync/locking/lockdep.go (about)

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