mirror of
https://github.com/golang/go
synced 2024-11-23 06:30:06 -07:00
runtime: add world-stopped assertions
Stopping the world is an implicit lock for many operations, so we should assert the world is stopped in functions that require it. This is enabled along with the rest of lock ranking, though it is a bit orthogonal and likely cheap enough to enable all the time should we choose. Requiring a lock _or_ world stop is common, so that can be expressed as well. Updates #40677 Change-Id: If0a58544f4251d367f73c4120c9d39974c6cd091 Reviewed-on: https://go-review.googlesource.com/c/go/+/248577 Run-TryBot: Michael Pratt <mpratt@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Austin Clements <austin@google.com> Trust: Michael Pratt <mpratt@google.com>
This commit is contained in:
parent
94b3fd06cb
commit
6abbfc17c2
@ -431,6 +431,9 @@ func finq_callback(fn *funcval, obj unsafe.Pointer, nret uintptr, fint *_type, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dumproots() {
|
func dumproots() {
|
||||||
|
// To protect mheap_.allspans.
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
// TODO(mwhudson): dump datamask etc from all objects
|
// TODO(mwhudson): dump datamask etc from all objects
|
||||||
// data segment
|
// data segment
|
||||||
dumpint(tagData)
|
dumpint(tagData)
|
||||||
@ -468,6 +471,9 @@ func dumproots() {
|
|||||||
var freemark [_PageSize / 8]bool
|
var freemark [_PageSize / 8]bool
|
||||||
|
|
||||||
func dumpobjs() {
|
func dumpobjs() {
|
||||||
|
// To protect mheap_.allspans.
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
for _, s := range mheap_.allspans {
|
for _, s := range mheap_.allspans {
|
||||||
if s.state.get() != mSpanInUse {
|
if s.state.get() != mSpanInUse {
|
||||||
continue
|
continue
|
||||||
@ -552,6 +558,8 @@ func dumpms() {
|
|||||||
|
|
||||||
//go:systemstack
|
//go:systemstack
|
||||||
func dumpmemstats(m *MemStats) {
|
func dumpmemstats(m *MemStats) {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
// These ints should be identical to the exported
|
// These ints should be identical to the exported
|
||||||
// MemStats structure and should be ordered the same
|
// MemStats structure and should be ordered the same
|
||||||
// way too.
|
// way too.
|
||||||
@ -634,6 +642,9 @@ func dumpmemprof_callback(b *bucket, nstk uintptr, pstk *uintptr, size, allocs,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dumpmemprof() {
|
func dumpmemprof() {
|
||||||
|
// To protect mheap_.allspans.
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
iterate_memprof(dumpmemprof_callback)
|
iterate_memprof(dumpmemprof_callback)
|
||||||
for _, s := range mheap_.allspans {
|
for _, s := range mheap_.allspans {
|
||||||
if s.state.get() != mSpanInUse {
|
if s.state.get() != mSpanInUse {
|
||||||
@ -655,6 +666,8 @@ func dumpmemprof() {
|
|||||||
var dumphdr = []byte("go1.7 heap dump\n")
|
var dumphdr = []byte("go1.7 heap dump\n")
|
||||||
|
|
||||||
func mdump(m *MemStats) {
|
func mdump(m *MemStats) {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
// make sure we're done sweeping
|
// make sure we're done sweeping
|
||||||
for _, s := range mheap_.allspans {
|
for _, s := range mheap_.allspans {
|
||||||
if s.state.get() == mSpanInUse {
|
if s.state.get() == mSpanInUse {
|
||||||
@ -676,6 +689,8 @@ func mdump(m *MemStats) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeheapdump_m(fd uintptr, m *MemStats) {
|
func writeheapdump_m(fd uintptr, m *MemStats) {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
_g_ := getg()
|
_g_ := getg()
|
||||||
casgstatus(_g_.m.curg, _Grunning, _Gwaiting)
|
casgstatus(_g_.m.curg, _Grunning, _Gwaiting)
|
||||||
_g_.waitreason = waitReasonDumpingHeap
|
_g_.waitreason = waitReasonDumpingHeap
|
||||||
|
@ -46,3 +46,19 @@ func assertLockHeld(l *mutex) {
|
|||||||
//go:nosplit
|
//go:nosplit
|
||||||
func assertRankHeld(r lockRank) {
|
func assertRankHeld(r lockRank) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:nosplit
|
||||||
|
func worldStopped() {
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:nosplit
|
||||||
|
func worldStarted() {
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:nosplit
|
||||||
|
func assertWorldStopped() {
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:nosplit
|
||||||
|
func assertWorldStoppedOrLockHeld(l *mutex) {
|
||||||
|
}
|
||||||
|
@ -7,9 +7,14 @@
|
|||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime/internal/atomic"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// worldIsStopped is accessed atomically to track world-stops. 1 == world
|
||||||
|
// stopped.
|
||||||
|
var worldIsStopped uint32
|
||||||
|
|
||||||
// lockRankStruct is embedded in mutex
|
// lockRankStruct is embedded in mutex
|
||||||
type lockRankStruct struct {
|
type lockRankStruct struct {
|
||||||
// static lock ranking of the lock
|
// static lock ranking of the lock
|
||||||
@ -284,3 +289,77 @@ func assertRankHeld(r lockRank) {
|
|||||||
throw("not holding required lock!")
|
throw("not holding required lock!")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// worldStopped notes that the world is stopped.
|
||||||
|
//
|
||||||
|
// Caller must hold worldsema.
|
||||||
|
//
|
||||||
|
// nosplit to ensure it can be called in as many contexts as possible.
|
||||||
|
//go:nosplit
|
||||||
|
func worldStopped() {
|
||||||
|
if stopped := atomic.Xadd(&worldIsStopped, 1); stopped != 1 {
|
||||||
|
print("world stop count=", stopped, "\n")
|
||||||
|
throw("recursive world stop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// worldStarted that the world is starting.
|
||||||
|
//
|
||||||
|
// Caller must hold worldsema.
|
||||||
|
//
|
||||||
|
// nosplit to ensure it can be called in as many contexts as possible.
|
||||||
|
//go:nosplit
|
||||||
|
func worldStarted() {
|
||||||
|
if stopped := atomic.Xadd(&worldIsStopped, -1); stopped != 0 {
|
||||||
|
print("world stop count=", stopped, "\n")
|
||||||
|
throw("released non-stopped world stop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nosplit to ensure it can be called in as many contexts as possible.
|
||||||
|
//go:nosplit
|
||||||
|
func checkWorldStopped() bool {
|
||||||
|
stopped := atomic.Load(&worldIsStopped)
|
||||||
|
if stopped > 1 {
|
||||||
|
print("inconsistent world stop count=", stopped, "\n")
|
||||||
|
throw("inconsistent world stop count")
|
||||||
|
}
|
||||||
|
|
||||||
|
return stopped == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertWorldStopped throws if the world is not stopped. It does not check
|
||||||
|
// which M stopped the world.
|
||||||
|
//
|
||||||
|
// nosplit to ensure it can be called in as many contexts as possible.
|
||||||
|
//go:nosplit
|
||||||
|
func assertWorldStopped() {
|
||||||
|
if checkWorldStopped() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw("world not stopped")
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertWorldStoppedOrLockHeld throws if the world is not stopped and the
|
||||||
|
// passed lock is not held.
|
||||||
|
//
|
||||||
|
// nosplit to ensure it can be called in as many contexts as possible.
|
||||||
|
//go:nosplit
|
||||||
|
func assertWorldStoppedOrLockHeld(l *mutex) {
|
||||||
|
if checkWorldStopped() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gp := getg()
|
||||||
|
systemstack(func() {
|
||||||
|
held := checkLockHeld(gp, l)
|
||||||
|
if !held {
|
||||||
|
printlock()
|
||||||
|
print("caller requires world stop or lock ", l, " (rank ", l.rank.String(), "), holding:\n")
|
||||||
|
println("<no world stop>")
|
||||||
|
printHeldLocks(gp)
|
||||||
|
throw("no world stop or required lock!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -34,6 +34,8 @@ var useCheckmark = false
|
|||||||
//
|
//
|
||||||
// The world must be stopped.
|
// The world must be stopped.
|
||||||
func startCheckmarks() {
|
func startCheckmarks() {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
// Clear all checkmarks.
|
// Clear all checkmarks.
|
||||||
for _, ai := range mheap_.allArenas {
|
for _, ai := range mheap_.allArenas {
|
||||||
arena := mheap_.arenas[ai.l1()][ai.l2()]
|
arena := mheap_.arenas[ai.l1()][ai.l2()]
|
||||||
|
@ -2164,6 +2164,8 @@ func gcMark(start_time int64) {
|
|||||||
//
|
//
|
||||||
//go:systemstack
|
//go:systemstack
|
||||||
func gcSweep(mode gcMode) {
|
func gcSweep(mode gcMode) {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
if gcphase != _GCoff {
|
if gcphase != _GCoff {
|
||||||
throw("gcSweep being done but phase is not GCoff")
|
throw("gcSweep being done but phase is not GCoff")
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,8 @@ const (
|
|||||||
//
|
//
|
||||||
// The world must be stopped.
|
// The world must be stopped.
|
||||||
func gcMarkRootPrepare() {
|
func gcMarkRootPrepare() {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
work.nFlushCacheRoots = 0
|
work.nFlushCacheRoots = 0
|
||||||
|
|
||||||
// Compute how many data and BSS root blocks there are.
|
// Compute how many data and BSS root blocks there are.
|
||||||
@ -1535,6 +1537,8 @@ func gcmarknewobject(span *mspan, obj, size, scanSize uintptr) {
|
|||||||
//
|
//
|
||||||
// The world must be stopped.
|
// The world must be stopped.
|
||||||
func gcMarkTinyAllocs() {
|
func gcMarkTinyAllocs() {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
for _, p := range allp {
|
for _, p := range allp {
|
||||||
c := p.mcache
|
c := p.mcache
|
||||||
if c == nil || c.tiny == 0 {
|
if c == nil || c.tiny == 0 {
|
||||||
|
@ -123,6 +123,8 @@ func (h *mheap) nextSpanForSweep() *mspan {
|
|||||||
//
|
//
|
||||||
//go:nowritebarrier
|
//go:nowritebarrier
|
||||||
func finishsweep_m() {
|
func finishsweep_m() {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
// Sweeping must be complete before marking commences, so
|
// Sweeping must be complete before marking commences, so
|
||||||
// sweep any unswept spans. If this is a concurrent GC, there
|
// sweep any unswept spans. If this is a concurrent GC, there
|
||||||
// shouldn't be any spans left to sweep, so this should finish
|
// shouldn't be any spans left to sweep, so this should finish
|
||||||
|
@ -601,6 +601,8 @@ func readGCStats_m(pauses *[]uint64) {
|
|||||||
//
|
//
|
||||||
//go:nowritebarrier
|
//go:nowritebarrier
|
||||||
func updatememstats() {
|
func updatememstats() {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
// Flush mcaches to mcentral before doing anything else.
|
// Flush mcaches to mcentral before doing anything else.
|
||||||
//
|
//
|
||||||
// Flushing to the mcentral may in general cause stats to
|
// Flushing to the mcentral may in general cause stats to
|
||||||
@ -706,6 +708,8 @@ func updatememstats() {
|
|||||||
//
|
//
|
||||||
//go:nowritebarrier
|
//go:nowritebarrier
|
||||||
func flushmcache(i int) {
|
func flushmcache(i int) {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
p := allp[i]
|
p := allp[i]
|
||||||
c := p.mcache
|
c := p.mcache
|
||||||
if c == nil {
|
if c == nil {
|
||||||
@ -721,6 +725,8 @@ func flushmcache(i int) {
|
|||||||
//
|
//
|
||||||
//go:nowritebarrier
|
//go:nowritebarrier
|
||||||
func flushallmcaches() {
|
func flushallmcaches() {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
for i := 0; i < int(gomaxprocs); i++ {
|
for i := 0; i < int(gomaxprocs); i++ {
|
||||||
flushmcache(i)
|
flushmcache(i)
|
||||||
}
|
}
|
||||||
@ -876,10 +882,10 @@ func (m *consistentHeapStats) release(c *mcache) {
|
|||||||
// unsafeRead aggregates the delta for this shard into out.
|
// unsafeRead aggregates the delta for this shard into out.
|
||||||
//
|
//
|
||||||
// Unsafe because it does so without any synchronization. The
|
// Unsafe because it does so without any synchronization. The
|
||||||
// only safe time to call this is if the world is stopped or
|
// world must be stopped.
|
||||||
// we're freezing the world or going down anyway (and we just
|
|
||||||
// want _some_ estimate).
|
|
||||||
func (m *consistentHeapStats) unsafeRead(out *heapStatsDelta) {
|
func (m *consistentHeapStats) unsafeRead(out *heapStatsDelta) {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
for i := range m.stats {
|
for i := range m.stats {
|
||||||
out.merge(&m.stats[i])
|
out.merge(&m.stats[i])
|
||||||
}
|
}
|
||||||
@ -890,6 +896,8 @@ func (m *consistentHeapStats) unsafeRead(out *heapStatsDelta) {
|
|||||||
// Unsafe because the world must be stopped and values should
|
// Unsafe because the world must be stopped and values should
|
||||||
// be donated elsewhere before clearing.
|
// be donated elsewhere before clearing.
|
||||||
func (m *consistentHeapStats) unsafeClear() {
|
func (m *consistentHeapStats) unsafeClear() {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
for i := range m.stats {
|
for i := range m.stats {
|
||||||
m.stats[i] = heapStatsDelta{}
|
m.stats[i] = heapStatsDelta{}
|
||||||
}
|
}
|
||||||
|
@ -587,6 +587,9 @@ func schedinit() {
|
|||||||
|
|
||||||
sched.maxmcount = 10000
|
sched.maxmcount = 10000
|
||||||
|
|
||||||
|
// The world starts stopped.
|
||||||
|
worldStopped()
|
||||||
|
|
||||||
moduledataverify()
|
moduledataverify()
|
||||||
stackinit()
|
stackinit()
|
||||||
mallocinit()
|
mallocinit()
|
||||||
@ -617,6 +620,9 @@ func schedinit() {
|
|||||||
}
|
}
|
||||||
unlock(&sched.lock)
|
unlock(&sched.lock)
|
||||||
|
|
||||||
|
// World is effectively started now, as P's can run.
|
||||||
|
worldStarted()
|
||||||
|
|
||||||
// For cgocheck > 1, we turn on the write barrier at all times
|
// For cgocheck > 1, we turn on the write barrier at all times
|
||||||
// and check all pointer writes. We can't do this until after
|
// and check all pointer writes. We can't do this until after
|
||||||
// procresize because the write barrier needs a P.
|
// procresize because the write barrier needs a P.
|
||||||
@ -1082,9 +1088,13 @@ func stopTheWorldWithSema() {
|
|||||||
if bad != "" {
|
if bad != "" {
|
||||||
throw(bad)
|
throw(bad)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
worldStopped()
|
||||||
}
|
}
|
||||||
|
|
||||||
func startTheWorldWithSema(emitTraceEvent bool) int64 {
|
func startTheWorldWithSema(emitTraceEvent bool) int64 {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
mp := acquirem() // disable preemption because it can be holding p in a local var
|
mp := acquirem() // disable preemption because it can be holding p in a local var
|
||||||
if netpollinited() {
|
if netpollinited() {
|
||||||
list := netpoll(0) // non-blocking
|
list := netpoll(0) // non-blocking
|
||||||
@ -1105,6 +1115,8 @@ func startTheWorldWithSema(emitTraceEvent bool) int64 {
|
|||||||
}
|
}
|
||||||
unlock(&sched.lock)
|
unlock(&sched.lock)
|
||||||
|
|
||||||
|
worldStarted()
|
||||||
|
|
||||||
for p1 != nil {
|
for p1 != nil {
|
||||||
p := p1
|
p := p1
|
||||||
p1 = p1.link.ptr()
|
p1 = p1.link.ptr()
|
||||||
@ -4539,6 +4551,7 @@ func (pp *p) init(id int32) {
|
|||||||
// sched.lock must be held and the world must be stopped.
|
// sched.lock must be held and the world must be stopped.
|
||||||
func (pp *p) destroy() {
|
func (pp *p) destroy() {
|
||||||
assertLockHeld(&sched.lock)
|
assertLockHeld(&sched.lock)
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
// Move all runnable goroutines to the global queue
|
// Move all runnable goroutines to the global queue
|
||||||
for pp.runqhead != pp.runqtail {
|
for pp.runqhead != pp.runqtail {
|
||||||
@ -4629,6 +4642,7 @@ func (pp *p) destroy() {
|
|||||||
// Returns list of Ps with local work, they need to be scheduled by the caller.
|
// Returns list of Ps with local work, they need to be scheduled by the caller.
|
||||||
func procresize(nprocs int32) *p {
|
func procresize(nprocs int32) *p {
|
||||||
assertLockHeld(&sched.lock)
|
assertLockHeld(&sched.lock)
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
old := gomaxprocs
|
old := gomaxprocs
|
||||||
if old < 0 || nprocs <= 0 {
|
if old < 0 || nprocs <= 0 {
|
||||||
|
Loading…
Reference in New Issue
Block a user