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 }