diff options
| author | Vlad Saioc <vsaioc@uber.com> | 2025-11-06 09:53:28 +0000 |
|---|---|---|
| committer | Michael Knyszek <mknyszek@google.com> | 2025-11-12 08:08:34 -0800 |
| commit | 8873e8bea29ac6de5fecee88b8b81239bd2eb179 (patch) | |
| tree | 611e017bb3870c45c3d0ab213269e88fddbd9046 /src/runtime/pprof/pprof.go | |
| parent | b8b84b789e4275aeea491dbdb50536facd1fa7d7 (diff) | |
| download | go-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.go | 34 |
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() |
