// semi-persistent CPU underutilization.
//
// The general pattern for submission is:
-// 1. Submit work to the local run queue, timer heap, or GC state.
+// 1. Submit work to the local or global run queue, timer heap, or GC state.
// 2. #StoreLoad-style memory barrier.
// 3. Check sched.nmspinning.
//
main_init_done = make(chan bool)
if iscgo {
+ if _cgo_pthread_key_created == nil {
+ throw("_cgo_pthread_key_created missing")
+ }
+
if _cgo_thread_start == nil {
throw("_cgo_thread_start missing")
}
if _cgo_notify_runtime_init_done == nil {
throw("_cgo_notify_runtime_init_done missing")
}
+
+ // Set the x_crosscall2_ptr C function pointer variable point to crosscall2.
+ if set_crosscall2 == nil {
+ throw("set_crosscall2 missing")
+ }
+ set_crosscall2()
+
// Start the template thread in case we enter Go from
// a C-created thread and need to create a new thread.
startTemplateThread()
// list can arrive a few different ways, but it will always
// contain the init tasks computed by the linker for all the
// packages in the program (excluding those added at runtime
- // by package plugin).
- for _, m := range activeModules() {
+ // by package plugin). Run through the modules in dependency
+ // order (the order they are initialized by the dynamic
+ // loader, i.e. they are added to the moduledata linked list).
+ for m := &firstmoduledata; m != nil; m = m.next {
doInit(m.inittasks)
}
}
}
if panicking.Load() != 0 {
- gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1)
+ gopark(nil, nil, waitReasonPanicWait, traceBlockForever, 1)
}
runExitHooks(0)
throw("forcegc: phase error")
}
forcegc.idle.Store(true)
- goparkunlock(&forcegc.lock, waitReasonForceGCIdle, traceEvGoBlock, 1)
+ goparkunlock(&forcegc.lock, waitReasonForceGCIdle, traceBlockSystemGoroutine, 1)
// this goroutine is explicitly resumed by sysmon
if debug.gctrace > 0 {
println("GC forced")
// Reason explains why the goroutine has been parked. It is displayed in stack
// traces and heap dumps. Reasons should be unique and descriptive. Do not
// re-use reasons, add new ones.
-func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) {
+func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceReason traceBlockReason, traceskip int) {
if reason != waitReasonSleep {
checkTimeouts() // timeouts may expire while two goroutines keep the scheduler busy
}
mp.waitlock = lock
mp.waitunlockf = unlockf
gp.waitreason = reason
- mp.waittraceev = traceEv
- mp.waittraceskip = traceskip
+ mp.waitTraceBlockReason = traceReason
+ mp.waitTraceSkip = traceskip
releasem(mp)
// can't do anything that might move the G between Ms here.
mcall(park_m)
// Puts the current goroutine into a waiting state and unlocks the lock.
// The goroutine can be made runnable again by calling goready(gp).
-func goparkunlock(lock *mutex, reason waitReason, traceEv byte, traceskip int) {
- gopark(parkunlock_c, unsafe.Pointer(lock), reason, traceEv, traceskip)
+func goparkunlock(lock *mutex, reason waitReason, traceReason traceBlockReason, traceskip int) {
+ gopark(parkunlock_c, unsafe.Pointer(lock), reason, traceReason, traceskip)
}
func goready(gp *g, traceskip int) {
//go:nosplit
//go:nowritebarrierrec
func badmorestackg0() {
- writeErrStr("fatal: morestack on g0\n")
+ if !crashStackImplemented {
+ writeErrStr("fatal: morestack on g0\n")
+ return
+ }
+
+ g := getg()
+ switchToCrashStack(func() {
+ print("runtime: morestack on g0, stack [", hex(g.stack.lo), " ", hex(g.stack.hi), "], sp=", hex(g.sched.sp), ", called from\n")
+ g.m.traceback = 2 // include pc and sp in stack trace
+ traceback1(g.sched.pc, g.sched.sp, g.sched.lr, g, 0)
+ print("\n")
+
+ throw("morestack on g0")
+ })
}
//go:nosplit
throw("ctxt != 0")
}
+// gcrash is a fake g that can be used when crashing due to bad
+// stack conditions.
+var gcrash g
+
+var crashingG atomic.Pointer[g]
+
+// Switch to crashstack and call fn, with special handling of
+// concurrent and recursive cases.
+//
+// Nosplit as it is called in a bad stack condition (we know
+// morestack would fail).
+//
+//go:nosplit
+//go:nowritebarrierrec
+func switchToCrashStack(fn func()) {
+ me := getg()
+ if crashingG.CompareAndSwapNoWB(nil, me) {
+ switchToCrashStack0(fn) // should never return
+ abort()
+ }
+ if crashingG.Load() == me {
+ // recursive crashing. too bad.
+ writeErrStr("fatal: recursive switchToCrashStack\n")
+ abort()
+ }
+ // Another g is crashing. Give it some time, hopefully it will finish traceback.
+ usleep_no_g(100)
+ writeErrStr("fatal: concurrent switchToCrashStack\n")
+ abort()
+}
+
+const crashStackImplemented = GOARCH == "amd64" || GOARCH == "arm64" || GOARCH == "mips64" || GOARCH == "mips64le" || GOARCH == "riscv64"
+
+//go:noescape
+func switchToCrashStack0(fn func()) // in assembly
+
func lockedOSThread() bool {
gp := getg()
return gp.lockedm != 0 && gp.m.lockedg != 0
goargs()
goenvs()
+ secure()
+ checkfds()
parsedebugvars()
gcinit()
+ // Allocate stack space that can be used when crashing due to bad stack
+ // conditions, e.g. morestack on g0.
+ gcrash.stack = stackalloc(16384)
+ gcrash.stackguard0 = gcrash.stack.lo + 1000
+ gcrash.stackguard1 = gcrash.stack.lo + 1000
+
// if disableMemoryProfiling is set, update MemProfileRate to 0 to turn off memprofile.
// Note: parsedebugvars may update MemProfileRate, but when disableMemoryProfiling is
// set to true by the linker, it means that nothing is consuming the profile, it is
// Mark gp ready to run.
func ready(gp *g, traceskip int, next bool) {
- if traceEnabled() {
- traceGoUnpark(gp, traceskip)
- }
-
status := readgstatus(gp)
// Mark runnable.
}
// status is Gwaiting or Gscanwaiting, make Grunnable and put on runq
+ trace := traceAcquire()
casgstatus(gp, _Gwaiting, _Grunnable)
+ if trace.ok() {
+ trace.GoUnpark(gp, traceskip)
+ traceRelease(trace)
+ }
runqput(mp.p.ptr(), gp, next)
wakep()
releasem(mp)
// This function must not lock any mutexes.
func freezetheworld() {
freezing.Store(true)
+ if debug.dontfreezetheworld > 0 {
+ // Don't prempt Ps to stop goroutines. That will perturb
+ // scheduler state, making debugging more difficult. Instead,
+ // allow goroutines to continue execution.
+ //
+ // fatalpanic will tracebackothers to trace all goroutines. It
+ // is unsafe to trace a running goroutine, so tracebackothers
+ // will skip running goroutines. That is OK and expected, we
+ // expect users of dontfreezetheworld to use core files anyway.
+ //
+ // However, allowing the scheduler to continue running free
+ // introduces a race: a goroutine may be stopped when
+ // tracebackothers checks its status, and then start running
+ // later when we are in the middle of traceback, potentially
+ // causing a crash.
+ //
+ // To mitigate this, when an M naturally enters the scheduler,
+ // schedule checks if freezing is set and if so stops
+ // execution. This guarantees that while Gs can transition from
+ // running to stopped, they can never transition from stopped
+ // to running.
+ //
+ // The sleep here allows racing Ms that missed freezing and are
+ // about to run a G to complete the transition to running
+ // before we start traceback.
+ usleep(1000)
+ return
+ }
+
// stopwait and preemption requests can be lost
// due to races with concurrently executing threads,
// so try several times
return gp.atomicstatus.CompareAndSwap(_Gpreempted, _Gwaiting)
}
+// stwReason is an enumeration of reasons the world is stopping.
+type stwReason uint8
+
+// Reasons to stop-the-world.
+//
+// Avoid reusing reasons and add new ones instead.
+const (
+ stwUnknown stwReason = iota // "unknown"
+ stwGCMarkTerm // "GC mark termination"
+ stwGCSweepTerm // "GC sweep termination"
+ stwWriteHeapDump // "write heap dump"
+ stwGoroutineProfile // "goroutine profile"
+ stwGoroutineProfileCleanup // "goroutine profile cleanup"
+ stwAllGoroutinesStack // "all goroutines stack trace"
+ stwReadMemStats // "read mem stats"
+ stwAllThreadsSyscall // "AllThreadsSyscall"
+ stwGOMAXPROCS // "GOMAXPROCS"
+ stwStartTrace // "start trace"
+ stwStopTrace // "stop trace"
+ stwForTestCountPagesInUse // "CountPagesInUse (test)"
+ stwForTestReadMetricsSlow // "ReadMetricsSlow (test)"
+ stwForTestReadMemStatsSlow // "ReadMemStatsSlow (test)"
+ stwForTestPageCachePagesLeaked // "PageCachePagesLeaked (test)"
+ stwForTestResetDebugLog // "ResetDebugLog (test)"
+)
+
+func (r stwReason) String() string {
+ return stwReasonStrings[r]
+}
+
+// If you add to this list, also add it to src/internal/trace/parser.go.
+// If you change the values of any of the stw* constants, bump the trace
+// version number and make a copy of this.
+var stwReasonStrings = [...]string{
+ stwUnknown: "unknown",
+ stwGCMarkTerm: "GC mark termination",
+ stwGCSweepTerm: "GC sweep termination",
+ stwWriteHeapDump: "write heap dump",
+ stwGoroutineProfile: "goroutine profile",
+ stwGoroutineProfileCleanup: "goroutine profile cleanup",
+ stwAllGoroutinesStack: "all goroutines stack trace",
+ stwReadMemStats: "read mem stats",
+ stwAllThreadsSyscall: "AllThreadsSyscall",
+ stwGOMAXPROCS: "GOMAXPROCS",
+ stwStartTrace: "start trace",
+ stwStopTrace: "stop trace",
+ stwForTestCountPagesInUse: "CountPagesInUse (test)",
+ stwForTestReadMetricsSlow: "ReadMetricsSlow (test)",
+ stwForTestReadMemStatsSlow: "ReadMemStatsSlow (test)",
+ stwForTestPageCachePagesLeaked: "PageCachePagesLeaked (test)",
+ stwForTestResetDebugLog: "ResetDebugLog (test)",
+}
+
// stopTheWorld stops all P's from executing goroutines, interrupting
// all goroutines at GC safe points and records reason as the reason
// for the stop. On return, only the current goroutine's P is running.
// This is also used by routines that do stack dumps. If the system is
// in panic or being exited, this may not reliably stop all
// goroutines.
-func stopTheWorld(reason string) {
+func stopTheWorld(reason stwReason) {
semacquire(&worldsema)
gp := getg()
- gp.m.preemptoff = reason
+ gp.m.preemptoff = reason.String()
systemstack(func() {
// Mark the goroutine which called stopTheWorld preemptible so its
// stack may be scanned.
// have already completed by the time we exit.
// Don't provide a wait reason because we're still executing.
casGToWaiting(gp, _Grunning, waitReasonStoppingTheWorld)
- stopTheWorldWithSema()
+ stopTheWorldWithSema(reason)
casgstatus(gp, _Gwaiting, _Grunning)
})
}
// startTheWorld undoes the effects of stopTheWorld.
func startTheWorld() {
- systemstack(func() { startTheWorldWithSema(false) })
+ systemstack(func() { startTheWorldWithSema() })
// worldsema must be held over startTheWorldWithSema to ensure
// gomaxprocs cannot change while worldsema is held.
// stopTheWorldGC has the same effect as stopTheWorld, but blocks
// until the GC is not running. It also blocks a GC from starting
// until startTheWorldGC is called.
-func stopTheWorldGC(reason string) {
+func stopTheWorldGC(reason stwReason) {
semacquire(&gcsema)
stopTheWorld(reason)
}
// startTheWorldWithSema and stopTheWorldWithSema.
// Holding worldsema causes any other goroutines invoking
// stopTheWorld to block.
-func stopTheWorldWithSema() {
+func stopTheWorldWithSema(reason stwReason) {
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.STWStart(reason)
+ traceRelease(trace)
+ }
gp := getg()
// If we hold a lock, then we won't be able to stop another M
gp.m.p.ptr().status = _Pgcstop // Pgcstop is only diagnostic.
sched.stopwait--
// try to retake all P's in Psyscall status
+ trace = traceAcquire()
for _, pp := range allp {
s := pp.status
if s == _Psyscall && atomic.Cas(&pp.status, s, _Pgcstop) {
- if traceEnabled() {
- traceGoSysBlock(pp)
- traceProcStop(pp)
+ if trace.ok() {
+ trace.GoSysBlock(pp)
+ trace.ProcStop(pp)
}
pp.syscalltick++
sched.stopwait--
}
}
+ if trace.ok() {
+ traceRelease(trace)
+ }
+
// stop idle P's
now := nanotime()
for {
worldStopped()
}
-func startTheWorldWithSema(emitTraceEvent bool) int64 {
+func startTheWorldWithSema() int64 {
assertWorldStopped()
mp := acquirem() // disable preemption because it can be holding p in a local var
if netpollinited() {
- list := netpoll(0) // non-blocking
+ list, delta := netpoll(0) // non-blocking
injectglist(&list)
+ netpollAdjustWaiters(delta)
}
lock(&sched.lock)
// Capture start-the-world time before doing clean-up tasks.
startTime := nanotime()
- if emitTraceEvent {
- traceGCSTWDone()
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.STWDone()
+ traceRelease(trace)
}
// Wakeup an additional proc in case we have excessive runnable goroutines
case "aix", "darwin", "illumos", "ios", "solaris", "windows":
return true
case "openbsd":
- return GOARCH == "386" || GOARCH == "amd64" || GOARCH == "arm" || GOARCH == "arm64"
+ return GOARCH != "mips64"
}
return false
}
case "aix", "darwin", "plan9", "illumos", "ios", "solaris", "windows":
return true
case "openbsd":
- switch GOARCH {
- case "386", "amd64", "arm", "arm64":
- return true
- }
+ return GOARCH != "mips64"
}
return false
}
// but is somewhat arbitrary.
size := gp.stack.hi
if size == 0 {
- size = 8192 * sys.StackGuardMultiplier
+ size = 16384 * sys.StackGuardMultiplier
}
gp.stack.hi = uintptr(noescape(unsafe.Pointer(&size)))
gp.stack.lo = gp.stack.hi - size + 1024
// Force Ps currently in _Psyscall into _Pidle and hand them
// off to induce safe point function execution.
+ trace := traceAcquire()
for _, p2 := range allp {
s := p2.status
if s == _Psyscall && p2.runSafePointFn == 1 && atomic.Cas(&p2.status, s, _Pidle) {
- if traceEnabled() {
- traceGoSysBlock(p2)
- traceProcStop(p2)
+ if trace.ok() {
+ trace.GoSysBlock(p2)
+ trace.ProcStop(p2)
}
p2.syscalltick++
handoffp(p2)
}
}
+ if trace.ok() {
+ traceRelease(trace)
+ }
// Wait for remaining Ps to run fn.
if wait {
if iscgo || mStackIsSystemAllocated() {
mp.g0 = malg(-1)
} else {
- mp.g0 = malg(8192 * sys.StackGuardMultiplier)
+ mp.g0 = malg(16384 * sys.StackGuardMultiplier)
}
mp.g0.m = mp
// pressed into service as the scheduling stack and current
// goroutine for the duration of the cgo callback.
//
-// When the callback is done with the m, it calls dropm to
-// put the m back on the list.
+// It calls dropm to put the m back on the list,
+// 1. when the callback is done with the m in non-pthread platforms,
+// 2. or when the C thread exiting on pthread platforms.
+//
+// The signal argument indicates whether we're called from a signal
+// handler.
//
//go:nosplit
-func needm() {
+func needm(signal bool) {
if (iscgo || GOOS == "windows") && !cgoHasExtraM {
// Can happen if C/C++ code calls Go from a global ctor.
// Can also happen on Windows if a global ctor uses a
sigsave(&sigmask)
sigblock(false)
- // nilokay=false is safe here because of the invariant above,
+ // getExtraM is safe here because of the invariant above,
// that the extra list always contains or will soon contain
// at least one m.
- mp, last := getExtraM(false)
+ mp, last := getExtraM()
// Set needextram when we've just emptied the list,
// so that the eventual call into cgocallbackg will
osSetupTLS(mp)
// Install g (= m->g0) and set the stack bounds
- // to match the current stack. We don't actually know
- // how big the stack is, like we don't know how big any
- // scheduling stack is, but we assume there's at least 32 kB,
- // which is more than enough for us.
+ // to match the current stack.
setg(mp.g0)
- gp := getg()
- gp.stack.hi = getcallersp() + 1024
- gp.stack.lo = getcallersp() - 32*1024
- gp.stackguard0 = gp.stack.lo + stackGuard
+ sp := getcallersp()
+ callbackUpdateSystemStack(mp, sp, signal)
+
+ // Should mark we are already in Go now.
+ // Otherwise, we may call needm again when we get a signal, before cgocallbackg1,
+ // which means the extram list may be empty, that will cause a deadlock.
+ mp.isExtraInC = false
// Initialize this thread to use the m.
asminit()
sched.ngsys.Add(-1)
}
+// Acquire an extra m and bind it to the C thread when a pthread key has been created.
+//
+//go:nosplit
+func needAndBindM() {
+ needm(false)
+
+ if _cgo_pthread_key_created != nil && *(*uintptr)(_cgo_pthread_key_created) != 0 {
+ cgoBindM()
+ }
+}
+
// newextram allocates m's and puts them on the extra list.
// It is called with a working local m, so that it can do things
// like call schedlock and allocate.
gp.m = mp
mp.curg = gp
mp.isextra = true
+ // mark we are in C by default.
+ mp.isExtraInC = true
mp.lockedInt++
mp.lockedg.set(gp)
gp.lockedm.set(mp)
if raceenabled {
gp.racectx = racegostart(abi.FuncPCABIInternal(newextram) + sys.PCQuantum)
}
- if traceEnabled() {
- traceOneNewExtraM(gp)
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.OneNewExtraM(gp)
+ traceRelease(trace)
}
// put on allg for garbage collector
allgadd(gp)
addExtraM(mp)
}
+// dropm puts the current m back onto the extra list.
+//
+// 1. On systems without pthreads, like Windows
// dropm is called when a cgo callback has called needm but is now
// done with the callback and returning back into the non-Go thread.
-// It puts the current m back onto the extra list.
//
// The main expense here is the call to signalstack to release the
// m's signal stack, and then the call to needm on the next callback
// call. These should typically not be scheduling operations, just a few
// atomics, so the cost should be small.
//
-// TODO(rsc): An alternative would be to allocate a dummy pthread per-thread
-// variable using pthread_key_create. Unlike the pthread keys we already use
-// on OS X, this dummy key would never be read by Go code. It would exist
-// only so that we could register at thread-exit-time destructor.
-// That destructor would put the m back onto the extra list.
-// This is purely a performance optimization. The current version,
-// in which dropm happens on each cgo call, is still correct too.
-// We may have to keep the current version on systems with cgo
-// but without pthreads, like Windows.
+// 2. On systems with pthreads
+// dropm is called while a non-Go thread is exiting.
+// We allocate a pthread per-thread variable using pthread_key_create,
+// to register a thread-exit-time destructor.
+// And store the g into a thread-specific value associated with the pthread key,
+// when first return back to C.
+// So that the destructor would invoke dropm while the non-Go thread is exiting.
+// This is much faster since it avoids expensive signal-related syscalls.
+//
+// This always runs without a P, so //go:nowritebarrierrec is required.
+//
+// This may run with a different stack than was recorded in g0 (there is no
+// call to callbackUpdateSystemStack prior to dropm), so this must be
+// //go:nosplit to avoid the stack bounds check.
+//
+//go:nowritebarrierrec
+//go:nosplit
func dropm() {
// Clear m and g, and return m to the extra list.
// After the call to setg we can only call nosplit functions
setg(nil)
+ // Clear g0 stack bounds to ensure that needm always refreshes the
+ // bounds when reusing this M.
+ g0 := mp.g0
+ g0.stack.hi = 0
+ g0.stack.lo = 0
+ g0.stackguard0 = 0
+ g0.stackguard1 = 0
+
putExtraM(mp)
msigrestore(sigmask)
}
+// bindm store the g0 of the current m into a thread-specific value.
+//
+// We allocate a pthread per-thread variable using pthread_key_create,
+// to register a thread-exit-time destructor.
+// We are here setting the thread-specific value of the pthread key, to enable the destructor.
+// So that the pthread_key_destructor would dropm while the C thread is exiting.
+//
+// And the saved g will be used in pthread_key_destructor,
+// since the g stored in the TLS by Go might be cleared in some platforms,
+// before the destructor invoked, so, we restore g by the stored g, before dropm.
+//
+// We store g0 instead of m, to make the assembly code simpler,
+// since we need to restore g0 in runtime.cgocallback.
+//
+// On systems without pthreads, like Windows, bindm shouldn't be used.
+//
+// NOTE: this always runs without a P, so, nowritebarrierrec required.
+//
+//go:nosplit
+//go:nowritebarrierrec
+func cgoBindM() {
+ if GOOS == "windows" || GOOS == "plan9" {
+ fatal("bindm in unexpected GOOS")
+ }
+ g := getg()
+ if g.m.g0 != g {
+ fatal("the current g is not g0")
+ }
+ if _cgo_bindm != nil {
+ asmcgocall(_cgo_bindm, unsafe.Pointer(g))
+ }
+}
+
// A helper function for EnsureDropM.
func getm() uintptr {
return uintptr(unsafe.Pointer(getg().m))
continue
}
if extraM.CompareAndSwap(old, locked) {
- extraMInUse.Add(1)
return (*m)(unsafe.Pointer(old))
}
osyield_no_g()
// Return an M from the extra M list. Returns last == true if the list becomes
// empty because of this call.
//
+// Spins waiting for an extra M, so caller must ensure that the list always
+// contains or will soon contain at least one M.
+//
//go:nosplit
-func getExtraM(nilokay bool) (mp *m, last bool) {
- mp = lockextra(nilokay)
- if mp == nil {
- unlockextra(nil, 0)
- return nil, true
- }
+func getExtraM() (mp *m, last bool) {
+ mp = lockextra(false)
+ extraMInUse.Add(1)
unlockextra(mp.schedlink.ptr(), -1)
return mp, mp.schedlink.ptr() == nil
}
setThreadCPUProfiler(hz)
}
- if traceEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
// GoSysExit has to happen when we have a P, but before GoStart.
// So we emit it here.
if gp.syscallsp != 0 {
- traceGoSysExit()
+ trace.GoSysExit()
}
- traceGoStart()
+ trace.GoStart()
+ traceRelease(trace)
}
gogo(&gp.sched)
if traceEnabled() || traceShuttingDown() {
gp := traceReader()
if gp != nil {
+ trace := traceAcquire()
casgstatus(gp, _Gwaiting, _Grunnable)
- traceGoUnpark(gp, 0)
+ if trace.ok() {
+ trace.GoUnpark(gp, 0)
+ traceRelease(trace)
+ }
return gp, false, true
}
}
// blocked thread (e.g. it has already returned from netpoll, but does
// not set lastpoll yet), this thread will do blocking netpoll below
// anyway.
- if netpollinited() && netpollWaiters.Load() > 0 && sched.lastpoll.Load() != 0 {
- if list := netpoll(0); !list.empty() { // non-blocking
+ if netpollinited() && netpollAnyWaiters() && sched.lastpoll.Load() != 0 {
+ if list, delta := netpoll(0); !list.empty() { // non-blocking
gp := list.pop()
injectglist(&list)
+ netpollAdjustWaiters(delta)
+ trace := traceAcquire()
casgstatus(gp, _Gwaiting, _Grunnable)
- if traceEnabled() {
- traceGoUnpark(gp, 0)
+ if trace.ok() {
+ trace.GoUnpark(gp, 0)
+ traceRelease(trace)
}
return gp, false, false
}
if node != nil {
pp.gcMarkWorkerMode = gcMarkWorkerIdleMode
gp := node.gp.ptr()
+
+ trace := traceAcquire()
casgstatus(gp, _Gwaiting, _Grunnable)
- if traceEnabled() {
- traceGoUnpark(gp, 0)
+ if trace.ok() {
+ trace.GoUnpark(gp, 0)
+ traceRelease(trace)
}
return gp, false, false
}
// until a callback was triggered.
gp, otherReady := beforeIdle(now, pollUntil)
if gp != nil {
+ trace := traceAcquire()
casgstatus(gp, _Gwaiting, _Grunnable)
- if traceEnabled() {
- traceGoUnpark(gp, 0)
+ if trace.ok() {
+ trace.GoUnpark(gp, 0)
+ traceRelease(trace)
}
return gp, false, false
}
//
// This applies to the following sources of work:
//
- // * Goroutines added to a per-P run queue.
+ // * Goroutines added to the global or a per-P run queue.
// * New/modified-earlier timers on a per-P timer heap.
// * Idle-priority GC work (barring golang.org/issue/19112).
//
//
// See https://go.dev/issue/43997.
- // Check all runqueues once again.
+ // Check global and P runqueues again.
+
+ lock(&sched.lock)
+ if sched.runqsize != 0 {
+ pp, _ := pidlegetSpinning(0)
+ if pp != nil {
+ gp := globrunqget(pp, 0)
+ if gp == nil {
+ throw("global runq empty with non-zero runqsize")
+ }
+ unlock(&sched.lock)
+ acquirep(pp)
+ mp.becomeSpinning()
+ return gp, false, false
+ }
+ }
+ unlock(&sched.lock)
+
pp := checkRunqsNoP(allpSnapshot, idlepMaskSnapshot)
if pp != nil {
acquirep(pp)
// Run the idle worker.
pp.gcMarkWorkerMode = gcMarkWorkerIdleMode
+ trace := traceAcquire()
casgstatus(gp, _Gwaiting, _Grunnable)
- if traceEnabled() {
- traceGoUnpark(gp, 0)
+ if trace.ok() {
+ trace.GoUnpark(gp, 0)
+ traceRelease(trace)
}
return gp, false, false
}
}
// Poll network until next timer.
- if netpollinited() && (netpollWaiters.Load() > 0 || pollUntil != 0) && sched.lastpoll.Swap(0) != 0 {
+ if netpollinited() && (netpollAnyWaiters() || pollUntil != 0) && sched.lastpoll.Swap(0) != 0 {
sched.pollUntil.Store(pollUntil)
if mp.p != 0 {
throw("findrunnable: netpoll with p")
if mp.spinning {
throw("findrunnable: netpoll with spinning")
}
- // Refresh now.
- now = nanotime()
delay := int64(-1)
if pollUntil != 0 {
+ if now == 0 {
+ now = nanotime()
+ }
delay = pollUntil - now
if delay < 0 {
delay = 0
// When using fake time, just poll.
delay = 0
}
- list := netpoll(delay) // block until new work is available
+ list, delta := netpoll(delay) // block until new work is available
+ // Refresh now again, after potentially blocking.
+ now = nanotime()
sched.pollUntil.Store(0)
sched.lastpoll.Store(now)
if faketime != 0 && list.empty() {
unlock(&sched.lock)
if pp == nil {
injectglist(&list)
+ netpollAdjustWaiters(delta)
} else {
acquirep(pp)
if !list.empty() {
gp := list.pop()
injectglist(&list)
+ netpollAdjustWaiters(delta)
+ trace := traceAcquire()
casgstatus(gp, _Gwaiting, _Grunnable)
- if traceEnabled() {
- traceGoUnpark(gp, 0)
+ if trace.ok() {
+ trace.GoUnpark(gp, 0)
+ traceRelease(trace)
}
return gp, false, false
}
if !runqempty(p) {
return true
}
- if netpollinited() && netpollWaiters.Load() > 0 && sched.lastpoll.Load() != 0 {
- if list := netpoll(0); !list.empty() {
+ if netpollinited() && netpollAnyWaiters() && sched.lastpoll.Load() != 0 {
+ if list, delta := netpoll(0); !list.empty() {
injectglist(&list)
+ netpollAdjustWaiters(delta)
return true
}
}
if glist.empty() {
return
}
- if traceEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
for gp := glist.head.ptr(); gp != nil; gp = gp.schedlink.ptr() {
- traceGoUnpark(gp, 0)
+ trace.GoUnpark(gp, 0)
}
+ traceRelease(trace)
}
// Mark all the goroutines as runnable before we put them
gp, inheritTime, tryWakeP := findRunnable() // blocks until work is available
+ if debug.dontfreezetheworld > 0 && freezing.Load() {
+ // See comment in freezetheworld. We don't want to perturb
+ // scheduler state, so we didn't gcstopm in findRunnable, but
+ // also don't want to allow new goroutines to run.
+ //
+ // Deadlock here rather than in the findRunnable loop so if
+ // findRunnable is stuck in a loop we don't perturb that
+ // either.
+ lock(&deadlock)
+ lock(&deadlock)
+ }
+
// This thread is going to run a goroutine and is not spinning anymore,
// so if it was marked as spinning we need to reset it now and potentially
// start a new spinning M.
func park_m(gp *g) {
mp := getg().m
- if traceEnabled() {
- traceGoPark(mp.waittraceev, mp.waittraceskip)
- }
+ trace := traceAcquire()
// N.B. Not using casGToWaiting here because the waitreason is
// set by park_m's caller.
casgstatus(gp, _Grunning, _Gwaiting)
+ if trace.ok() {
+ trace.GoPark(mp.waitTraceBlockReason, mp.waitTraceSkip)
+ traceRelease(trace)
+ }
+
dropg()
if fn := mp.waitunlockf; fn != nil {
mp.waitunlockf = nil
mp.waitlock = nil
if !ok {
- if traceEnabled() {
- traceGoUnpark(gp, 2)
- }
+ trace := traceAcquire()
casgstatus(gp, _Gwaiting, _Grunnable)
+ if trace.ok() {
+ trace.GoUnpark(gp, 2)
+ traceRelease(trace)
+ }
execute(gp, true) // Schedule it back, never returns.
}
}
schedule()
}
-func goschedImpl(gp *g) {
+func goschedImpl(gp *g, preempted bool) {
+ trace := traceAcquire()
status := readgstatus(gp)
if status&^_Gscan != _Grunning {
dumpgstatus(gp)
throw("bad g status")
}
casgstatus(gp, _Grunning, _Grunnable)
+ if trace.ok() {
+ if preempted {
+ trace.GoPreempt()
+ } else {
+ trace.GoSched()
+ }
+ traceRelease(trace)
+ }
+
dropg()
lock(&sched.lock)
globrunqput(gp)
unlock(&sched.lock)
+ if mainStarted {
+ wakep()
+ }
+
schedule()
}
// Gosched continuation on g0.
func gosched_m(gp *g) {
- if traceEnabled() {
- traceGoSched()
- }
- goschedImpl(gp)
+ goschedImpl(gp, false)
}
// goschedguarded is a forbidden-states-avoided version of gosched_m.
func goschedguarded_m(gp *g) {
-
if !canPreemptM(gp.m) {
gogo(&gp.sched) // never return
}
-
- if traceEnabled() {
- traceGoSched()
- }
- goschedImpl(gp)
+ goschedImpl(gp, false)
}
func gopreempt_m(gp *g) {
- if traceEnabled() {
- traceGoPreempt()
- }
- goschedImpl(gp)
+ goschedImpl(gp, true)
}
// preemptPark parks gp and puts it in _Gpreempted.
//
//go:systemstack
func preemptPark(gp *g) {
- if traceEnabled() {
- traceGoPark(traceEvGoBlock, 0)
- }
status := readgstatus(gp)
if status&^_Gscan != _Grunning {
dumpgstatus(gp)
// transitions until we can dropg.
casGToPreemptScan(gp, _Grunning, _Gscan|_Gpreempted)
dropg()
+
+ // Be careful about how we trace this next event. The ordering
+ // is subtle.
+ //
+ // The moment we CAS into _Gpreempted, suspendG could CAS to
+ // _Gwaiting, do its work, and ready the goroutine. All of
+ // this could happen before we even get the chance to emit
+ // an event. The end result is that the events could appear
+ // out of order, and the tracer generally assumes the scheduler
+ // takes care of the ordering between GoPark and GoUnpark.
+ //
+ // The answer here is simple: emit the event while we still hold
+ // the _Gscan bit on the goroutine. We still need to traceAcquire
+ // and traceRelease across the CAS because the tracer could be
+ // what's calling suspendG in the first place, and we want the
+ // CAS and event emission to appear atomic to the tracer.
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.GoPark(traceBlockPreempted, 0)
+ }
casfrom_Gscanstatus(gp, _Gscan|_Gpreempted, _Gpreempted)
+ if trace.ok() {
+ traceRelease(trace)
+ }
schedule()
}
}
func goyield_m(gp *g) {
- if traceEnabled() {
- traceGoPreempt()
- }
+ trace := traceAcquire()
pp := gp.m.p.ptr()
casgstatus(gp, _Grunning, _Grunnable)
+ if trace.ok() {
+ trace.GoPreempt()
+ traceRelease(trace)
+ }
dropg()
runqput(pp, gp, false)
schedule()
if raceenabled {
racegoend()
}
- if traceEnabled() {
- traceGoEnd()
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.GoEnd()
+ traceRelease(trace)
}
mcall(goexit0)
}
//
//go:nosplit
func reentersyscall(pc, sp uintptr) {
+ trace := traceAcquire()
gp := getg()
// Disable preemption because during this function g is in Gsyscall status,
})
}
- if traceEnabled() {
- systemstack(traceGoSysCall)
+ if trace.ok() {
+ systemstack(func() {
+ trace.GoSysCall()
+ traceRelease(trace)
+ })
// systemstack itself clobbers g.sched.{pc,sp} and we might
// need them later when the G is genuinely blocked in a
// syscall
lock(&sched.lock)
if sched.stopwait > 0 && atomic.Cas(&pp.status, _Psyscall, _Pgcstop) {
- if traceEnabled() {
- traceGoSysBlock(pp)
- traceProcStop(pp)
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.GoSysBlock(pp)
+ trace.ProcStop(pp)
+ traceRelease(trace)
}
pp.syscalltick++
if sched.stopwait--; sched.stopwait == 0 {
}
func entersyscallblock_handoff() {
- if traceEnabled() {
- traceGoSysCall()
- traceGoSysBlock(getg().m.p.ptr())
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.GoSysCall()
+ trace.GoSysBlock(getg().m.p.ptr())
+ traceRelease(trace)
}
handoffp(releasep())
}
tryRecordGoroutineProfileWB(gp)
})
}
- if traceEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
if oldp != gp.m.p.ptr() || gp.m.syscalltick != gp.m.p.ptr().syscalltick {
- systemstack(traceGoStart)
+ systemstack(func() {
+ trace.GoStart()
+ })
}
}
// There's a cpu for us, so we can run.
gp.m.p.ptr().syscalltick++
// We need to cas the status and scan before resuming...
casgstatus(gp, _Gsyscall, _Grunning)
+ if trace.ok() {
+ traceRelease(trace)
+ }
// Garbage collector isn't running (since we are),
// so okay to clear syscallsp.
return
}
- if traceEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
// Wait till traceGoSysBlock event is emitted.
// This ensures consistency of the trace (the goroutine is started after it is blocked).
for oldp != nil && oldp.syscalltick == gp.m.syscalltick {
// Tracing code can invoke write barriers that cannot run without a P.
// So instead we remember the syscall exit time and emit the event
// in execute when we have a P.
- gp.trace.sysExitTicks = cputicks()
+ gp.trace.sysExitTime = traceClockNow()
+ traceRelease(trace)
}
gp.m.locks--
var ok bool
systemstack(func() {
ok = exitsyscallfast_pidle()
- if ok && traceEnabled() {
- if oldp != nil {
- // Wait till traceGoSysBlock event is emitted.
- // This ensures consistency of the trace (the goroutine is started after it is blocked).
- for oldp.syscalltick == gp.m.syscalltick {
- osyield()
+ if ok {
+ trace := traceAcquire()
+ if trace.ok() {
+ if oldp != nil {
+ // Wait till traceGoSysBlock event is emitted.
+ // This ensures consistency of the trace (the goroutine is started after it is blocked).
+ for oldp.syscalltick == gp.m.syscalltick {
+ osyield()
+ }
}
+ trace.GoSysExit()
+ traceRelease(trace)
}
- traceGoSysExit()
}
})
if ok {
func exitsyscallfast_reacquired() {
gp := getg()
if gp.m.syscalltick != gp.m.p.ptr().syscalltick {
- if traceEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
// The p was retaken and then enter into syscall again (since gp.m.syscalltick has changed).
// traceGoSysBlock for this syscall was already emitted,
// but here we effectively retake the p from the new syscall running on the same p.
systemstack(func() {
// Denote blocking of the new syscall.
- traceGoSysBlock(gp.m.p.ptr())
+ trace.GoSysBlock(gp.m.p.ptr())
// Denote completion of the current syscall.
- traceGoSysExit()
+ trace.GoSysExit()
+ traceRelease(trace)
})
}
gp.m.p.ptr().syscalltick++
totalSize := uintptr(4*goarch.PtrSize + sys.MinFrameSize) // extra space in case of reads slightly beyond frame
totalSize = alignUp(totalSize, sys.StackAlign)
sp := newg.stack.hi - totalSize
- spArg := sp
if usesLR {
// caller's LR
*(*uintptr)(unsafe.Pointer(sp)) = 0
prepGoExitFrame(sp)
- spArg += sys.MinFrameSize
+ }
+ if GOARCH == "arm64" {
+ // caller's FP
+ *(*uintptr)(unsafe.Pointer(sp - goarch.PtrSize)) = 0
}
memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
if newg.trackingSeq%gTrackingPeriod == 0 {
newg.tracking = true
}
- casgstatus(newg, _Gdead, _Grunnable)
gcController.addScannableStack(pp, int64(newg.stack.hi-newg.stack.lo))
+ // Get a goid and switch to runnable. Make all this atomic to the tracer.
+ trace := traceAcquire()
+ casgstatus(newg, _Gdead, _Grunnable)
if pp.goidcache == pp.goidcacheend {
// Sched.goidgen is the last allocated id,
// this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].
}
newg.goid = pp.goidcache
pp.goidcache++
+ if trace.ok() {
+ trace.GoCreate(newg, newg.startpc)
+ traceRelease(trace)
+ }
+
+ // Set up race context.
if raceenabled {
newg.racectx = racegostart(callerpc)
+ newg.raceignore = 0
if newg.labels != nil {
// See note in proflabel.go on labelSync's role in synchronizing
// with the reads in the signal handler.
racereleasemergeg(newg, unsafe.Pointer(&labelSync))
}
}
- if traceEnabled() {
- traceGoCreate(newg, newg.startpc)
- }
releasem(mp)
return newg
pp.sudogbuf[i] = nil
}
pp.sudogcache = pp.sudogbuf[:0]
+ pp.pinnerCache = nil
for j := range pp.deferpoolbuf {
pp.deferpoolbuf[j] = nil
}
if old < 0 || nprocs <= 0 {
throw("procresize: invalid arg")
}
- if traceEnabled() {
- traceGomaxprocs(nprocs)
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.Gomaxprocs(nprocs)
+ traceRelease(trace)
}
// update statistics
// because p.destroy itself has write barriers, so we
// need to do that from a valid P.
if gp.m.p != 0 {
- if traceEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
// Pretend that we were descheduled
// and then scheduled again to keep
// the trace sane.
- traceGoSched()
- traceProcStop(gp.m.p.ptr())
+ trace.GoSched()
+ trace.ProcStop(gp.m.p.ptr())
+ traceRelease(trace)
}
gp.m.p.ptr().m = 0
}
pp.m = 0
pp.status = _Pidle
acquirep(pp)
- if traceEnabled() {
- traceGoStart()
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.GoStart()
+ traceRelease(trace)
}
}
// from a potentially stale mcache.
pp.mcache.prepareForSweep()
- if traceEnabled() {
- traceProcStart()
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.ProcStart()
+ traceRelease(trace)
}
}
print("releasep: m=", gp.m, " m->p=", gp.m.p.ptr(), " p->m=", hex(pp.m), " p->status=", pp.status, "\n")
throw("releasep: invalid p state")
}
- if traceEnabled() {
- traceProcStop(gp.m.p.ptr())
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.ProcStop(gp.m.p.ptr())
+ traceRelease(trace)
}
gp.m.p = 0
pp.m = 0
lastpoll := sched.lastpoll.Load()
if netpollinited() && lastpoll != 0 && lastpoll+10*1000*1000 < now {
sched.lastpoll.CompareAndSwap(lastpoll, now)
- list := netpoll(0) // non-blocking - returns list of goroutines
+ list, delta := netpoll(0) // non-blocking - returns list of goroutines
if !list.empty() {
// Need to decrement number of idle locked M's
// (pretending that one more is running) before injectglist.
incidlelocked(-1)
injectglist(&list)
incidlelocked(1)
+ netpollAdjustWaiters(delta)
}
}
if GOOS == "netbsd" && needSysmonWorkaround {
// increment nmidle and report deadlock.
incidlelocked(-1)
if atomic.Cas(&pp.status, s, _Pidle) {
- if traceEnabled() {
- traceGoSysBlock(pp)
- traceProcStop(pp)
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.GoSysBlock(pp)
+ trace.ProcStop(pp)
+ traceRelease(trace)
}
n++
pp.syscalltick++