|
|
@@ -0,0 +1,144 @@
|
|
|
+// Copyright (C) 2015 The Syncthing Authors.
|
|
|
+//
|
|
|
+// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
+// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
+
|
|
|
+package sync
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "path/filepath"
|
|
|
+ "runtime"
|
|
|
+ "strings"
|
|
|
+ "sync"
|
|
|
+ "sync/atomic"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+type Mutex interface {
|
|
|
+ Lock()
|
|
|
+ Unlock()
|
|
|
+}
|
|
|
+
|
|
|
+type RWMutex interface {
|
|
|
+ Mutex
|
|
|
+ RLock()
|
|
|
+ RUnlock()
|
|
|
+}
|
|
|
+
|
|
|
+type WaitGroup interface {
|
|
|
+ Add(int)
|
|
|
+ Done()
|
|
|
+ Wait()
|
|
|
+}
|
|
|
+
|
|
|
+func NewMutex() Mutex {
|
|
|
+ if debug {
|
|
|
+ return &loggedMutex{}
|
|
|
+ }
|
|
|
+ return &sync.Mutex{}
|
|
|
+}
|
|
|
+
|
|
|
+func NewRWMutex() RWMutex {
|
|
|
+ if debug {
|
|
|
+ return &loggedRWMutex{
|
|
|
+ unlockers: make([]string, 0),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return &sync.RWMutex{}
|
|
|
+}
|
|
|
+
|
|
|
+func NewWaitGroup() WaitGroup {
|
|
|
+ if debug {
|
|
|
+ return &loggedWaitGroup{}
|
|
|
+ }
|
|
|
+ return &sync.WaitGroup{}
|
|
|
+}
|
|
|
+
|
|
|
+type loggedMutex struct {
|
|
|
+ sync.Mutex
|
|
|
+ start time.Time
|
|
|
+ lockedAt string
|
|
|
+}
|
|
|
+
|
|
|
+func (m *loggedMutex) Lock() {
|
|
|
+ m.Mutex.Lock()
|
|
|
+ m.start = time.Now()
|
|
|
+ m.lockedAt = getCaller()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *loggedMutex) Unlock() {
|
|
|
+ duration := time.Now().Sub(m.start)
|
|
|
+ if duration >= threshold {
|
|
|
+ l.Debugf("Mutex held for %v. Locked at %s unlocked at %s", duration, m.lockedAt, getCaller())
|
|
|
+ }
|
|
|
+ m.Mutex.Unlock()
|
|
|
+}
|
|
|
+
|
|
|
+type loggedRWMutex struct {
|
|
|
+ sync.RWMutex
|
|
|
+ start time.Time
|
|
|
+ lockedAt string
|
|
|
+
|
|
|
+ logUnlockers uint32
|
|
|
+
|
|
|
+ unlockers []string
|
|
|
+ unlockersMut sync.Mutex
|
|
|
+}
|
|
|
+
|
|
|
+func (m *loggedRWMutex) Lock() {
|
|
|
+ start := time.Now()
|
|
|
+
|
|
|
+ atomic.StoreUint32(&m.logUnlockers, 1)
|
|
|
+ m.RWMutex.Lock()
|
|
|
+ m.logUnlockers = 0
|
|
|
+
|
|
|
+ m.start = time.Now()
|
|
|
+ duration := m.start.Sub(start)
|
|
|
+
|
|
|
+ m.lockedAt = getCaller()
|
|
|
+ if duration > threshold {
|
|
|
+ l.Debugf("RWMutex took %v to lock. Locked at %s. RUnlockers while locking: %s", duration, m.lockedAt, strings.Join(m.unlockers, ", "))
|
|
|
+ }
|
|
|
+ m.unlockers = m.unlockers[0:]
|
|
|
+}
|
|
|
+
|
|
|
+func (m *loggedRWMutex) Unlock() {
|
|
|
+ duration := time.Now().Sub(m.start)
|
|
|
+ if duration >= threshold {
|
|
|
+ l.Debugf("RWMutex held for %v. Locked at %s: unlocked at %s", duration, m.lockedAt, getCaller())
|
|
|
+ }
|
|
|
+ m.RWMutex.Unlock()
|
|
|
+}
|
|
|
+
|
|
|
+func (m *loggedRWMutex) RUnlock() {
|
|
|
+ if atomic.LoadUint32(&m.logUnlockers) == 1 {
|
|
|
+ m.unlockersMut.Lock()
|
|
|
+ m.unlockers = append(m.unlockers, getCaller())
|
|
|
+ m.unlockersMut.Unlock()
|
|
|
+ }
|
|
|
+ m.RWMutex.RUnlock()
|
|
|
+}
|
|
|
+
|
|
|
+type loggedWaitGroup struct {
|
|
|
+ sync.WaitGroup
|
|
|
+}
|
|
|
+
|
|
|
+func (wg *loggedWaitGroup) Done() {
|
|
|
+ start := time.Now()
|
|
|
+ wg.WaitGroup.Done()
|
|
|
+ duration := time.Now().Sub(start)
|
|
|
+ if duration > threshold {
|
|
|
+ l.Debugf("WaitGroup took %v at %s", duration, getCaller())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func getCaller() string {
|
|
|
+ pc := make([]uintptr, 10)
|
|
|
+ runtime.Callers(3, pc)
|
|
|
+ f := runtime.FuncForPC(pc[0])
|
|
|
+ file, line := f.FileLine(pc[0])
|
|
|
+ file = filepath.Join(filepath.Base(filepath.Dir(file)), filepath.Base(file))
|
|
|
+ return fmt.Sprintf("%s:%d", file, line)
|
|
|
+}
|