github.com/camlistore/go4@v0.0.0-20200104003542-c7e774b10ea0/syncutil/syncdebug/syncdebug.go (about) 1 /* 2 Copyright 2013 The Perkeep Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package syncdebug contains facilities for debugging synchronization 18 // problems. 19 package syncdebug // import "go4.org/syncutil/syncdebug" 20 21 import ( 22 "bytes" 23 "fmt" 24 "log" 25 "runtime" 26 "sync" 27 "sync/atomic" 28 "time" 29 30 "go4.org/strutil" 31 ) 32 33 // RWMutexTracker is a sync.RWMutex that tracks who owns the current 34 // exclusive lock. It's used for debugging deadlocks. 35 type RWMutexTracker struct { 36 mu sync.RWMutex 37 38 // Atomic counters for number waiting and having read and write locks. 39 nwaitr int32 40 nwaitw int32 41 nhaver int32 42 nhavew int32 // should always be 0 or 1 43 44 logOnce sync.Once 45 46 hmu sync.Mutex 47 holder []byte 48 holdr map[int64]bool // goroutines holding read lock 49 } 50 51 const stackBufSize = 16 << 20 52 53 var stackBuf = make(chan []byte, 8) 54 55 func getBuf() []byte { 56 select { 57 case b := <-stackBuf: 58 return b[:stackBufSize] 59 default: 60 return make([]byte, stackBufSize) 61 } 62 } 63 64 func putBuf(b []byte) { 65 select { 66 case stackBuf <- b: 67 default: 68 } 69 } 70 71 var goroutineSpace = []byte("goroutine ") 72 73 // GoroutineID returns the current goroutine's ID. 74 // Use of this function is almost always a terrible idea. 75 // It is also very slow. 76 // GoroutineID is intended only for debugging. 77 // In particular, it is used by syncutil. 78 func GoroutineID() int64 { 79 b := getBuf() 80 defer putBuf(b) 81 b = b[:runtime.Stack(b, false)] 82 // Parse the 4707 out of "goroutine 4707 [" 83 b = bytes.TrimPrefix(b, goroutineSpace) 84 i := bytes.IndexByte(b, ' ') 85 if i < 0 { 86 panic(fmt.Sprintf("No space found in %q", b)) 87 } 88 b = b[:i] 89 n, err := strutil.ParseUintBytes(b, 10, 64) 90 if err != nil { 91 panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) 92 } 93 return int64(n) 94 } 95 96 func (m *RWMutexTracker) startLogger() { 97 go func() { 98 var buf bytes.Buffer 99 for { 100 time.Sleep(1 * time.Second) 101 buf.Reset() 102 m.hmu.Lock() 103 for gid := range m.holdr { 104 fmt.Fprintf(&buf, " [%d]", gid) 105 } 106 m.hmu.Unlock() 107 log.Printf("Mutex %p: waitW %d haveW %d waitR %d haveR %d %s", 108 m, 109 atomic.LoadInt32(&m.nwaitw), 110 atomic.LoadInt32(&m.nhavew), 111 atomic.LoadInt32(&m.nwaitr), 112 atomic.LoadInt32(&m.nhaver), buf.Bytes()) 113 } 114 }() 115 } 116 117 func (m *RWMutexTracker) Lock() { 118 m.logOnce.Do(m.startLogger) 119 atomic.AddInt32(&m.nwaitw, 1) 120 m.mu.Lock() 121 atomic.AddInt32(&m.nwaitw, -1) 122 atomic.AddInt32(&m.nhavew, 1) 123 124 m.hmu.Lock() 125 defer m.hmu.Unlock() 126 if len(m.holder) == 0 { 127 m.holder = make([]byte, stackBufSize) 128 } 129 m.holder = m.holder[:runtime.Stack(m.holder[:stackBufSize], false)] 130 log.Printf("Lock at %s", string(m.holder)) 131 } 132 133 func (m *RWMutexTracker) Unlock() { 134 m.hmu.Lock() 135 m.holder = nil 136 m.hmu.Unlock() 137 138 atomic.AddInt32(&m.nhavew, -1) 139 m.mu.Unlock() 140 } 141 142 func (m *RWMutexTracker) RLock() { 143 m.logOnce.Do(m.startLogger) 144 atomic.AddInt32(&m.nwaitr, 1) 145 146 // Catch read-write-read lock. See if somebody (us? via 147 // another goroutine?) already has a read lock, and then 148 // somebody else is waiting to write, meaning our second read 149 // will deadlock. 150 if atomic.LoadInt32(&m.nhaver) > 0 && atomic.LoadInt32(&m.nwaitw) > 0 { 151 buf := getBuf() 152 buf = buf[:runtime.Stack(buf, false)] 153 log.Printf("Potential R-W-R deadlock at: %s", buf) 154 putBuf(buf) 155 } 156 157 m.mu.RLock() 158 atomic.AddInt32(&m.nwaitr, -1) 159 atomic.AddInt32(&m.nhaver, 1) 160 161 gid := GoroutineID() 162 m.hmu.Lock() 163 defer m.hmu.Unlock() 164 if m.holdr == nil { 165 m.holdr = make(map[int64]bool) 166 } 167 if m.holdr[gid] { 168 buf := getBuf() 169 buf = buf[:runtime.Stack(buf, false)] 170 log.Fatalf("Recursive call to RLock: %s", buf) 171 } 172 m.holdr[gid] = true 173 } 174 175 func stack() []byte { 176 buf := make([]byte, 1024) 177 return buf[:runtime.Stack(buf, false)] 178 } 179 180 func (m *RWMutexTracker) RUnlock() { 181 atomic.AddInt32(&m.nhaver, -1) 182 183 gid := GoroutineID() 184 m.hmu.Lock() 185 delete(m.holdr, gid) 186 m.hmu.Unlock() 187 188 m.mu.RUnlock() 189 } 190 191 // Holder returns the stack trace of the current exclusive lock holder's stack 192 // when it acquired the lock (with Lock). It returns the empty string if the lock 193 // is not currently held. 194 func (m *RWMutexTracker) Holder() string { 195 m.hmu.Lock() 196 defer m.hmu.Unlock() 197 return string(m.holder) 198 }