Skip to content

Commit cfe0ae0

Browse files
mknyszekprattmic
authored andcommitted
[release-branch.go1.23] runtime: uphold goroutine profile invariants in coroswitch
Goroutine profiles require checking in with the profiler before any goroutine starts running. coroswitch is a place where a goroutine may start running, but where we do not check in with the profiler, which leads to crashes. Fix this by checking in with the profiler the same way execute does. For #69998. Fixes #70001. Change-Id: Idef6dd31b70a73dd1c967b56c307c7a46a26ba73 Reviewed-on: https://go-review.googlesource.com/c/go/+/622016 Reviewed-by: David Chase <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> (cherry picked from commit 2a98a18) Reviewed-on: https://go-review.googlesource.com/c/go/+/622375 Reviewed-by: Michael Pratt <[email protected]>
1 parent 58babf6 commit cfe0ae0

File tree

2 files changed

+57
-0
lines changed

2 files changed

+57
-0
lines changed

src/runtime/coro.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,18 @@ func coroswitch_m(gp *g) {
208208
// directly if possible.
209209
setGNoWB(&mp.curg, gnext)
210210
setMNoWB(&gnext.m, mp)
211+
212+
// Synchronize with any out-standing goroutine profile. We're about to start
213+
// executing, and an invariant of the profiler is that we tryRecordGoroutineProfile
214+
// whenever a goroutine is about to start running.
215+
//
216+
// N.B. We must do this before transitioning to _Grunning but after installing gnext
217+
// in curg, so that we have a valid curg for allocation (tryRecordGoroutineProfile
218+
// may allocate).
219+
if goroutineProfile.active {
220+
tryRecordGoroutineProfile(gnext, nil, osyield)
221+
}
222+
211223
if !gnext.atomicstatus.CompareAndSwap(_Gwaiting, _Grunning) {
212224
// The CAS failed: use casgstatus, which will take care of
213225
// coordinating with the garbage collector about the state change.

src/runtime/pprof/pprof_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"internal/syscall/unix"
1616
"internal/testenv"
1717
"io"
18+
"iter"
1819
"math"
1920
"math/big"
2021
"os"
@@ -1754,6 +1755,50 @@ func TestGoroutineProfileConcurrency(t *testing.T) {
17541755
}
17551756
}
17561757

1758+
// Regression test for #69998.
1759+
func TestGoroutineProfileCoro(t *testing.T) {
1760+
testenv.MustHaveParallelism(t)
1761+
1762+
goroutineProf := Lookup("goroutine")
1763+
1764+
// Set up a goroutine to just create and run coroutine goroutines all day.
1765+
iterFunc := func() {
1766+
p, stop := iter.Pull2(
1767+
func(yield func(int, int) bool) {
1768+
for i := 0; i < 10000; i++ {
1769+
if !yield(i, i) {
1770+
return
1771+
}
1772+
}
1773+
},
1774+
)
1775+
defer stop()
1776+
for {
1777+
_, _, ok := p()
1778+
if !ok {
1779+
break
1780+
}
1781+
}
1782+
}
1783+
var wg sync.WaitGroup
1784+
done := make(chan struct{})
1785+
wg.Add(1)
1786+
go func() {
1787+
defer wg.Done()
1788+
for {
1789+
iterFunc()
1790+
select {
1791+
case <-done:
1792+
default:
1793+
}
1794+
}
1795+
}()
1796+
1797+
// Take a goroutine profile. If the bug in #69998 is present, this will crash
1798+
// with high probability. We don't care about the output for this bug.
1799+
goroutineProf.WriteTo(io.Discard, 1)
1800+
}
1801+
17571802
func BenchmarkGoroutine(b *testing.B) {
17581803
withIdle := func(n int, fn func(b *testing.B)) func(b *testing.B) {
17591804
return func(b *testing.B) {

0 commit comments

Comments
 (0)