checklocks_on_unsafe.go raw

   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