Skip to content

Commit 16ae11a

Browse files
mknyszekgopherbot
authored andcommitted
runtime: move TestReadMetricsSched to testprog
There are just too many flakes resulting from background pollution by the testing package and other tests. Run in a subprocess where at least the environment can be more tightly controlled. Fixes golang#75049. Change-Id: Iad59edaaf31268f1fcb77273f01317d963708fa6 Reviewed-on: https://go-review.googlesource.com/c/go/+/707155 Reviewed-by: Michael Pratt <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Michael Knyszek <[email protected]>
1 parent 459f3a3 commit 16ae11a

File tree

4 files changed

+274
-209
lines changed

4 files changed

+274
-209
lines changed

src/runtime/metrics_test.go

Lines changed: 5 additions & 207 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"strings"
2323
"sync"
2424
"sync/atomic"
25-
"syscall"
2625
"testing"
2726
"time"
2827
"unsafe"
@@ -1578,211 +1577,10 @@ func TestReadMetricsFinalizers(t *testing.T) {
15781577
}
15791578

15801579
func TestReadMetricsSched(t *testing.T) {
1581-
const (
1582-
notInGo = iota
1583-
runnable
1584-
running
1585-
waiting
1586-
created
1587-
threads
1588-
numSamples
1589-
)
1590-
var s [numSamples]metrics.Sample
1591-
s[notInGo].Name = "/sched/goroutines/not-in-go:goroutines"
1592-
s[runnable].Name = "/sched/goroutines/runnable:goroutines"
1593-
s[running].Name = "/sched/goroutines/running:goroutines"
1594-
s[waiting].Name = "/sched/goroutines/waiting:goroutines"
1595-
s[created].Name = "/sched/goroutines-created:goroutines"
1596-
s[threads].Name = "/sched/threads/total:threads"
1597-
1598-
logMetrics := func(t *testing.T, s []metrics.Sample) {
1599-
for i := range s {
1600-
t.Logf("%s: %d", s[i].Name, s[i].Value.Uint64())
1601-
}
1602-
}
1603-
1604-
// generalSlack is the amount of goroutines we allow ourselves to be
1605-
// off by in any given category, either due to background system
1606-
// goroutines or testing package goroutines.
1607-
const generalSlack = 4
1608-
1609-
// waitingSlack is the max number of blocked goroutines left
1610-
// from other tests, the testing package, or system
1611-
// goroutines.
1612-
const waitingSlack = 100
1613-
1614-
// threadsSlack is the maximum number of threads left over
1615-
// from other tests and the runtime (sysmon, the template thread, etc.)
1616-
const threadsSlack = 20
1617-
1618-
// Make sure GC isn't running, since GC workers interfere with
1619-
// expected counts.
1620-
defer debug.SetGCPercent(debug.SetGCPercent(-1))
1621-
runtime.GC()
1622-
1623-
check := func(t *testing.T, s *metrics.Sample, min, max uint64) {
1624-
val := s.Value.Uint64()
1625-
if val < min {
1626-
t.Errorf("%s too low; %d < %d", s.Name, val, min)
1627-
}
1628-
if val > max {
1629-
t.Errorf("%s too high; %d > %d", s.Name, val, max)
1630-
}
1631-
}
1632-
checkEq := func(t *testing.T, s *metrics.Sample, value uint64) {
1633-
check(t, s, value, value)
1580+
// This test is run in a subprocess to prevent other tests from polluting the metrics.
1581+
output := runTestProg(t, "testprog", "SchedMetrics")
1582+
want := "OK\n"
1583+
if output != want {
1584+
t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
16341585
}
1635-
spinUntil := func(f func() bool) bool {
1636-
for {
1637-
if f() {
1638-
return true
1639-
}
1640-
time.Sleep(50 * time.Millisecond)
1641-
}
1642-
}
1643-
1644-
// Check base values.
1645-
t.Run("base", func(t *testing.T) {
1646-
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
1647-
metrics.Read(s[:])
1648-
logMetrics(t, s[:])
1649-
check(t, &s[notInGo], 0, generalSlack)
1650-
check(t, &s[runnable], 0, generalSlack)
1651-
checkEq(t, &s[running], 1)
1652-
check(t, &s[waiting], 0, waitingSlack)
1653-
})
1654-
1655-
metrics.Read(s[:])
1656-
createdAfterBase := s[created].Value.Uint64()
1657-
1658-
// Force Running count to be high. We'll use these goroutines
1659-
// for Runnable, too.
1660-
const count = 10
1661-
var ready, exit atomic.Uint32
1662-
for i := 0; i < count-1; i++ {
1663-
go func() {
1664-
ready.Add(1)
1665-
for exit.Load() == 0 {
1666-
// Spin to get us and keep us running, but check
1667-
// the exit condition so we exit out early if we're
1668-
// done.
1669-
start := time.Now()
1670-
for time.Since(start) < 10*time.Millisecond && exit.Load() == 0 {
1671-
}
1672-
runtime.Gosched()
1673-
}
1674-
}()
1675-
}
1676-
for ready.Load() < count-1 {
1677-
runtime.Gosched()
1678-
}
1679-
1680-
// Be careful. We've entered a dangerous state for platforms
1681-
// that do not return back to the underlying system unless all
1682-
// goroutines are blocked, like js/wasm, since we have a bunch
1683-
// of runnable goroutines all spinning. We cannot write anything
1684-
// out.
1685-
if testenv.HasParallelism() {
1686-
t.Run("created", func(t *testing.T) {
1687-
metrics.Read(s[:])
1688-
logMetrics(t, s[:])
1689-
checkEq(t, &s[created], createdAfterBase+count)
1690-
})
1691-
t.Run("running", func(t *testing.T) {
1692-
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(count + 4))
1693-
// It can take a little bit for the scheduler to
1694-
// distribute the goroutines to Ps, so retry until
1695-
// we see the count we expect or the test times out.
1696-
spinUntil(func() bool {
1697-
metrics.Read(s[:])
1698-
return s[running].Value.Uint64() >= count
1699-
})
1700-
logMetrics(t, s[:])
1701-
check(t, &s[running], count, count+4)
1702-
check(t, &s[threads], count, count+4+threadsSlack)
1703-
})
1704-
1705-
// Force runnable count to be high.
1706-
t.Run("runnable", func(t *testing.T) {
1707-
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
1708-
metrics.Read(s[:])
1709-
logMetrics(t, s[:])
1710-
checkEq(t, &s[running], 1)
1711-
check(t, &s[runnable], count-1, count+generalSlack)
1712-
})
1713-
1714-
// Done with the running/runnable goroutines.
1715-
exit.Store(1)
1716-
} else {
1717-
// Read metrics and then exit all the other goroutines,
1718-
// so that system calls may proceed.
1719-
metrics.Read(s[:])
1720-
1721-
// Done with the running/runnable goroutines.
1722-
exit.Store(1)
1723-
1724-
// Now we can check our invariants.
1725-
t.Run("created", func(t *testing.T) {
1726-
// Look for count-1 goroutines because we read metrics
1727-
// *before* t.Run goroutine was created for this sub-test.
1728-
checkEq(t, &s[created], createdAfterBase+count-1)
1729-
})
1730-
t.Run("running", func(t *testing.T) {
1731-
logMetrics(t, s[:])
1732-
checkEq(t, &s[running], 1)
1733-
checkEq(t, &s[threads], 1)
1734-
})
1735-
t.Run("runnable", func(t *testing.T) {
1736-
logMetrics(t, s[:])
1737-
check(t, &s[runnable], count-1, count+generalSlack)
1738-
})
1739-
}
1740-
1741-
// Force not-in-go count to be high. This is a little tricky since
1742-
// we try really hard not to let things block in system calls.
1743-
// We have to drop to the syscall package to do this reliably.
1744-
t.Run("not-in-go", func(t *testing.T) {
1745-
// Block a bunch of goroutines on an OS pipe.
1746-
pr, pw, err := pipe()
1747-
if err != nil {
1748-
switch runtime.GOOS {
1749-
case "js", "wasip1":
1750-
t.Skip("creating pipe:", err)
1751-
}
1752-
t.Fatal("creating pipe:", err)
1753-
}
1754-
for i := 0; i < count; i++ {
1755-
go syscall.Read(pr, make([]byte, 1))
1756-
}
1757-
1758-
// Let the goroutines block.
1759-
spinUntil(func() bool {
1760-
metrics.Read(s[:])
1761-
return s[notInGo].Value.Uint64() >= count
1762-
})
1763-
logMetrics(t, s[:])
1764-
check(t, &s[notInGo], count, count+generalSlack)
1765-
1766-
syscall.Close(pw)
1767-
syscall.Close(pr)
1768-
})
1769-
1770-
t.Run("waiting", func(t *testing.T) {
1771-
// Force waiting count to be high.
1772-
const waitingCount = 1000
1773-
stop := make(chan bool)
1774-
for i := 0; i < waitingCount; i++ {
1775-
go func() { <-stop }()
1776-
}
1777-
1778-
// Let the goroutines block.
1779-
spinUntil(func() bool {
1780-
metrics.Read(s[:])
1781-
return s[waiting].Value.Uint64() >= waitingCount
1782-
})
1783-
logMetrics(t, s[:])
1784-
check(t, &s[waiting], waitingCount, waitingCount+waitingSlack)
1785-
1786-
close(stop)
1787-
})
17881586
}

src/runtime/pipe_unix_test.go renamed to src/runtime/testdata/testprog/pipe_unix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
//go:build !windows
66

7-
package runtime_test
7+
package main
88

99
import "syscall"
1010

src/runtime/pipe_windows_test.go renamed to src/runtime/testdata/testprog/pipe_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
package runtime_test
5+
package main
66

77
import "syscall"
88

0 commit comments

Comments
 (0)