File: sync.go

package info (click to toggle)
golang-github-anacrolix-sync 0.5.3-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 144 kB
  • sloc: makefile: 2
file content (92 lines) | stat: -rw-r--r-- 2,511 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// Package sync is an extension of the stdlib "sync" package. It has extra
// functionality that helps debug the use of synchronization primitives. The
// package should be importable in place of "sync". The extra functionality
// can be enabled by calling Enable() or passing a non-empty PPROF_SYNC
// environment variable to the process.
//
// Several profiles are exposed on the default HTTP muxer (and to
// "/debug/pprof" when "net/http/pprof" is imported by the process).
// "lockHolders" lists the stack traces of goroutines that called Mutex.Lock
// that haven't subsequently been Unlocked. "lockBlockers" contains goroutines
// that are waiting to obtain locks. "/debug/lockTimes" or PrintLockTimes()
// shows the longest time a lock is held for each stack trace.
//
// Note that currently RWMutex is treated like a Mutex when the package is
// enabled.
package sync

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"runtime/pprof"
	"strings"
	"sync"
	"text/tabwriter"

	"github.com/anacrolix/missinggo/v2"
)

var (
	// Protects initialization and enabling of the package.
	enableMu sync.Mutex
	// Whether shared locks must be handled as exclusive locks.
	noSharedLocking = false
	contentionOn    = false
	lockTimesOn     = false
	// Current lock holders.
	lockHolders *pprof.Profile
	// Those blocked on acquiring a lock.
	lockBlockers *pprof.Profile
)

// Writes out the longest time a Mutex remains locked for each stack trace
// that locks a Mutex.
func PrintLockTimes(w io.Writer) {
	lockTimes := sortedLockTimes()
	tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
	defer tw.Flush()
	w = tw
	for _, elem := range lockTimes {
		fmt.Fprintf(w, "%s (%s * %d [%s, %s])\n", elem.Total, elem.MeanTime(), elem.Count, elem.Min, elem.Max)
		missinggo.WriteStack(w, elem.stack[:])
	}
}

func Enable() {
	EnableContention()
	EnableLockTimes()
}

func EnableContention() {
	lockHolders = pprof.NewProfile("lockHolders")
	lockBlockers = pprof.NewProfile("lockBlockers")
	noSharedLocking = true
	contentionOn = true
}

func EnableLockTimes() {
	lockStatsByStack = make(map[lockStackKey]lockStats)
	http.DefaultServeMux.HandleFunc("/debug/lockTimes", func(w http.ResponseWriter, r *http.Request) {
		PrintLockTimes(w)
	})
	noSharedLocking = true
	lockTimesOn = true
}

func init() {
	env := os.Getenv("PPROF_SYNC")
	all := true
	if strings.Contains(env, "times") {
		EnableLockTimes()
		all = false
	}
	if strings.Contains(env, "contention") {
		EnableContention()
		all = false
	}
	if all && env != "" {
		Enable()
	}
}