aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/runtime/proc.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/runtime/proc.go')
-rw-r--r--libgo/go/runtime/proc.go294
1 files changed, 203 insertions, 91 deletions
diff --git a/libgo/go/runtime/proc.go b/libgo/go/runtime/proc.go
index 6b6f9c9..345f57b 100644
--- a/libgo/go/runtime/proc.go
+++ b/libgo/go/runtime/proc.go
@@ -214,8 +214,17 @@ func main() {
// Make racy client program work: if panicking on
// another goroutine at the same time as main returns,
// let the other goroutine finish printing the panic trace.
- // Once it does, it will exit. See issue 3934.
- if panicking != 0 {
+ // Once it does, it will exit. See issues 3934 and 20018.
+ if atomic.Load(&runningPanicDefers) != 0 {
+ // Running deferred functions should not take long.
+ for c := 0; c < 1000; c++ {
+ if atomic.Load(&runningPanicDefers) == 0 {
+ break
+ }
+ Gosched()
+ }
+ }
+ if atomic.Load(&panicking) != 0 {
gopark(nil, nil, "panicwait", traceEvGoStop, 1)
}
@@ -255,18 +264,25 @@ func forcegchelper() {
if debug.gctrace > 0 {
println("GC forced")
}
- gcStart(gcBackgroundMode, true)
+ // Time-triggered, fully concurrent.
+ gcStart(gcBackgroundMode, gcTrigger{kind: gcTriggerTime, now: nanotime()})
}
}
-//go:nosplit
-
// Gosched yields the processor, allowing other goroutines to run. It does not
// suspend the current goroutine, so execution resumes automatically.
+//go:nosplit
func Gosched() {
mcall(gosched_m)
}
+// goschedguarded yields the processor like gosched, but also checks
+// for forbidden states and opts out of the yield in those cases.
+//go:nosplit
+func goschedguarded() {
+ mcall(goschedguarded_m)
+}
+
// Puts the current goroutine into a waiting state and calls unlockf.
// If unlockf returns false, the goroutine is resumed.
// unlockf must not access this G's stack, as it may be moved between
@@ -419,16 +435,6 @@ func allgadd(gp *g) {
lock(&allglock)
allgs = append(allgs, gp)
allglen = uintptr(len(allgs))
-
- // Grow GC rescan list if necessary.
- if len(allgs) > cap(work.rescan.list) {
- lock(&work.rescan.lock)
- l := work.rescan.list
- // Let append do the heavy lifting, but keep the
- // length the same.
- work.rescan.list = append(l[:cap(l)], 0)[:len(l)]
- unlock(&work.rescan.lock)
- }
unlock(&allglock)
}
@@ -765,9 +771,8 @@ func casgstatus(gp *g, oldval, newval uint32) {
nextYield = nanotime() + yieldDelay/2
}
}
- if newval == _Grunning && gp.gcscanvalid {
- // Run queueRescan on the system stack so it has more space.
- systemstack(func() { queueRescan(gp) })
+ if newval == _Grunning {
+ gp.gcscanvalid = false
}
}
@@ -779,8 +784,6 @@ func scang(gp *g, gcw *gcWork) {
// Nothing is racing with us now, but gcscandone might be set to true left over
// from an earlier round of stack scanning (we scan twice per GC).
// We use gcscandone to record whether the scan has been done during this round.
- // It is important that the scan happens exactly once: if called twice,
- // the installation of stack barriers will detect the double scan and die.
gp.gcscandone = false
@@ -902,7 +905,7 @@ func restartg(gp *g) {
// in panic or being exited, this may not reliably stop all
// goroutines.
func stopTheWorld(reason string) {
- semacquire(&worldsema, 0)
+ semacquire(&worldsema)
getg().m.preemptoff = reason
systemstack(stopTheWorldWithSema)
}
@@ -1129,10 +1132,10 @@ func mstart1() {
}
asminit()
- minit()
// Install signal handlers; after minit so that minit can
// prepare the thread to be able to handle the signals.
+ // For gccgo minit was called by C code.
if _g_.m == &m0 {
// Create an extra M for callbacks on threads not created by Go.
if iscgo && !cgoHasExtraM {
@@ -1363,6 +1366,7 @@ func needm(x byte) {
// running at all (that is, there's no garbage collection
// running right now).
mp.needextram = mp.schedlink == 0
+ extraMCount--
unlockextra(mp.schedlink.ptr())
// Save and block signals before installing g.
@@ -1376,12 +1380,16 @@ func needm(x byte) {
// Install g (= m->curg).
setg(mp.curg)
- atomic.Store(&mp.curg.atomicstatus, _Gsyscall)
- setGContext()
// Initialize this thread to use the m.
asminit()
minit()
+
+ setGContext()
+
+ // mp.curg is now a real goroutine.
+ casgstatus(mp.curg, _Gdead, _Gsyscall)
+ atomic.Xadd(&sched.ngsys, -1)
}
var earlycgocallback = []byte("fatal error: cgo callback before cgo call\n")
@@ -1414,14 +1422,12 @@ func oneNewExtraM() {
// the goroutine stack ends.
mp, g0SP, g0SPSize := allocm(nil, nil, true)
gp := malg(true, false, nil, nil)
- gp.gcscanvalid = true // fresh G, so no dequeueRescan necessary
+ gp.gcscanvalid = true
gp.gcscandone = true
- gp.gcRescan = -1
-
- // malg returns status as Gidle, change to Gdead before adding to allg
- // where GC will see it.
- // gccgo uses Gdead here, not Gsyscall, because the split
- // stack context is not initialized.
+ // malg returns status as _Gidle. Change to _Gdead before
+ // adding to allg where GC can see it. We use _Gdead to hide
+ // this from tracebacks and stack scans since it isn't a
+ // "real" goroutine until needm grabs it.
casgstatus(gp, _Gidle, _Gdead)
gp.m = mp
mp.curg = gp
@@ -1436,9 +1442,16 @@ func oneNewExtraM() {
// Here we need to set the context for g0.
makeGContext(mp.g0, g0SP, g0SPSize)
+ // gp is now on the allg list, but we don't want it to be
+ // counted by gcount. It would be more "proper" to increment
+ // sched.ngfree, but that requires locking. Incrementing ngsys
+ // has the same effect.
+ atomic.Xadd(&sched.ngsys, +1)
+
// Add m to the extra list.
mnext := lockextra(true)
mp.schedlink.set(mnext)
+ extraMCount++
unlockextra(mp)
}
@@ -1474,6 +1487,10 @@ func dropm() {
// with no pointer manipulation.
mp := getg().m
+ // Return mp.curg to dead state.
+ casgstatus(mp.curg, _Gsyscall, _Gdead)
+ atomic.Xadd(&sched.ngsys, +1)
+
// Block signals before unminit.
// Unminit unregisters the signal handling stack (but needs g on some systems).
// Setg(nil) clears g, which is the signal handler's cue not to run Go handlers.
@@ -1489,6 +1506,7 @@ func dropm() {
mp.curg.gcnextsp = 0
mnext := lockextra(true)
+ extraMCount++
mp.schedlink.set(mnext)
setg(nil)
@@ -1505,6 +1523,7 @@ func getm() uintptr {
}
var extram uintptr
+var extraMCount uint32 // Protected by lockextra
var extraMWaiters uint32
// lockextra locks the extra list and returns the list head.
@@ -1551,6 +1570,10 @@ func unlockextra(mp *m) {
atomic.Storeuintptr(&extram, uintptr(unsafe.Pointer(mp)))
}
+// execLock serializes exec and clone to avoid bugs or unspecified behaviour
+// around exec'ing while creating/destroying threads. See issue #19546.
+var execLock rwmutex
+
// Create a new m. It will start off with a call to fn, or else the scheduler.
// fn needs to be static and not a heap allocated closure.
// May run with m.p==nil, so write barriers are not allowed.
@@ -1559,7 +1582,9 @@ func newm(fn func(), _p_ *p) {
mp, _, _ := allocm(_p_, fn, false)
mp.nextp.set(_p_)
mp.sigmask = initSigmask
+ execLock.rlock() // Prevent process clone.
newosproc(mp)
+ execLock.runlock()
}
// Stops execution of the current m until new work is available.
@@ -1812,7 +1837,7 @@ func execute(gp *g, inheritTime bool) {
// Check whether the profiler needs to be turned on or off.
hz := sched.profilehz
if _g_.m.profilehz != hz {
- resetcpuprofiler(hz)
+ setThreadCPUProfiler(hz)
}
if trace.enabled {
@@ -1850,6 +1875,9 @@ top:
ready(gp, 0, true)
}
}
+ if *cgo_yield != nil {
+ asmcgocall(*cgo_yield, nil)
+ }
// local runq
if gp, inheritTime := runqget(_p_); gp != nil {
@@ -2007,7 +2035,7 @@ stop:
}
// poll network
- if netpollinited() && atomic.Xchg64(&sched.lastpoll, 0) != 0 {
+ if netpollinited() && atomic.Load(&netpollWaiters) > 0 && atomic.Xchg64(&sched.lastpoll, 0) != 0 {
if _g_.m.p != 0 {
throw("findrunnable: netpoll with p")
}
@@ -2048,7 +2076,7 @@ func pollWork() bool {
if !runqempty(p) {
return true
}
- if netpollinited() && sched.lastpoll != 0 {
+ if netpollinited() && atomic.Load(&netpollWaiters) > 0 && sched.lastpoll != 0 {
if gp := netpoll(false); gp != nil {
injectglist(gp)
return true
@@ -2211,7 +2239,7 @@ func park_m(gp *g) {
_g_ := getg()
if trace.enabled {
- traceGoPark(_g_.m.waittraceev, _g_.m.waittraceskip, gp)
+ traceGoPark(_g_.m.waittraceev, _g_.m.waittraceskip)
}
casgstatus(gp, _Grunning, _Gwaiting)
@@ -2256,6 +2284,19 @@ func gosched_m(gp *g) {
goschedImpl(gp)
}
+// goschedguarded is a forbidden-states-avoided version of gosched_m
+func goschedguarded_m(gp *g) {
+
+ if gp.m.locks != 0 || gp.m.mallocing != 0 || gp.m.preemptoff != "" || gp.m.p.ptr().status != _Prunning {
+ gogo(gp) // never return
+ }
+
+ if trace.enabled {
+ traceGoSched()
+ }
+ goschedImpl(gp)
+}
+
func gopreempt_m(gp *g) {
if trace.enabled {
traceGoPreempt()
@@ -2290,10 +2331,11 @@ func goexit0(gp *g) {
gp.writebuf = nil
gp.waitreason = ""
gp.param = nil
+ gp.labels = nil
+ gp.timer = nil
// Note that gp's stack scan is now "valid" because it has no
- // stack. We could dequeueRescan, but that takes a lock and
- // isn't really necessary.
+ // stack.
gp.gcscanvalid = true
dropg()
@@ -2641,12 +2683,12 @@ func syscall_exitsyscall() {
func beforefork() {
gp := getg().m.curg
- // Fork can hang if preempted with signals frequently enough (see issue 5517).
- // Ensure that we stay on the same M where we disable profiling.
+ // Block signals during a fork, so that the child does not run
+ // a signal handler before exec if a signal is sent to the process
+ // group. See issue #18600.
gp.m.locks++
- if gp.m.profilehz != 0 {
- resetcpuprofiler(0)
- }
+ msigsave(gp.m)
+ sigblock()
}
// Called from syscall package before fork.
@@ -2659,10 +2701,8 @@ func syscall_runtime_BeforeFork() {
func afterfork() {
gp := getg().m.curg
- hz := sched.profilehz
- if hz != 0 {
- resetcpuprofiler(hz)
- }
+ msigrestore(gp.m.sigmask)
+
gp.m.locks--
}
@@ -2673,6 +2713,50 @@ func syscall_runtime_AfterFork() {
systemstack(afterfork)
}
+// inForkedChild is true while manipulating signals in the child process.
+// This is used to avoid calling libc functions in case we are using vfork.
+var inForkedChild bool
+
+// Called from syscall package after fork in child.
+// It resets non-sigignored signals to the default handler, and
+// restores the signal mask in preparation for the exec.
+//
+// Because this might be called during a vfork, and therefore may be
+// temporarily sharing address space with the parent process, this must
+// not change any global variables or calling into C code that may do so.
+//
+//go:linkname syscall_runtime_AfterForkInChild syscall.runtime_AfterForkInChild
+//go:nosplit
+//go:nowritebarrierrec
+func syscall_runtime_AfterForkInChild() {
+ // It's OK to change the global variable inForkedChild here
+ // because we are going to change it back. There is no race here,
+ // because if we are sharing address space with the parent process,
+ // then the parent process can not be running concurrently.
+ inForkedChild = true
+
+ clearSignalHandlers()
+
+ // When we are the child we are the only thread running,
+ // so we know that nothing else has changed gp.m.sigmask.
+ msigrestore(getg().m.sigmask)
+
+ inForkedChild = false
+}
+
+// Called from syscall package before Exec.
+//go:linkname syscall_runtime_BeforeExec syscall.runtime_BeforeExec
+func syscall_runtime_BeforeExec() {
+ // Prevent thread creation during exec.
+ execLock.lock()
+}
+
+// Called from syscall package after Exec.
+//go:linkname syscall_runtime_AfterExec syscall.runtime_AfterExec
+func syscall_runtime_AfterExec() {
+ execLock.unlock()
+}
+
// Create a new g running fn passing arg as the single argument.
// Put it on the queue of g's waiting to run.
// The compiler turns a go statement into a call to this.
@@ -2695,7 +2779,6 @@ func newproc(fn uintptr, arg unsafe.Pointer) *g {
if newg == nil {
newg = malg(true, false, &sp, &spsize)
casgstatus(newg, _Gidle, _Gdead)
- newg.gcRescan = -1
allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
} else {
resetNewG(newg, &sp, &spsize)
@@ -2717,17 +2800,13 @@ func newproc(fn uintptr, arg unsafe.Pointer) *g {
newg.param = arg
newg.gopc = getcallerpc(unsafe.Pointer(&fn))
newg.startpc = fn
- // The stack is dirty from the argument frame, so queue it for
- // scanning. Do this before setting it to runnable so we still
- // own the G. If we're recycling a G, it may already be on the
- // rescan list.
- if newg.gcRescan == -1 {
- queueRescan(newg)
- } else {
- // The recycled G is already on the rescan list. Just
- // mark the stack dirty.
- newg.gcscanvalid = false
+ if _g_.m.curg != nil {
+ newg.labels = _g_.m.curg.labels
+ }
+ if isSystemGoroutine(newg) {
+ atomic.Xadd(&sched.ngsys, +1)
}
+ newg.gcscanvalid = false
casgstatus(newg, _Gdead, _Grunnable)
if _p_.goidcache == _p_.goidcacheend {
@@ -2926,8 +3005,7 @@ func badunlockosthread() {
func gcount() int32 {
n := int32(allglen) - sched.ngfree - int32(atomic.Load(&sched.ngsys))
- for i := 0; ; i++ {
- _p_ := allp[i]
+ for _, _p_ := range &allp {
if _p_ == nil {
break
}
@@ -2947,13 +3025,18 @@ func mcount() int32 {
}
var prof struct {
- lock uint32
- hz int32
+ signalLock uint32
+ hz int32
}
-func _System() { _System() }
-func _ExternalCode() { _ExternalCode() }
-func _GC() { _GC() }
+func _System() { _System() }
+func _ExternalCode() { _ExternalCode() }
+func _LostExternalCode() { _LostExternalCode() }
+func _GC() { _GC() }
+func _LostSIGPROFDuringAtomic64() { _LostSIGPROFDuringAtomic64() }
+
+// Counts SIGPROFs received while in atomic64 critical section, on mips{,le}
+var lostAtomic64Count uint64
var _SystemPC = funcPC(_System)
var _ExternalCodePC = funcPC(_ExternalCode)
@@ -3009,14 +3092,11 @@ func sigprof(pc uintptr, gp *g, mp *m) {
}
if prof.hz != 0 {
- // Simple cas-lock to coordinate with setcpuprofilerate.
- for !atomic.Cas(&prof.lock, 0, 1) {
- osyield()
+ if (GOARCH == "mips" || GOARCH == "mipsle") && lostAtomic64Count > 0 {
+ cpuprof.addLostAtomic64(lostAtomic64Count)
+ lostAtomic64Count = 0
}
- if prof.hz != 0 {
- cpuprof.add(stk[:n])
- }
- atomic.Store(&prof.lock, 0)
+ cpuprof.add(gp, stk[:n])
}
getg().m.mallocing--
}
@@ -3047,19 +3127,28 @@ func sigprofNonGo(pc uintptr) {
nonprofGoStk[1] = _ExternalCodePC + sys.PCQuantum
}
- // Simple cas-lock to coordinate with setcpuprofilerate.
- for !atomic.Cas(&prof.lock, 0, 1) {
- osyield()
- }
- if prof.hz != 0 {
- cpuprof.addNonGo(nonprofGoStk[:n])
+ cpuprof.addNonGo(nonprofGoStk[:n])
+ }
+}
+
+// sigprofNonGoPC is called when a profiling signal arrived on a
+// non-Go thread and we have a single PC value, not a stack trace.
+// g is nil, and what we can do is very limited.
+//go:nosplit
+//go:nowritebarrierrec
+func sigprofNonGoPC(pc uintptr) {
+ if prof.hz != 0 {
+ stk := []uintptr{
+ pc,
+ funcPC(_ExternalCode) + sys.PCQuantum,
}
- atomic.Store(&prof.lock, 0)
+ cpuprof.addNonGo(stk)
}
}
-// Arrange to call fn with a traceback hz times a second.
-func setcpuprofilerate_m(hz int32) {
+// setcpuprofilerate sets the CPU profiling rate to hz times per second.
+// If hz <= 0, setcpuprofilerate turns off CPU profiling.
+func setcpuprofilerate(hz int32) {
// Force sane arguments.
if hz < 0 {
hz = 0
@@ -3073,20 +3162,23 @@ func setcpuprofilerate_m(hz int32) {
// Stop profiler on this thread so that it is safe to lock prof.
// if a profiling signal came in while we had prof locked,
// it would deadlock.
- resetcpuprofiler(0)
+ setThreadCPUProfiler(0)
- for !atomic.Cas(&prof.lock, 0, 1) {
+ for !atomic.Cas(&prof.signalLock, 0, 1) {
osyield()
}
- prof.hz = hz
- atomic.Store(&prof.lock, 0)
+ if prof.hz != hz {
+ setProcessCPUProfiler(hz)
+ prof.hz = hz
+ }
+ atomic.Store(&prof.signalLock, 0)
lock(&sched.lock)
sched.profilehz = hz
unlock(&sched.lock)
if hz != 0 {
- resetcpuprofiler(hz)
+ setThreadCPUProfiler(hz)
}
_g_.m.locks--
@@ -3424,7 +3516,25 @@ func sysmon() {
if scavengelimit < forcegcperiod {
maxsleep = scavengelimit / 2
}
+ shouldRelax := true
+ if osRelaxMinNS > 0 {
+ lock(&timers.lock)
+ if timers.sleeping {
+ now := nanotime()
+ next := timers.sleepUntil
+ if next-now < osRelaxMinNS {
+ shouldRelax = false
+ }
+ }
+ unlock(&timers.lock)
+ }
+ if shouldRelax {
+ osRelax(true)
+ }
notetsleep(&sched.sysmonnote, maxsleep)
+ if shouldRelax {
+ osRelax(false)
+ }
lock(&sched.lock)
atomic.Store(&sched.sysmonwait, 0)
noteclear(&sched.sysmonnote)
@@ -3433,10 +3543,13 @@ func sysmon() {
}
unlock(&sched.lock)
}
+ // trigger libc interceptors if needed
+ if *cgo_yield != nil {
+ asmcgocall(*cgo_yield, nil)
+ }
// poll network if not polled for more than 10ms
lastpoll := int64(atomic.Load64(&sched.lastpoll))
now := nanotime()
- unixnow := unixnanotime()
if lastpoll != 0 && lastpoll+10*1000*1000 < now {
atomic.Cas64(&sched.lastpoll, uint64(lastpoll), uint64(now))
gp := netpoll(false) // non-blocking - returns list of goroutines
@@ -3461,8 +3574,7 @@ func sysmon() {
idle++
}
// check if we need to force a GC
- lastgc := int64(atomic.Load64(&memstats.last_gc))
- if gcphase == _GCoff && lastgc != 0 && unixnow-lastgc > forcegcperiod && atomic.Load(&forcegc.idle) != 0 {
+ if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {
lock(&forcegc.lock)
forcegc.idle = 0
forcegc.g.schedlink = 0
@@ -3482,7 +3594,7 @@ func sysmon() {
}
}
-var pdesc [_MaxGomaxprocs]struct {
+type sysmontick struct {
schedtick uint32
schedwhen int64
syscalltick uint32
@@ -3500,7 +3612,7 @@ func retake(now int64) uint32 {
if _p_ == nil {
continue
}
- pd := &pdesc[i]
+ pd := &_p_.sysmontick
s := _p_.status
if s == _Psyscall {
// Retake P from syscall if it's there for more than 1 sysmon tick (at least 20us).
@@ -3905,7 +4017,7 @@ func runqputslow(_p_ *p, gp *g, h, t uint32) bool {
if randomizeScheduler {
for i := uint32(1); i <= n; i++ {
- j := fastrand() % (i + 1)
+ j := fastrandn(i + 1)
batch[i], batch[j] = batch[j], batch[i]
}
}