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  }