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  }