runtime: fix double wakeup in CPU profile buffer

The profBuf.wakeupExtra method wakes up the profile reader if it's
sleeping, either when the buffer is closed or when there is a pending
overflow entry.  Unlike in profBuf.write, profBuf.wakeupExtra does not
clear the profReaderSleeping bit before doing the wakeup. As a result,
if there are two writes to a full buffer before the sleeping reader has
time to wake up, we'll see two consecutive calls to notewakeup, which is
a fatal error. This CL updates profBuf.wakeupExtra to clear the sleeping
bit before doing the wakeup.

This CL adds a unit test that demonstrates the problem. This is
theoretically possible to trigger for real programs as well, but it's
more difficult. The profBufWordCount is large enough that it takes
several CPU-seconds to fill up the buffer. So we'd need to run on a
system with lots of cores to have a chance of running into this failure,
and the reader would need to fully go to sleep before a large burst of
CPU activity.

Change-Id: I59b4fa86a12f6236890b82cd353a95706a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/722940
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Mark Freeman <markfreeman@google.com>
This commit is contained in:
Nick Ripley 2025-11-21 14:09:57 +00:00
parent 22f24f90b5
commit b604148c4e
2 changed files with 21 additions and 0 deletions

View file

@ -406,6 +406,11 @@ func (b *profBuf) wakeupExtra() {
for {
old := b.w.load()
new := old | profWriteExtra
// Clear profReaderSleeping. We're going to wake up the reader
// if it was sleeping and we don't want double wakeups in case
// we, for example, attempt to write into a full buffer multiple
// times before the reader wakes up.
new &^= profReaderSleeping
if !b.w.cas(old, new) {
continue
}

View file

@ -174,3 +174,19 @@ func TestProfBuf(t *testing.T) {
}
})
}
func TestProfBufDoubleWakeup(t *testing.T) {
b := NewProfBuf(2, 16, 2)
go func() {
for range 1000 {
b.Write(nil, 1, []uint64{5, 6}, []uintptr{7, 8})
}
b.Close()
}()
for {
_, _, eof := b.Read(ProfBufBlocking)
if eof {
return
}
}
}