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  }