github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/sync/lock.go (about)

     1  package sync
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"runtime"
     7  	"sync"
     8  	"time"
     9  )
    10  
    11  // RWMutex provides locking functions, and an ability to detect and remove
    12  // deadlocks.
    13  type RWMutex struct {
    14  	openLocks        map[int]lockInfo
    15  	openLocksCounter int
    16  	openLocksMutex   sync.Mutex
    17  
    18  	callDepth   int
    19  	maxLockTime time.Duration
    20  
    21  	mu sync.RWMutex
    22  }
    23  
    24  // lockInfo contains information about when and how a lock call was made.
    25  type lockInfo struct {
    26  	// When the lock was called.
    27  	lockTime time.Time
    28  
    29  	// Whether it was a RLock or a Lock.
    30  	read bool
    31  
    32  	// Call stack of the caller.
    33  	callingFiles []string
    34  	callingLines []int
    35  }
    36  
    37  // New takes a maxLockTime and returns a lock. The lock will never stay locked
    38  // for more than maxLockTime, instead printing an error and unlocking after
    39  // maxLockTime has passed.
    40  func New(maxLockTime time.Duration, callDepth int) *RWMutex {
    41  	rwm := &RWMutex{
    42  		openLocks:   make(map[int]lockInfo),
    43  		maxLockTime: maxLockTime,
    44  		callDepth:   callDepth,
    45  	}
    46  
    47  	go rwm.threadedDeadlockFinder()
    48  
    49  	return rwm
    50  }
    51  
    52  // threadedDeadlockFinder occasionally freezes the mutexes and scans all open mutexes,
    53  // reporting any that have exceeded their time limit.
    54  func (rwm *RWMutex) threadedDeadlockFinder() {
    55  	for {
    56  		rwm.openLocksMutex.Lock()
    57  		for id, info := range rwm.openLocks {
    58  			// Check if the lock has been held for longer than 'maxLockTime'.
    59  			if time.Now().Sub(info.lockTime) > rwm.maxLockTime {
    60  				str := fmt.Sprintf("A lock was held for too long, id '%v'. Call stack:\n", id)
    61  				for i := 0; i <= rwm.callDepth; i++ {
    62  					str += fmt.Sprintf("\tFile: '%v:%v'\n", info.callingFiles[i], info.callingLines[i])
    63  				}
    64  				os.Stderr.WriteString(str)
    65  				os.Stderr.Sync()
    66  
    67  				// Undo the deadlock and delete the entry from the map.
    68  				if info.read {
    69  					rwm.mu.RUnlock()
    70  				} else {
    71  					rwm.mu.Unlock()
    72  				}
    73  				delete(rwm.openLocks, id)
    74  			}
    75  		}
    76  		rwm.openLocksMutex.Unlock()
    77  
    78  		time.Sleep(rwm.maxLockTime)
    79  	}
    80  }
    81  
    82  // safeLock is the generic function for doing safe locking. If the read flag is
    83  // set, then a readlock will be used, otherwise a lock will be used.
    84  func (rwm *RWMutex) safeLock(read bool) int {
    85  	// Get the call stack.
    86  	var li lockInfo
    87  	li.read = read
    88  	li.callingFiles = make([]string, rwm.callDepth+1)
    89  	li.callingLines = make([]int, rwm.callDepth+1)
    90  	for i := 0; i <= rwm.callDepth; i++ {
    91  		_, li.callingFiles[i], li.callingLines[i], _ = runtime.Caller(2 + i)
    92  	}
    93  
    94  	// Lock the mutex.
    95  	if read {
    96  		rwm.mu.RLock()
    97  	} else {
    98  		rwm.mu.Lock()
    99  	}
   100  
   101  	// Safely register that a lock has been triggered.
   102  	rwm.openLocksMutex.Lock()
   103  	li.lockTime = time.Now()
   104  	id := rwm.openLocksCounter
   105  	rwm.openLocks[id] = li
   106  	rwm.openLocksCounter++
   107  	rwm.openLocksMutex.Unlock()
   108  
   109  	return id
   110  }
   111  
   112  // safeUnlock is the generic function for doing safe unlocking. If the lock had
   113  // to be removed because a deadlock was detected, an error is printed.
   114  func (rwm *RWMutex) safeUnlock(read bool, id int) {
   115  	rwm.openLocksMutex.Lock()
   116  	defer rwm.openLocksMutex.Unlock()
   117  
   118  	// Check if a deadlock has been detected and fixed manually.
   119  	_, exists := rwm.openLocks[id]
   120  	if !exists {
   121  		// Get the call stack.
   122  		callingFiles := make([]string, rwm.callDepth+1)
   123  		callingLines := make([]int, rwm.callDepth+1)
   124  		for i := 0; i <= rwm.callDepth; i++ {
   125  			_, callingFiles[i], callingLines[i], _ = runtime.Caller(2 + i)
   126  		}
   127  
   128  		fmt.Printf("A lock was held until deadlock, subsequent call to unlock failed. id '%v'. Call stack:\n", id)
   129  		for i := 0; i <= rwm.callDepth; i++ {
   130  			fmt.Printf("\tFile: '%v:%v'\n", callingFiles[i], callingLines[i])
   131  		}
   132  		return
   133  	}
   134  
   135  	// Remove the lock and delete the entry from the map.
   136  	if read {
   137  		rwm.mu.RUnlock()
   138  	} else {
   139  		rwm.mu.Unlock()
   140  	}
   141  	delete(rwm.openLocks, id)
   142  }
   143  
   144  // RLock will read lock the RWMutex. The return value must be used as input
   145  // when calling RUnlock.
   146  func (rwm *RWMutex) RLock() int {
   147  	return rwm.safeLock(true)
   148  }
   149  
   150  // RUnlock will read unlock the RWMutex. The return value of calling RLock must
   151  // be used as input.
   152  func (rwm *RWMutex) RUnlock(id int) {
   153  	rwm.safeUnlock(true, id)
   154  }
   155  
   156  // Lock will lock the RWMutex. The return value must be used as input when
   157  // calling RUnlock.
   158  func (rwm *RWMutex) Lock() int {
   159  	return rwm.safeLock(false)
   160  }
   161  
   162  // Unlock will unlock the RWMutex. The return value of calling Lock must be
   163  // used as input.
   164  func (rwm *RWMutex) Unlock(id int) {
   165  	rwm.safeUnlock(false, id)
   166  }