mirror of
https://github.com/golang/go
synced 2024-11-26 08:27:56 -07:00
runtime: encapsulate access to allgs
Correctly accessing allgs is a bit hairy. Some paths need to lock allglock, some don't. Those that don't are safest using atomicAllG, but usage is not consistent. Rather than doing this ad-hoc, move all access* through forEachG / forEachGRace, the locking and atomic versions, respectively. This will make it easier to ensure safe access. * markroot is the only exception, as it has a far-removed guarantee of safe access via an atomic load of allglen far before actual use. Change-Id: Ie1c7a8243e155ae2b4bc3143577380c695680e89 Reviewed-on: https://go-review.googlesource.com/c/go/+/279994 Trust: Michael Pratt <mpratt@google.com> Run-TryBot: Michael Pratt <mpratt@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
parent
39bdd41d03
commit
d85083911d
@ -403,9 +403,10 @@ func dumpgoroutine(gp *g) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dumpgs() {
|
func dumpgs() {
|
||||||
|
assertWorldStopped()
|
||||||
|
|
||||||
// goroutines & stacks
|
// goroutines & stacks
|
||||||
for i := 0; uintptr(i) < allglen; i++ {
|
forEachG(func(gp *g) {
|
||||||
gp := allgs[i]
|
|
||||||
status := readgstatus(gp) // The world is stopped so gp will not be in a scan state.
|
status := readgstatus(gp) // The world is stopped so gp will not be in a scan state.
|
||||||
switch status {
|
switch status {
|
||||||
default:
|
default:
|
||||||
@ -418,7 +419,7 @@ func dumpgs() {
|
|||||||
_Gwaiting:
|
_Gwaiting:
|
||||||
dumpgoroutine(gp)
|
dumpgoroutine(gp)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func finq_callback(fn *funcval, obj unsafe.Pointer, nret uintptr, fint *_type, ot *ptrtype) {
|
func finq_callback(fn *funcval, obj unsafe.Pointer, nret uintptr, fint *_type, ot *ptrtype) {
|
||||||
|
@ -2229,14 +2229,12 @@ func gcSweep(mode gcMode) {
|
|||||||
//
|
//
|
||||||
//go:systemstack
|
//go:systemstack
|
||||||
func gcResetMarkState() {
|
func gcResetMarkState() {
|
||||||
// This may be called during a concurrent phase, so make sure
|
// This may be called during a concurrent phase, so lock to make sure
|
||||||
// allgs doesn't change.
|
// allgs doesn't change.
|
||||||
lock(&allglock)
|
forEachG(func(gp *g) {
|
||||||
for _, gp := range allgs {
|
|
||||||
gp.gcscandone = false // set to true in gcphasework
|
gp.gcscandone = false // set to true in gcphasework
|
||||||
gp.gcAssistBytes = 0
|
gp.gcAssistBytes = 0
|
||||||
}
|
})
|
||||||
unlock(&allglock)
|
|
||||||
|
|
||||||
// Clear page marks. This is just 1MB per 64GB of heap, so the
|
// Clear page marks. This is just 1MB per 64GB of heap, so the
|
||||||
// time here is pretty trivial.
|
// time here is pretty trivial.
|
||||||
|
@ -116,25 +116,28 @@ func gcMarkRootCheck() {
|
|||||||
throw("left over markroot jobs")
|
throw("left over markroot jobs")
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(&allglock)
|
|
||||||
// Check that stacks have been scanned.
|
// Check that stacks have been scanned.
|
||||||
var gp *g
|
//
|
||||||
for i := 0; i < work.nStackRoots; i++ {
|
// We only check the first nStackRoots Gs that we should have scanned.
|
||||||
gp = allgs[i]
|
// Since we don't care about newer Gs (see comment in
|
||||||
if !gp.gcscandone {
|
// gcMarkRootPrepare), no locking is required.
|
||||||
goto fail
|
i := 0
|
||||||
}
|
forEachGRace(func(gp *g) {
|
||||||
}
|
if i >= work.nStackRoots {
|
||||||
unlock(&allglock)
|
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fail:
|
if !gp.gcscandone {
|
||||||
println("gp", gp, "goid", gp.goid,
|
println("gp", gp, "goid", gp.goid,
|
||||||
"status", readgstatus(gp),
|
"status", readgstatus(gp),
|
||||||
"gcscandone", gp.gcscandone)
|
"gcscandone", gp.gcscandone)
|
||||||
throw("scan missed a g")
|
throw("scan missed a g")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ptrmask for an allocation containing a single pointer.
|
// ptrmask for an allocation containing a single pointer.
|
||||||
var oneptrmask = [...]uint8{1}
|
var oneptrmask = [...]uint8{1}
|
||||||
|
|
||||||
@ -189,6 +192,9 @@ func markroot(gcw *gcWork, i uint32) {
|
|||||||
// the rest is scanning goroutine stacks
|
// the rest is scanning goroutine stacks
|
||||||
var gp *g
|
var gp *g
|
||||||
if baseStacks <= i && i < end {
|
if baseStacks <= i && i < end {
|
||||||
|
// N.B. Atomic read of allglen in gcMarkRootPrepare
|
||||||
|
// acts as a barrier to ensure that allgs must be large
|
||||||
|
// enough to contain all relevant Gs.
|
||||||
gp = allgs[i-baseStacks]
|
gp = allgs[i-baseStacks]
|
||||||
} else {
|
} else {
|
||||||
throw("markroot: bad index")
|
throw("markroot: bad index")
|
||||||
|
@ -731,12 +731,13 @@ func goroutineProfileWithLabels(p []StackRecord, labels []unsafe.Pointer) (n int
|
|||||||
|
|
||||||
stopTheWorld("profile")
|
stopTheWorld("profile")
|
||||||
|
|
||||||
|
// World is stopped, no locking required.
|
||||||
n = 1
|
n = 1
|
||||||
for _, gp1 := range allgs {
|
forEachGRace(func(gp1 *g) {
|
||||||
if isOK(gp1) {
|
if isOK(gp1) {
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
if n <= len(p) {
|
if n <= len(p) {
|
||||||
ok = true
|
ok = true
|
||||||
@ -757,12 +758,15 @@ func goroutineProfileWithLabels(p []StackRecord, labels []unsafe.Pointer) (n int
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save other goroutines.
|
// Save other goroutines.
|
||||||
for _, gp1 := range allgs {
|
forEachGRace(func(gp1 *g) {
|
||||||
if isOK(gp1) {
|
if !isOK(gp1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if len(r) == 0 {
|
if len(r) == 0 {
|
||||||
// Should be impossible, but better to return a
|
// Should be impossible, but better to return a
|
||||||
// truncated profile than to crash the entire process.
|
// truncated profile than to crash the entire process.
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
saveg(^uintptr(0), ^uintptr(0), gp1, &r[0])
|
saveg(^uintptr(0), ^uintptr(0), gp1, &r[0])
|
||||||
if labels != nil {
|
if labels != nil {
|
||||||
@ -770,8 +774,7 @@ func goroutineProfileWithLabels(p []StackRecord, labels []unsafe.Pointer) (n int
|
|||||||
lbl = lbl[1:]
|
lbl = lbl[1:]
|
||||||
}
|
}
|
||||||
r = r[1:]
|
r = r[1:]
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startTheWorld()
|
startTheWorld()
|
||||||
|
@ -541,6 +541,30 @@ func atomicAllGIndex(ptr **g, i uintptr) *g {
|
|||||||
return *(**g)(add(unsafe.Pointer(ptr), i*sys.PtrSize))
|
return *(**g)(add(unsafe.Pointer(ptr), i*sys.PtrSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forEachG calls fn on every G from allgs.
|
||||||
|
//
|
||||||
|
// forEachG takes a lock to exclude concurrent addition of new Gs.
|
||||||
|
func forEachG(fn func(gp *g)) {
|
||||||
|
lock(&allglock)
|
||||||
|
for _, gp := range allgs {
|
||||||
|
fn(gp)
|
||||||
|
}
|
||||||
|
unlock(&allglock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// forEachGRace calls fn on every G from allgs.
|
||||||
|
//
|
||||||
|
// forEachGRace avoids locking, but does not exclude addition of new Gs during
|
||||||
|
// execution, which may be missed.
|
||||||
|
func forEachGRace(fn func(gp *g)) {
|
||||||
|
ptr, length := atomicAllG()
|
||||||
|
for i := uintptr(0); i < length; i++ {
|
||||||
|
gp := atomicAllGIndex(ptr, i)
|
||||||
|
fn(gp)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Number of goroutine ids to grab from sched.goidgen to local per-P cache at once.
|
// Number of goroutine ids to grab from sched.goidgen to local per-P cache at once.
|
||||||
// 16 seems to provide enough amortization, but other than that it's mostly arbitrary number.
|
// 16 seems to provide enough amortization, but other than that it's mostly arbitrary number.
|
||||||
@ -4969,11 +4993,9 @@ func checkdead() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
grunning := 0
|
grunning := 0
|
||||||
lock(&allglock)
|
forEachG(func(gp *g) {
|
||||||
for i := 0; i < len(allgs); i++ {
|
|
||||||
gp := allgs[i]
|
|
||||||
if isSystemGoroutine(gp, false) {
|
if isSystemGoroutine(gp, false) {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
s := readgstatus(gp)
|
s := readgstatus(gp)
|
||||||
switch s &^ _Gscan {
|
switch s &^ _Gscan {
|
||||||
@ -4986,8 +5008,7 @@ func checkdead() {
|
|||||||
print("runtime: checkdead: find g ", gp.goid, " in status ", s, "\n")
|
print("runtime: checkdead: find g ", gp.goid, " in status ", s, "\n")
|
||||||
throw("checkdead: runnable g")
|
throw("checkdead: runnable g")
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
unlock(&allglock)
|
|
||||||
if grunning == 0 { // possible if main goroutine calls runtime·Goexit()
|
if grunning == 0 { // possible if main goroutine calls runtime·Goexit()
|
||||||
unlock(&sched.lock) // unlock so that GODEBUG=scheddetail=1 doesn't hang
|
unlock(&sched.lock) // unlock so that GODEBUG=scheddetail=1 doesn't hang
|
||||||
throw("no goroutines (main called runtime.Goexit) - deadlock!")
|
throw("no goroutines (main called runtime.Goexit) - deadlock!")
|
||||||
@ -5390,9 +5411,7 @@ func schedtrace(detailed bool) {
|
|||||||
print(" M", mp.id, ": p=", id1, " curg=", id2, " mallocing=", mp.mallocing, " throwing=", mp.throwing, " preemptoff=", mp.preemptoff, ""+" locks=", mp.locks, " dying=", mp.dying, " spinning=", mp.spinning, " blocked=", mp.blocked, " lockedg=", id3, "\n")
|
print(" M", mp.id, ": p=", id1, " curg=", id2, " mallocing=", mp.mallocing, " throwing=", mp.throwing, " preemptoff=", mp.preemptoff, ""+" locks=", mp.locks, " dying=", mp.dying, " spinning=", mp.spinning, " blocked=", mp.blocked, " lockedg=", id3, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(&allglock)
|
forEachG(func(gp *g) {
|
||||||
for gi := 0; gi < len(allgs); gi++ {
|
|
||||||
gp := allgs[gi]
|
|
||||||
mp := gp.m
|
mp := gp.m
|
||||||
lockedm := gp.lockedm.ptr()
|
lockedm := gp.lockedm.ptr()
|
||||||
id1 := int64(-1)
|
id1 := int64(-1)
|
||||||
@ -5404,8 +5423,7 @@ func schedtrace(detailed bool) {
|
|||||||
id2 = lockedm.id
|
id2 = lockedm.id
|
||||||
}
|
}
|
||||||
print(" G", gp.goid, ": status=", readgstatus(gp), "(", gp.waitreason.String(), ") m=", id1, " lockedm=", id2, "\n")
|
print(" G", gp.goid, ": status=", readgstatus(gp), "(", gp.waitreason.String(), ") m=", id1, " lockedm=", id2, "\n")
|
||||||
}
|
})
|
||||||
unlock(&allglock)
|
|
||||||
unlock(&sched.lock)
|
unlock(&sched.lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +221,8 @@ func StartTrace() error {
|
|||||||
stackID := traceStackID(mp, stkBuf, 2)
|
stackID := traceStackID(mp, stkBuf, 2)
|
||||||
releasem(mp)
|
releasem(mp)
|
||||||
|
|
||||||
for _, gp := range allgs {
|
// World is stopped, no need to lock.
|
||||||
|
forEachGRace(func(gp *g) {
|
||||||
status := readgstatus(gp)
|
status := readgstatus(gp)
|
||||||
if status != _Gdead {
|
if status != _Gdead {
|
||||||
gp.traceseq = 0
|
gp.traceseq = 0
|
||||||
@ -241,7 +242,7 @@ func StartTrace() error {
|
|||||||
} else {
|
} else {
|
||||||
gp.sysblocktraced = false
|
gp.sysblocktraced = false
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
traceProcStart()
|
traceProcStart()
|
||||||
traceGoStart()
|
traceGoStart()
|
||||||
// Note: ticksStart needs to be set after we emit traceEvGoInSyscall events.
|
// Note: ticksStart needs to be set after we emit traceEvGoInSyscall events.
|
||||||
|
@ -945,19 +945,16 @@ func tracebackothers(me *g) {
|
|||||||
traceback(^uintptr(0), ^uintptr(0), 0, curgp)
|
traceback(^uintptr(0), ^uintptr(0), 0, curgp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can't take allglock here because this may be during fatal
|
// We can't call locking forEachG here because this may be during fatal
|
||||||
// throw/panic, where locking allglock could be out-of-order or a
|
// throw/panic, where locking could be out-of-order or a direct
|
||||||
// direct deadlock.
|
// deadlock.
|
||||||
//
|
//
|
||||||
// Instead, use atomic access to allgs which requires no locking. We
|
// Instead, use forEachGRace, which requires no locking. We don't lock
|
||||||
// don't lock against concurrent creation of new Gs, but even with
|
// against concurrent creation of new Gs, but even with allglock we may
|
||||||
// allglock we may miss Gs created after this loop.
|
// miss Gs created after this loop.
|
||||||
ptr, length := atomicAllG()
|
forEachGRace(func(gp *g) {
|
||||||
for i := uintptr(0); i < length; i++ {
|
|
||||||
gp := atomicAllGIndex(ptr, i)
|
|
||||||
|
|
||||||
if gp == me || gp == curgp || readgstatus(gp) == _Gdead || isSystemGoroutine(gp, false) && level < 2 {
|
if gp == me || gp == curgp || readgstatus(gp) == _Gdead || isSystemGoroutine(gp, false) && level < 2 {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
print("\n")
|
print("\n")
|
||||||
goroutineheader(gp)
|
goroutineheader(gp)
|
||||||
@ -971,7 +968,7 @@ func tracebackothers(me *g) {
|
|||||||
} else {
|
} else {
|
||||||
traceback(^uintptr(0), ^uintptr(0), 0, gp)
|
traceback(^uintptr(0), ^uintptr(0), 0, gp)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// tracebackHexdump hexdumps part of stk around frame.sp and frame.fp
|
// tracebackHexdump hexdumps part of stk around frame.sp and frame.fp
|
||||||
|
Loading…
Reference in New Issue
Block a user