github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/lockset/table.go (about)

     1  package lockset
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  	"strings"
     7  	"sync"
     8  )
     9  
    10  // TODO:
    11  //   * per goroutine table
    12  //   * across goroutine locks
    13  //   * replacement for sync package
    14  //   * reduce overhead
    15  //   * garbage collection of locks
    16  
    17  type Table struct {
    18  	mu sync.Mutex
    19  
    20  	root    Lock
    21  	holding []*Lock
    22  	callers []CallFrame
    23  }
    24  
    25  type CallFrame struct {
    26  	entries [4]uintptr
    27  	count   int
    28  }
    29  
    30  func (cf *CallFrame) String() string {
    31  	frames := runtime.CallersFrames(cf.entries[:cf.count])
    32  	var builder strings.Builder
    33  	for {
    34  		frame, more := frames.Next()
    35  		fmt.Fprintf(&builder, "    %s (%s:%d)\n", frame.Function, frame.File, frame.Line)
    36  		if !more {
    37  			break
    38  		}
    39  	}
    40  	return builder.String()
    41  }
    42  
    43  type Lock struct {
    44  	followers Set
    45  }
    46  
    47  func NewTable() *Table {
    48  	table := &Table{}
    49  	table.holding = append(table.holding, &table.root)
    50  	table.callers = append(table.callers, CallFrame{})
    51  	return table
    52  }
    53  
    54  type Inversion struct {
    55  	Held     *Lock
    56  	HeldCall CallFrame
    57  
    58  	Lock     *Lock
    59  	LockCall CallFrame
    60  }
    61  
    62  func (inv *Inversion) String() string {
    63  	return fmt.Sprintf("previously locked:\n%s\nlocking:\n%s", &inv.HeldCall, &inv.LockCall)
    64  }
    65  
    66  func (table *Table) Locked(lock *Lock) *Inversion {
    67  	table.mu.Lock()
    68  	defer table.mu.Unlock()
    69  
    70  	var frame CallFrame
    71  	frame.count = runtime.Callers(2, frame.entries[:])
    72  
    73  	var inversion *Inversion
    74  
    75  	for i, held := range table.holding {
    76  		if lock.followers.Contains(held) {
    77  			inversion = &Inversion{
    78  				Held:     held,
    79  				HeldCall: table.callers[i],
    80  
    81  				Lock:     lock,
    82  				LockCall: frame,
    83  			}
    84  			break
    85  		}
    86  		held.followers.Include(lock)
    87  	}
    88  
    89  	table.holding = append(table.holding, lock)
    90  	table.callers = append(table.callers, frame)
    91  
    92  	return inversion
    93  }
    94  
    95  func (table *Table) Unlocking(lock *Lock) bool {
    96  	table.mu.Lock()
    97  	defer table.mu.Unlock()
    98  
    99  	for i := len(table.holding) - 1; i >= 0; i-- {
   100  		if table.holding[i] == lock {
   101  			table.holding = append(table.holding[:i], table.holding[i+1:]...)
   102  			table.callers = append(table.callers[:i], table.callers[i+1:]...)
   103  			return true
   104  		}
   105  	}
   106  
   107  	return false
   108  }
   109  
   110  type Set struct{ list []*Lock }
   111  
   112  func (set *Set) Include(lock *Lock) {
   113  	if !set.Contains(lock) {
   114  		set.list = append(set.list, lock)
   115  	}
   116  }
   117  
   118  func (set *Set) Contains(lock *Lock) bool {
   119  	for _, k := range set.list {
   120  		if k == lock {
   121  			return true
   122  		}
   123  	}
   124  	return false
   125  }
   126  
   127  func (set *Set) Remove(lock *Lock) bool {
   128  	for i, k := range set.list {
   129  		if k == lock {
   130  			set.list = append(set.list[:i], set.list[i+1:]...)
   131  			return true
   132  		}
   133  	}
   134  	return false
   135  }