Skip to content

Commit 0b31a46

Browse files
rhyshgopherbot
authored andcommitted
runtime: profile contended lock calls
Add runtime-internal locks to the mutex contention profile. Store up to one call stack responsible for lock contention on the M, until it's safe to contribute its value to the mprof table. Try to use that limited local storage space for a relatively large source of contention, and attribute any contention in stacks we're not able to store to a sentinel _LostContendedLock function. Avoid ballooning lock contention while manipulating the mprof table by attributing to that sentinel function any lock contention experienced while reporting lock contention. Guard collecting real call stacks with GODEBUG=profileruntimelocks=1, since the available data has mixed semantics; we can easily capture an M's own wait time, but we'd prefer for the profile entry of each critical section to describe how long it made the other Ms wait. It's too late in the Go 1.22 cycle to make the required changes to futex-based locks. When not enabled, attribute the time to the sentinel function instead. Fixes #57071 Change-Id: I3eee0ccbfc20f333b56f20d8725dfd7f3a526b41 Reviewed-on: https://go-review.googlesource.com/c/go/+/528657 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Auto-Submit: Rhys Hiltner <[email protected]> Reviewed-by: Than McIntosh <[email protected]>
1 parent 3cdc2a1 commit 0b31a46

11 files changed

+627
-23
lines changed

src/runtime/debug.go

+11
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ func NumCgoCall() int64 {
5252
return n
5353
}
5454

55+
func totalMutexWaitTimeNanos() int64 {
56+
total := sched.totalMutexWaitTime.Load()
57+
58+
total += sched.totalRuntimeLockWaitTime.Load()
59+
for mp := (*m)(atomic.Loadp(unsafe.Pointer(&allm))); mp != nil; mp = mp.alllink {
60+
total += mp.mLockProfile.waitTime.Load()
61+
}
62+
63+
return total
64+
}
65+
5566
// NumGoroutine returns the number of goroutines that currently exist.
5667
func NumGoroutine() int {
5768
return int(gcount())

src/runtime/export_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,27 @@ func PageCachePagesLeaked() (leaked uintptr) {
13401340
return
13411341
}
13421342

1343+
type Mutex = mutex
1344+
1345+
var Lock = lock
1346+
var Unlock = unlock
1347+
1348+
func MutexContended(l *mutex) bool {
1349+
switch atomic.Loaduintptr(&l.key) {
1350+
case 0: // unlocked
1351+
return false
1352+
case 1: // locked
1353+
return false
1354+
default: // an M is sleeping
1355+
return true
1356+
}
1357+
}
1358+
1359+
func SemRootLock(addr *uint32) *mutex {
1360+
root := semtable.rootFor(addr)
1361+
return &root.lock
1362+
}
1363+
13431364
var Semacquire = semacquire
13441365
var Semrelease1 = semrelease1
13451366

src/runtime/extern.go

+8
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ It is a comma-separated list of name=val pairs setting these named variables:
145145
risk in that scenario. Currently not supported on Windows, plan9 or js/wasm. Setting this
146146
option for some applications can produce large traces, so use with care.
147147
148+
profileruntimelocks: setting profileruntimelocks=1 includes call stacks related to
149+
contention on runtime-internal locks in the "mutex" profile, subject to the
150+
MutexProfileFraction setting. The call stacks will correspond to the unlock call that
151+
released the lock. But instead of the value corresponding to the amount of contention that
152+
call stack caused, it corresponds to the amount of time the caller of unlock had to wait
153+
in its original call to lock. A future release is expected to align those and remove this
154+
setting.
155+
148156
invalidptr: invalidptr=1 (the default) causes the garbage collector and stack
149157
copier to crash the program if an invalid pointer value (for example, 1)
150158
is found in a pointer-typed location. Setting invalidptr=0 disables this check.

src/runtime/lock_futex.go

+6
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ func lock2(l *mutex) {
7171
// its wakeup call.
7272
wait := v
7373

74+
timer := &lockTimer{lock: l}
75+
timer.begin()
7476
// On uniprocessors, no point spinning.
7577
// On multiprocessors, spin for ACTIVE_SPIN attempts.
7678
spin := 0
@@ -82,6 +84,7 @@ func lock2(l *mutex) {
8284
for i := 0; i < spin; i++ {
8385
for l.key == mutex_unlocked {
8486
if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {
87+
timer.end()
8588
return
8689
}
8790
}
@@ -92,6 +95,7 @@ func lock2(l *mutex) {
9295
for i := 0; i < passive_spin; i++ {
9396
for l.key == mutex_unlocked {
9497
if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {
98+
timer.end()
9599
return
96100
}
97101
}
@@ -101,6 +105,7 @@ func lock2(l *mutex) {
101105
// Sleep.
102106
v = atomic.Xchg(key32(&l.key), mutex_sleeping)
103107
if v == mutex_unlocked {
108+
timer.end()
104109
return
105110
}
106111
wait = mutex_sleeping
@@ -122,6 +127,7 @@ func unlock2(l *mutex) {
122127
}
123128

124129
gp := getg()
130+
gp.m.mLockProfile.recordUnlock(l)
125131
gp.m.locks--
126132
if gp.m.locks < 0 {
127133
throw("runtime·unlock: lock count")

src/runtime/lock_sema.go

+4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ func lock2(l *mutex) {
4848
}
4949
semacreate(gp.m)
5050

51+
timer := &lockTimer{lock: l}
52+
timer.begin()
5153
// On uniprocessor's, no point spinning.
5254
// On multiprocessors, spin for ACTIVE_SPIN attempts.
5355
spin := 0
@@ -60,6 +62,7 @@ Loop:
6062
if v&locked == 0 {
6163
// Unlocked. Try to lock.
6264
if atomic.Casuintptr(&l.key, v, v|locked) {
65+
timer.end()
6366
return
6467
}
6568
i = 0
@@ -119,6 +122,7 @@ func unlock2(l *mutex) {
119122
}
120123
}
121124
}
125+
gp.m.mLockProfile.recordUnlock(l)
122126
gp.m.locks--
123127
if gp.m.locks < 0 {
124128
throw("runtime·unlock: lock count")

src/runtime/metrics.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ func initMetrics() {
470470
"/sync/mutex/wait/total:seconds": {
471471
compute: func(_ *statAggregate, out *metricValue) {
472472
out.kind = metricKindFloat64
473-
out.scalar = float64bits(nsToSec(sched.totalMutexWaitTime.Load()))
473+
out.scalar = float64bits(nsToSec(totalMutexWaitTimeNanos()))
474474
},
475475
},
476476
}

0 commit comments

Comments
 (0)