github.com/Cloud-Foundations/Dominator@v0.3.4/lib/lockwatcher/impl.go (about)

     1  package lockwatcher
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"runtime"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/Cloud-Foundations/Dominator/lib/backoffdelay"
    11  )
    12  
    13  var (
    14  	dumpedMutex sync.Mutex
    15  	dumpedStack bool
    16  )
    17  
    18  func newLockWatcher(lock sync.Locker, options LockWatcherOptions) *LockWatcher {
    19  	if options.CheckInterval < time.Second {
    20  		options.CheckInterval = 5 * time.Second
    21  	}
    22  	if options.LogTimeout < time.Millisecond {
    23  		options.LogTimeout = time.Second
    24  	}
    25  	if options.MaximumTryInterval > options.LogTimeout>>5 {
    26  		options.MaximumTryInterval = options.LogTimeout >> 5
    27  	}
    28  	if options.MinimumTryInterval > options.LogTimeout>>8 {
    29  		options.MinimumTryInterval = options.LogTimeout >> 8
    30  	}
    31  	rstopChannel := make(chan struct{}, 1)
    32  	stopChannel := make(chan struct{}, 1)
    33  	lockWatcher := &LockWatcher{
    34  		LockWatcherOptions: options,
    35  		lock:               lock,
    36  		rstopChannel:       rstopChannel,
    37  		stopChannel:        stopChannel,
    38  	}
    39  	if _, ok := lock.(RWLock); ok {
    40  		go lockWatcher.loop(lockWatcher.rcheck, rstopChannel)
    41  		go lockWatcher.loop(lockWatcher.wcheck, stopChannel)
    42  	} else {
    43  		go lockWatcher.loop(lockWatcher.check, stopChannel)
    44  	}
    45  	return lockWatcher
    46  }
    47  
    48  func (lw *LockWatcher) logTimeout(lockType string) {
    49  	dumpedMutex.Lock()
    50  	defer dumpedMutex.Unlock()
    51  	if dumpedStack {
    52  		lw.Logger.Printf("timed out getting %slock\n", lockType)
    53  		return
    54  	}
    55  	dumpedStack = true
    56  	logLine := fmt.Sprintf(
    57  		"timed out getting %slock, first stack trace follows:\n",
    58  		lockType)
    59  	buffer := make([]byte, 1<<20)
    60  	copy(buffer, logLine)
    61  	nBytes := runtime.Stack(buffer[len(logLine):], true)
    62  	lw.Logger.Print(string(buffer[:len(logLine)+nBytes]))
    63  }
    64  
    65  func (lw *LockWatcher) check() {
    66  	lockedChannel := make(chan struct{}, 1)
    67  	timer := time.NewTimer(lw.LogTimeout)
    68  	go func() {
    69  		lw.lock.Lock()
    70  		lockedChannel <- struct{}{}
    71  		if lw.Function != nil {
    72  			lw.Function()
    73  		}
    74  		lw.lock.Unlock()
    75  	}()
    76  	select {
    77  	case <-lockedChannel:
    78  		if !timer.Stop() {
    79  			<-timer.C
    80  		}
    81  		return
    82  	case <-timer.C:
    83  	}
    84  	lw.incrementNumLockTimeouts()
    85  	lw.logTimeout("")
    86  	<-lockedChannel
    87  	lw.clearLockWaiting()
    88  	lw.Logger.Println("eventually got lock")
    89  }
    90  
    91  func (lw *LockWatcher) clearLockWaiting() {
    92  	lw.statsMutex.Lock()
    93  	defer lw.statsMutex.Unlock()
    94  	lw.stats.WaitingForLock = false
    95  }
    96  
    97  func (lw *LockWatcher) clearRLockWaiting() {
    98  	lw.statsMutex.Lock()
    99  	defer lw.statsMutex.Unlock()
   100  	lw.stats.WaitingForRLock = false
   101  }
   102  
   103  func (lw *LockWatcher) clearWLockWaiting() {
   104  	lw.statsMutex.Lock()
   105  	defer lw.statsMutex.Unlock()
   106  	lw.stats.WaitingForWLock = false
   107  }
   108  
   109  func (lw *LockWatcher) getStats() LockWatcherStats {
   110  	lw.statsMutex.RLock()
   111  	defer lw.statsMutex.RUnlock()
   112  	return lw.stats
   113  }
   114  
   115  func (lw *LockWatcher) incrementNumLockTimeouts() {
   116  	lw.statsMutex.Lock()
   117  	defer lw.statsMutex.Unlock()
   118  	lw.stats.NumLockTimeouts++
   119  	lw.stats.WaitingForLock = true
   120  }
   121  
   122  func (lw *LockWatcher) incrementNumRLockTimeouts() {
   123  	lw.statsMutex.Lock()
   124  	defer lw.statsMutex.Unlock()
   125  	lw.stats.NumRLockTimeouts++
   126  	lw.stats.WaitingForRLock = true
   127  }
   128  
   129  func (lw *LockWatcher) incrementNumWLockTimeouts() {
   130  	lw.statsMutex.Lock()
   131  	defer lw.statsMutex.Unlock()
   132  	lw.stats.NumWLockTimeouts++
   133  	lw.stats.WaitingForWLock = true
   134  }
   135  
   136  func (lw *LockWatcher) loop(check func(), stopChannel <-chan struct{}) {
   137  	for {
   138  		timer := time.NewTimer(lw.CheckInterval)
   139  		select {
   140  		case <-stopChannel:
   141  			if !timer.Stop() {
   142  				<-timer.C
   143  			}
   144  			return
   145  		case <-timer.C:
   146  			check()
   147  		}
   148  	}
   149  }
   150  
   151  func (lw *LockWatcher) rcheck() {
   152  	lw.blockReadLock.Lock()
   153  	lockedChannel := make(chan struct{}, 1)
   154  	timer := time.NewTimer(lw.LogTimeout)
   155  	rwlock := lw.lock.(RWLock)
   156  	go func() {
   157  		rwlock.RLock()
   158  		lockedChannel <- struct{}{}
   159  		if lw.RFunction != nil {
   160  			lw.RFunction()
   161  		}
   162  		rwlock.RUnlock()
   163  	}()
   164  	select {
   165  	case <-lockedChannel:
   166  		lw.blockReadLock.Unlock()
   167  		if !timer.Stop() {
   168  			<-timer.C
   169  		}
   170  		return
   171  	case <-timer.C:
   172  	}
   173  	lw.blockReadLock.Unlock()
   174  	lw.incrementNumRLockTimeouts()
   175  	lw.logTimeout("r")
   176  	<-lockedChannel
   177  	lw.clearRLockWaiting()
   178  	lw.Logger.Println("eventually got rlock")
   179  }
   180  
   181  // wcheck initially uses a TryLock() to grab a write lock, so as to not block
   182  // future read lockers. After a while it falls back to Lock() in case there is
   183  // a fast loop grabbing and releasing the read lock which would starve out the
   184  // TryLock().
   185  func (lw *LockWatcher) wcheck() {
   186  	rwlock := lw.lock.(RWLock)
   187  	sleeper := backoffdelay.NewExponential(lw.MinimumTryInterval,
   188  		lw.MaximumTryInterval,
   189  		1)
   190  	timeoutHalfTime := time.Now().Add(lw.LogTimeout >> 1)
   191  	for ; time.Until(timeoutHalfTime) > 0; sleeper.Sleep() {
   192  		if rwlock.TryLock() {
   193  			if lw.Function != nil {
   194  				lw.Function()
   195  			}
   196  			rwlock.Unlock()
   197  			return
   198  		}
   199  	}
   200  	lockedChannel := make(chan struct{}, 1)
   201  	timer := time.NewTimer(lw.LogTimeout >> 1)
   202  	go func() {
   203  		// We're about to block read lockers, so stop the checker so that we
   204  		// don't generate noise logs.
   205  		lw.blockReadLock.Lock()
   206  		rwlock.Lock()
   207  		lockedChannel <- struct{}{}
   208  		if lw.Function != nil {
   209  			lw.Function()
   210  		}
   211  		rwlock.Unlock()
   212  		lw.blockReadLock.Unlock()
   213  	}()
   214  	select {
   215  	case <-lockedChannel:
   216  		if !timer.Stop() {
   217  			<-timer.C
   218  		}
   219  		return
   220  	case <-timer.C:
   221  	}
   222  	lw.incrementNumWLockTimeouts()
   223  	lw.logTimeout("w")
   224  	<-lockedChannel
   225  	lw.clearWLockWaiting()
   226  	lw.Logger.Println("eventually got wlock")
   227  }
   228  
   229  func (lw *LockWatcher) stop() {
   230  	select {
   231  	case lw.rstopChannel <- struct{}{}:
   232  	default:
   233  	}
   234  	select {
   235  	case lw.stopChannel <- struct{}{}:
   236  	default:
   237  	}
   238  }
   239  
   240  func (lw *LockWatcher) writeHtml(writer io.Writer,
   241  	prefix string) (bool, error) {
   242  	stats := lw.GetStats()
   243  	if stats.NumLockTimeouts > 0 {
   244  		if stats.WaitingForLock {
   245  			fmt.Fprintf(writer, "<font color=\"red\">")
   246  		} else {
   247  			fmt.Fprintf(writer, "<font color=\"salmon\">")
   248  		}
   249  		fmt.Fprintf(writer, "%sLock timeouts: %d",
   250  			prefix, stats.NumLockTimeouts)
   251  		if stats.WaitingForLock {
   252  			fmt.Fprintf(writer, " still waiting for lock")
   253  		}
   254  		_, err := fmt.Fprintln(writer, "</font><br>\n")
   255  		return true, err
   256  	}
   257  	if stats.NumRLockTimeouts < 1 && stats.NumWLockTimeouts < 1 {
   258  		return false, nil
   259  	}
   260  	if stats.WaitingForRLock || stats.WaitingForWLock {
   261  		fmt.Fprintf(writer, "<font color=\"red\">")
   262  	} else {
   263  		fmt.Fprintf(writer, "<font color=\"salmon\">")
   264  	}
   265  	if stats.NumRLockTimeouts > 0 {
   266  		fmt.Fprintf(writer,
   267  			"%sRLock timeouts: %d", prefix, stats.NumRLockTimeouts)
   268  		if stats.WaitingForRLock {
   269  			fmt.Fprintf(writer, ", still waiting for RLock")
   270  		}
   271  		prefix = ", "
   272  	}
   273  	if stats.NumWLockTimeouts > 0 {
   274  		fmt.Fprintf(writer,
   275  			"%sWLock timeouts: %d", prefix, stats.NumWLockTimeouts)
   276  		if stats.WaitingForWLock {
   277  			fmt.Fprintf(writer, ", still waiting for WLock")
   278  		}
   279  	}
   280  	_, err := fmt.Fprintln(writer, "</font><br>")
   281  	return true, err
   282  }