aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/pprof/pprof.go
diff options
context:
space:
mode:
authorVlad Saioc <vsaioc@uber.com>2025-11-06 09:53:28 +0000
committerMichael Knyszek <mknyszek@google.com>2025-11-12 08:08:34 -0800
commit8873e8bea29ac6de5fecee88b8b81239bd2eb179 (patch)
tree611e017bb3870c45c3d0ab213269e88fddbd9046 /src/runtime/pprof/pprof.go
parentb8b84b789e4275aeea491dbdb50536facd1fa7d7 (diff)
downloadgo-8873e8bea29ac6de5fecee88b8b81239bd2eb179.tar.xz
runtime,runtime/pprof: clean up goroutine leak profile writing
Cleaned up goroutine leak profile extraction: - removed the acquisition of goroutineProfile semaphore - inlined the call to saveg when recording stacks instead of using doRecordGoroutineProfile, which had side-effects over goroutineProfile fields. Added regression tests for goroutine leak profiling frontend for binary and debug=1 profile formats. Added stress tests for concurrent goroutine and goroutine leak profile requests. Change-Id: I55c1bcef11e9a7fb7699b4c5a2353e594d3e7173 GitHub-Last-Rev: 5e9eb3b1d80c4d2d9b668a01f6b39a7b42f7bb45 GitHub-Pull-Request: golang/go#76045 Reviewed-on: https://go-review.googlesource.com/c/go/+/714580 Reviewed-by: Michael Pratt <mpratt@google.com> Reviewed-by: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Junyang Shao <shaojunyang@google.com>
Diffstat (limited to 'src/runtime/pprof/pprof.go')
-rw-r--r--src/runtime/pprof/pprof.go34
1 files changed, 34 insertions, 0 deletions
diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go
index b524e992b8..78d00af6ca 100644
--- a/src/runtime/pprof/pprof.go
+++ b/src/runtime/pprof/pprof.go
@@ -228,6 +228,31 @@ var mutexProfile = &Profile{
write: writeMutex,
}
+// goroutineLeakProfileLock ensures that the goroutine leak profile writer observes the
+// leaked goroutines discovered during the goroutine leak detection GC cycle
+// that was triggered by the profile request.
+// This prevents a race condition between the garbage collector and the profile writer
+// when multiple profile requests are issued concurrently: the status of leaked goroutines
+// is reset to _Gwaiting at the beginning of a leak detection cycle, which may lead the
+// profile writer of another concurrent request to produce an incomplete profile.
+//
+// Example trace:
+//
+// G1 | GC | G2
+// ----------------------+-----------------------------+---------------------
+// Request profile | . | .
+// . | . | Request profile
+// . | [G1] Resets leaked g status | .
+// . | [G1] Leaks detected | .
+// . | <New cycle> | .
+// . | [G2] Resets leaked g status | .
+// Write profile | . | .
+// . | [G2] Leaks detected | .
+// . | . | Write profile
+// ----------------------+-----------------------------+---------------------
+// Incomplete profile |+++++++++++++++++++++++++++++| Complete profile
+var goroutineLeakProfileLock sync.Mutex
+
func lockProfiles() {
profiles.mu.Lock()
if profiles.m == nil {
@@ -763,6 +788,15 @@ func writeGoroutine(w io.Writer, debug int) error {
// writeGoroutineLeak first invokes a GC cycle that performs goroutine leak detection.
// It then writes the goroutine profile, filtering for leaked goroutines.
func writeGoroutineLeak(w io.Writer, debug int) error {
+ // Acquire the goroutine leak detection lock and release
+ // it after the goroutine leak profile is written.
+ //
+ // While the critical section is long, this is needed to prevent
+ // a race condition between the garbage collector and the goroutine
+ // leak profile writer when multiple profile requests are issued concurrently.
+ goroutineLeakProfileLock.Lock()
+ defer goroutineLeakProfileLock.Unlock()
+
// Run the GC with leak detection first so that leaked goroutines
// may transition to the leaked state.
runtime_goroutineLeakGC()