2014-08-19 11:49:59 +04:00
|
|
|
// Copyright 2014 The Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
package runtime
|
|
|
|
|
|
2015-11-02 14:09:24 -05:00
|
|
|
import (
|
2021-04-02 13:24:35 -04:00
|
|
|
"internal/abi"
|
2018-05-25 14:01:25 +02:00
|
|
|
"internal/cpu"
|
2021-06-17 19:10:18 +00:00
|
|
|
"internal/goarch"
|
2015-11-02 14:09:24 -05:00
|
|
|
"runtime/internal/atomic"
|
2015-11-11 12:39:30 -05:00
|
|
|
"runtime/internal/sys"
|
2015-11-02 14:09:24 -05:00
|
|
|
"unsafe"
|
|
|
|
|
)
|
2014-08-21 20:41:09 +04:00
|
|
|
|
2019-04-22 23:01:26 -04:00
|
|
|
// set using cmd/go/internal/modload.ModInfoProg
|
|
|
|
|
var modinfo string
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Goroutine scheduler
|
|
|
|
|
// The scheduler's job is to distribute ready-to-run goroutines over worker threads.
|
|
|
|
|
//
|
|
|
|
|
// The main concepts are:
|
|
|
|
|
// G - goroutine.
|
|
|
|
|
// M - worker thread, or machine.
|
|
|
|
|
// P - processor, a resource that is required to execute Go code.
|
|
|
|
|
// M must have an associated P to execute Go code, however it can be
|
|
|
|
|
// blocked or in a syscall w/o an associated P.
|
|
|
|
|
//
|
|
|
|
|
// Design doc at https://golang.org/s/go11sched.
|
|
|
|
|
|
2015-12-08 15:11:27 +01:00
|
|
|
// Worker thread parking/unparking.
|
|
|
|
|
// We need to balance between keeping enough running worker threads to utilize
|
|
|
|
|
// available hardware parallelism and parking excessive running worker threads
|
|
|
|
|
// to conserve CPU resources and power. This is not simple for two reasons:
|
|
|
|
|
// (1) scheduler state is intentionally distributed (in particular, per-P work
|
|
|
|
|
// queues), so it is not possible to compute global predicates on fast paths;
|
|
|
|
|
// (2) for optimal thread management we would need to know the future (don't park
|
|
|
|
|
// a worker thread when a new goroutine will be readied in near future).
|
|
|
|
|
//
|
|
|
|
|
// Three rejected approaches that would work badly:
|
|
|
|
|
// 1. Centralize all scheduler state (would inhibit scalability).
|
|
|
|
|
// 2. Direct goroutine handoff. That is, when we ready a new goroutine and there
|
|
|
|
|
// is a spare P, unpark a thread and handoff it the thread and the goroutine.
|
|
|
|
|
// This would lead to thread state thrashing, as the thread that readied the
|
|
|
|
|
// goroutine can be out of work the very next moment, we will need to park it.
|
|
|
|
|
// Also, it would destroy locality of computation as we want to preserve
|
|
|
|
|
// dependent goroutines on the same thread; and introduce additional latency.
|
|
|
|
|
// 3. Unpark an additional thread whenever we ready a goroutine and there is an
|
|
|
|
|
// idle P, but don't do handoff. This would lead to excessive thread parking/
|
|
|
|
|
// unparking as the additional threads will instantly park without discovering
|
|
|
|
|
// any work to do.
|
|
|
|
|
//
|
|
|
|
|
// The current approach:
|
2021-02-16 10:52:38 -05:00
|
|
|
//
|
|
|
|
|
// This approach applies to three primary sources of potential work: readying a
|
|
|
|
|
// goroutine, new/modified-earlier timers, and idle-priority GC. See below for
|
|
|
|
|
// additional details.
|
|
|
|
|
//
|
|
|
|
|
// We unpark an additional thread when we submit work if (this is wakep()):
|
|
|
|
|
// 1. There is an idle P, and
|
|
|
|
|
// 2. There are no "spinning" worker threads.
|
|
|
|
|
//
|
|
|
|
|
// A worker thread is considered spinning if it is out of local work and did
|
|
|
|
|
// not find work in the global run queue or netpoller; the spinning state is
|
|
|
|
|
// denoted in m.spinning and in sched.nmspinning. Threads unparked this way are
|
|
|
|
|
// also considered spinning; we don't do goroutine handoff so such threads are
|
|
|
|
|
// out of work initially. Spinning threads spin on looking for work in per-P
|
|
|
|
|
// run queues and timer heaps or from the GC before parking. If a spinning
|
2015-12-08 15:11:27 +01:00
|
|
|
// thread finds work it takes itself out of the spinning state and proceeds to
|
2021-02-16 10:52:38 -05:00
|
|
|
// execution. If it does not find work it takes itself out of the spinning
|
|
|
|
|
// state and then parks.
|
|
|
|
|
//
|
|
|
|
|
// If there is at least one spinning thread (sched.nmspinning>1), we don't
|
|
|
|
|
// unpark new threads when submitting work. To compensate for that, if the last
|
|
|
|
|
// spinning thread finds work and stops spinning, it must unpark a new spinning
|
2022-03-01 15:06:37 -05:00
|
|
|
// thread. This approach smooths out unjustified spikes of thread unparking,
|
2021-02-16 10:52:38 -05:00
|
|
|
// but at the same time guarantees eventual maximal CPU parallelism
|
|
|
|
|
// utilization.
|
|
|
|
|
//
|
|
|
|
|
// The main implementation complication is that we need to be very careful
|
|
|
|
|
// during spinning->non-spinning thread transition. This transition can race
|
|
|
|
|
// with submission of new work, and either one part or another needs to unpark
|
|
|
|
|
// another worker thread. If they both fail to do that, we can end up with
|
|
|
|
|
// semi-persistent CPU underutilization.
|
|
|
|
|
//
|
|
|
|
|
// The general pattern for submission is:
|
|
|
|
|
// 1. Submit work to the local run queue, timer heap, or GC state.
|
|
|
|
|
// 2. #StoreLoad-style memory barrier.
|
|
|
|
|
// 3. Check sched.nmspinning.
|
2015-12-08 15:11:27 +01:00
|
|
|
//
|
2021-02-16 10:52:38 -05:00
|
|
|
// The general pattern for spinning->non-spinning transition is:
|
|
|
|
|
// 1. Decrement nmspinning.
|
|
|
|
|
// 2. #StoreLoad-style memory barrier.
|
|
|
|
|
// 3. Check all per-P work queues and GC for new work.
|
|
|
|
|
//
|
|
|
|
|
// Note that all this complexity does not apply to global run queue as we are
|
|
|
|
|
// not sloppy about thread unparking when submitting to global queue. Also see
|
|
|
|
|
// comments for nmspinning manipulation.
|
|
|
|
|
//
|
|
|
|
|
// How these different sources of work behave varies, though it doesn't affect
|
|
|
|
|
// the synchronization approach:
|
|
|
|
|
// * Ready goroutine: this is an obvious source of work; the goroutine is
|
|
|
|
|
// immediately ready and must run on some thread eventually.
|
|
|
|
|
// * New/modified-earlier timer: The current timer implementation (see time.go)
|
|
|
|
|
// uses netpoll in a thread with no work available to wait for the soonest
|
|
|
|
|
// timer. If there is no thread waiting, we want a new spinning thread to go
|
|
|
|
|
// wait.
|
|
|
|
|
// * Idle-priority GC: The GC wakes a stopped idle thread to contribute to
|
|
|
|
|
// background GC work (note: currently disabled per golang.org/issue/19112).
|
|
|
|
|
// Also see golang.org/issue/44313, as this should be extended to all GC
|
|
|
|
|
// workers.
|
2015-12-08 15:11:27 +01:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
var (
|
2016-02-26 21:57:16 +01:00
|
|
|
m0 m
|
|
|
|
|
g0 g
|
2019-11-04 14:25:22 -08:00
|
|
|
mcache0 *mcache
|
2016-02-26 21:57:16 +01:00
|
|
|
raceprocctx0 uintptr
|
2015-10-18 17:04:05 -07:00
|
|
|
)
|
|
|
|
|
|
2023-01-12 20:25:39 -08:00
|
|
|
// This slice records the initializing tasks that need to be
|
|
|
|
|
// done to start up the runtime. It is built by the linker.
|
|
|
|
|
var runtime_inittasks []*initTask
|
2014-12-22 13:27:53 -05:00
|
|
|
|
2015-04-13 19:31:39 -04:00
|
|
|
// main_init_done is a signal used by cgocallbackg that initialization
|
|
|
|
|
// has been completed. It is made before _cgo_notify_runtime_init_done,
|
|
|
|
|
// so all cgo calls can rely on it existing. When main_init is complete,
|
|
|
|
|
// it is closed, meaning cgocallbackg can reliably receive from it.
|
|
|
|
|
var main_init_done chan bool
|
|
|
|
|
|
2014-12-22 13:27:53 -05:00
|
|
|
//go:linkname main_main main.main
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
func main_main()
|
|
|
|
|
|
runtime: capture runtimeInitTime after nanotime is initialized
CL 36428 changed the way nanotime works so on Darwin and Windows it
now depends on runtime.startNano, which is computed at runtime.init
time. Unfortunately, the `runtimeInitTime = nanotime()` initialization
happened *before* runtime.init, so on these platforms runtimeInitTime
is set incorrectly. The one (and only) consequence of this is that the
start time printed in gctrace lines is bogus:
gc 1 18446653480.186s 0%: 0.092+0.47+0.038 ms clock, 0.37+0.15/0.81/1.8+0.15 ms cpu, 4->4->1 MB, 5 MB goal, 8 P
To fix this, this commit moves the runtimeInitTime initialization to
shortly after runtime.init, at which point nanotime is safe to use.
This also requires changing the condition in newproc1 that currently
uses runtimeInitTime != 0 simply to detect whether or not the main M
has started. Since runtimeInitTime could genuinely be 0 now, this
introduces a separate flag to newproc1.
Fixes #21554.
Change-Id: Id874a4b912d3fa3d22f58d01b31ffb3548266d3b
Reviewed-on: https://go-review.googlesource.com/58690
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rick Hudson <rlh@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
2017-08-24 15:06:26 -04:00
|
|
|
// mainStarted indicates that the main M has started.
|
|
|
|
|
var mainStarted bool
|
|
|
|
|
|
2015-03-26 18:48:42 -04:00
|
|
|
// runtimeInitTime is the nanotime() at which the runtime started.
|
|
|
|
|
var runtimeInitTime int64
|
|
|
|
|
|
2015-12-19 10:17:10 -08:00
|
|
|
// Value to use for signal mask for newly created M's.
|
|
|
|
|
var initSigmask sigset
|
|
|
|
|
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
// The main goroutine.
|
|
|
|
|
func main() {
|
2022-07-20 13:31:10 -04:00
|
|
|
mp := getg().m
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
|
|
|
|
|
// Racectx of m0->g0 is used only as the parent of the main goroutine.
|
|
|
|
|
// It must not be used for anything else.
|
2022-07-20 13:31:10 -04:00
|
|
|
mp.g0.racectx = 0
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
|
|
|
|
|
// Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.
|
|
|
|
|
// Using decimal instead of binary GB and MB because
|
|
|
|
|
// they look nicer in the stack overflow failure message.
|
2021-06-16 23:05:44 +00:00
|
|
|
if goarch.PtrSize == 8 {
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
maxstacksize = 1000000000
|
|
|
|
|
} else {
|
|
|
|
|
maxstacksize = 250000000
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-18 19:15:41 +03:00
|
|
|
// An upper limit for max stack size. Used to avoid random crashes
|
|
|
|
|
// after calling SetMaxStack and trying to allocate a stack that is too big,
|
|
|
|
|
// since stackalloc works with 32-bit sizes.
|
|
|
|
|
maxstackceiling = 2 * maxstacksize
|
|
|
|
|
|
runtime: capture runtimeInitTime after nanotime is initialized
CL 36428 changed the way nanotime works so on Darwin and Windows it
now depends on runtime.startNano, which is computed at runtime.init
time. Unfortunately, the `runtimeInitTime = nanotime()` initialization
happened *before* runtime.init, so on these platforms runtimeInitTime
is set incorrectly. The one (and only) consequence of this is that the
start time printed in gctrace lines is bogus:
gc 1 18446653480.186s 0%: 0.092+0.47+0.038 ms clock, 0.37+0.15/0.81/1.8+0.15 ms cpu, 4->4->1 MB, 5 MB goal, 8 P
To fix this, this commit moves the runtimeInitTime initialization to
shortly after runtime.init, at which point nanotime is safe to use.
This also requires changing the condition in newproc1 that currently
uses runtimeInitTime != 0 simply to detect whether or not the main M
has started. Since runtimeInitTime could genuinely be 0 now, this
introduces a separate flag to newproc1.
Fixes #21554.
Change-Id: Id874a4b912d3fa3d22f58d01b31ffb3548266d3b
Reviewed-on: https://go-review.googlesource.com/58690
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rick Hudson <rlh@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
2017-08-24 15:06:26 -04:00
|
|
|
// Allow newproc to start new Ms.
|
|
|
|
|
mainStarted = true
|
2015-03-26 18:48:42 -04:00
|
|
|
|
2018-03-31 23:14:17 +02:00
|
|
|
if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon
|
|
|
|
|
systemstack(func() {
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
newm(sysmon, nil, -1)
|
2018-03-31 23:14:17 +02:00
|
|
|
})
|
|
|
|
|
}
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
|
|
|
|
|
// Lock the main goroutine onto this, the main OS thread,
|
2016-03-01 23:21:55 +00:00
|
|
|
// during initialization. Most programs won't care, but a few
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
// do require certain calls to be made by the main thread.
|
|
|
|
|
// Those can arrange for main.main to run in the main thread
|
|
|
|
|
// by calling runtime.LockOSThread during initialization
|
|
|
|
|
// to preserve the lock.
|
|
|
|
|
lockOSThread()
|
|
|
|
|
|
2022-07-20 13:31:10 -04:00
|
|
|
if mp != &m0 {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("runtime.main not on m0")
|
runtime: use traceback to traverse defer structures
This makes the GC and the stack copying agree about how
to interpret the defer structures. Previously, only the stack
copying treated them precisely.
This removes an untyped memory allocation and fixes
at least three copystack bugs.
To make sure the GC can find the deferred argument
frame until it has been copied, keep a Defer on the defer list
during its execution.
In addition to making it possible to remove the untyped
memory allocation, keeping the Defer on the list fixes
two races between copystack and execution of defers
(in both gopanic and Goexit). The problem is that once
the defer has been taken off the list, a stack copy that
happens before the deferred arguments have been copied
back to the stack will not update the arguments correctly.
The new tests TestDeferPtrsPanic and TestDeferPtrsGoexit
(variations on the existing TestDeferPtrs) pass now but
failed before this CL.
In addition to those fixes, keeping the Defer on the list
helps correct a dangling pointer error during copystack.
The traceback routines walk the Defer chain to provide
information about where a panic may resume execution.
When the executing Defer was not on the Defer chain
but instead linked from the Panic chain, the traceback
had to walk the Panic chain too. But Panic structs are
on the stack and being updated by copystack.
Traceback's use of the Panic chain while copystack is
updating those structs means that it can follow an
updated pointer and find itself reading from the new stack.
The new stack is usually all zeros, so it sees an incorrect
early end to the chain. The new TestPanicUseStack makes
this happen at tip and dies when adjustdefers finds an
unexpected argp. The new StackCopyPoison mode
causes an earlier bad dereference instead.
By keeping the Defer on the list, traceback can avoid
walking the Panic chain at all, making it okay for copystack
to update the Panics.
We'd have the same problem for any Defers on the stack.
There was only one: gopanic's dabort. Since we are not
taking the executing Defer off the chain, we can use it
to do what dabort was doing, and then there are no
Defers on the stack ever, so it is okay for traceback to use
the Defer chain even while copystack is executing:
copystack cannot modify the Defer chain.
LGTM=khr
R=khr
CC=dvyukov, golang-codereviews, iant, rlh
https://golang.org/cl/141490043
2014-09-16 10:36:38 -04:00
|
|
|
}
|
|
|
|
|
|
runtime: implement GODEBUG=inittrace=1 support
Setting inittrace=1 causes the runtime to emit a single line to standard error for
each package with init work, summarizing the execution time and memory allocation.
The emitted debug information for init functions can be used to find bottlenecks
or regressions in Go startup performance.
Packages with no init function work (user defined or compiler generated) are omitted.
Tracing plugin inits is not supported as they can execute concurrently. This would
make the implementation of tracing more complex while adding support for a very rare
use case. Plugin inits can be traced separately by testing a main package importing
the plugins package imports explicitly.
$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...
Inspired by stapelberg@google.com who instrumented doInit
in a prototype to measure init times with GDB.
Fixes #41378
Change-Id: Ic37c6a0cfc95488de9e737f5e346b8dbb39174e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/254659
Trust: Martin Möhrmann <moehrmann@google.com>
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2020-09-14 16:55:34 +02:00
|
|
|
// Record when the world started.
|
|
|
|
|
// Must be before doInit for tracing init.
|
|
|
|
|
runtimeInitTime = nanotime()
|
|
|
|
|
if runtimeInitTime == 0 {
|
2017-10-25 11:13:23 -04:00
|
|
|
throw("nanotime returning zero")
|
|
|
|
|
}
|
runtime: use traceback to traverse defer structures
This makes the GC and the stack copying agree about how
to interpret the defer structures. Previously, only the stack
copying treated them precisely.
This removes an untyped memory allocation and fixes
at least three copystack bugs.
To make sure the GC can find the deferred argument
frame until it has been copied, keep a Defer on the defer list
during its execution.
In addition to making it possible to remove the untyped
memory allocation, keeping the Defer on the list fixes
two races between copystack and execution of defers
(in both gopanic and Goexit). The problem is that once
the defer has been taken off the list, a stack copy that
happens before the deferred arguments have been copied
back to the stack will not update the arguments correctly.
The new tests TestDeferPtrsPanic and TestDeferPtrsGoexit
(variations on the existing TestDeferPtrs) pass now but
failed before this CL.
In addition to those fixes, keeping the Defer on the list
helps correct a dangling pointer error during copystack.
The traceback routines walk the Defer chain to provide
information about where a panic may resume execution.
When the executing Defer was not on the Defer chain
but instead linked from the Panic chain, the traceback
had to walk the Panic chain too. But Panic structs are
on the stack and being updated by copystack.
Traceback's use of the Panic chain while copystack is
updating those structs means that it can follow an
updated pointer and find itself reading from the new stack.
The new stack is usually all zeros, so it sees an incorrect
early end to the chain. The new TestPanicUseStack makes
this happen at tip and dies when adjustdefers finds an
unexpected argp. The new StackCopyPoison mode
causes an earlier bad dereference instead.
By keeping the Defer on the list, traceback can avoid
walking the Panic chain at all, making it okay for copystack
to update the Panics.
We'd have the same problem for any Defers on the stack.
There was only one: gopanic's dabort. Since we are not
taking the executing Defer off the chain, we can use it
to do what dabort was doing, and then there are no
Defers on the stack ever, so it is okay for traceback to use
the Defer chain even while copystack is executing:
copystack cannot modify the Defer chain.
LGTM=khr
R=khr
CC=dvyukov, golang-codereviews, iant, rlh
https://golang.org/cl/141490043
2014-09-16 10:36:38 -04:00
|
|
|
|
runtime: implement GODEBUG=inittrace=1 support
Setting inittrace=1 causes the runtime to emit a single line to standard error for
each package with init work, summarizing the execution time and memory allocation.
The emitted debug information for init functions can be used to find bottlenecks
or regressions in Go startup performance.
Packages with no init function work (user defined or compiler generated) are omitted.
Tracing plugin inits is not supported as they can execute concurrently. This would
make the implementation of tracing more complex while adding support for a very rare
use case. Plugin inits can be traced separately by testing a main package importing
the plugins package imports explicitly.
$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...
Inspired by stapelberg@google.com who instrumented doInit
in a prototype to measure init times with GDB.
Fixes #41378
Change-Id: Ic37c6a0cfc95488de9e737f5e346b8dbb39174e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/254659
Trust: Martin Möhrmann <moehrmann@google.com>
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2020-09-14 16:55:34 +02:00
|
|
|
if debug.inittrace != 0 {
|
|
|
|
|
inittrace.id = getg().goid
|
|
|
|
|
inittrace.active = true
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 20:25:39 -08:00
|
|
|
doInit(runtime_inittasks) // Must be before defer.
|
runtime: implement GODEBUG=inittrace=1 support
Setting inittrace=1 causes the runtime to emit a single line to standard error for
each package with init work, summarizing the execution time and memory allocation.
The emitted debug information for init functions can be used to find bottlenecks
or regressions in Go startup performance.
Packages with no init function work (user defined or compiler generated) are omitted.
Tracing plugin inits is not supported as they can execute concurrently. This would
make the implementation of tracing more complex while adding support for a very rare
use case. Plugin inits can be traced separately by testing a main package importing
the plugins package imports explicitly.
$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...
Inspired by stapelberg@google.com who instrumented doInit
in a prototype to measure init times with GDB.
Fixes #41378
Change-Id: Ic37c6a0cfc95488de9e737f5e346b8dbb39174e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/254659
Trust: Martin Möhrmann <moehrmann@google.com>
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2020-09-14 16:55:34 +02:00
|
|
|
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
// Defer unlock so that runtime.Goexit during init does the unlock too.
|
|
|
|
|
needUnlock := true
|
|
|
|
|
defer func() {
|
|
|
|
|
if needUnlock {
|
|
|
|
|
unlockOSThread()
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2015-03-05 16:04:17 -05:00
|
|
|
gcenable()
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
|
2015-04-13 19:31:39 -04:00
|
|
|
main_init_done = make(chan bool)
|
2014-11-11 17:08:33 -05:00
|
|
|
if iscgo {
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
if _cgo_pthread_key_created == nil {
|
|
|
|
|
throw("_cgo_pthread_key_created missing")
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-11 17:08:33 -05:00
|
|
|
if _cgo_thread_start == nil {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("_cgo_thread_start missing")
|
2014-11-11 17:08:33 -05:00
|
|
|
}
|
2014-11-21 15:59:22 +11:00
|
|
|
if GOOS != "windows" {
|
|
|
|
|
if _cgo_setenv == nil {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("_cgo_setenv missing")
|
2014-11-21 15:59:22 +11:00
|
|
|
}
|
|
|
|
|
if _cgo_unsetenv == nil {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("_cgo_unsetenv missing")
|
2014-11-21 15:59:22 +11:00
|
|
|
}
|
2014-11-11 17:08:33 -05:00
|
|
|
}
|
2015-03-25 17:50:35 -07:00
|
|
|
if _cgo_notify_runtime_init_done == nil {
|
|
|
|
|
throw("_cgo_notify_runtime_init_done missing")
|
|
|
|
|
}
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
|
|
|
|
|
// Set the x_crosscall2_ptr C function pointer variable point to crosscall2.
|
|
|
|
|
if set_crosscall2 == nil {
|
|
|
|
|
throw("set_crosscall2 missing")
|
|
|
|
|
}
|
|
|
|
|
set_crosscall2()
|
|
|
|
|
|
2017-06-15 10:51:15 -04:00
|
|
|
// Start the template thread in case we enter Go from
|
|
|
|
|
// a C-created thread and need to create a new thread.
|
|
|
|
|
startTemplateThread()
|
2015-04-27 17:32:23 +10:00
|
|
|
cgocall(_cgo_notify_runtime_init_done, nil)
|
2014-11-11 17:08:33 -05:00
|
|
|
}
|
|
|
|
|
|
2023-01-12 20:25:39 -08:00
|
|
|
// Run the initializing tasks. Depending on build mode this
|
|
|
|
|
// 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() {
|
|
|
|
|
doInit(m.inittasks)
|
|
|
|
|
}
|
2019-02-05 16:22:38 -08:00
|
|
|
|
runtime: implement GODEBUG=inittrace=1 support
Setting inittrace=1 causes the runtime to emit a single line to standard error for
each package with init work, summarizing the execution time and memory allocation.
The emitted debug information for init functions can be used to find bottlenecks
or regressions in Go startup performance.
Packages with no init function work (user defined or compiler generated) are omitted.
Tracing plugin inits is not supported as they can execute concurrently. This would
make the implementation of tracing more complex while adding support for a very rare
use case. Plugin inits can be traced separately by testing a main package importing
the plugins package imports explicitly.
$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...
Inspired by stapelberg@google.com who instrumented doInit
in a prototype to measure init times with GDB.
Fixes #41378
Change-Id: Ic37c6a0cfc95488de9e737f5e346b8dbb39174e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/254659
Trust: Martin Möhrmann <moehrmann@google.com>
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2020-09-14 16:55:34 +02:00
|
|
|
// Disable init tracing after main init done to avoid overhead
|
|
|
|
|
// of collecting statistics in malloc and newproc
|
|
|
|
|
inittrace.active = false
|
|
|
|
|
|
2015-04-13 19:31:39 -04:00
|
|
|
close(main_init_done)
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
|
|
|
|
|
needUnlock = false
|
|
|
|
|
unlockOSThread()
|
|
|
|
|
|
2015-04-16 16:05:52 -04:00
|
|
|
if isarchive || islibrary {
|
|
|
|
|
// A program compiled with -buildmode=c-archive or c-shared
|
|
|
|
|
// has a main, but it is not executed.
|
2015-04-09 15:09:52 -04:00
|
|
|
return
|
|
|
|
|
}
|
2019-02-05 16:22:38 -08:00
|
|
|
fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
|
2016-09-14 14:47:12 -04:00
|
|
|
fn()
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
if raceenabled {
|
2021-10-08 11:51:40 -04:00
|
|
|
runExitHooks(0) // run hooks now, since racefini does not return
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
racefini()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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.
|
2017-04-19 07:32:34 -07:00
|
|
|
// Once it does, it will exit. See issues 3934 and 20018.
|
2022-07-14 17:29:29 -04:00
|
|
|
if runningPanicDefers.Load() != 0 {
|
2017-04-19 07:32:34 -07:00
|
|
|
// Running deferred functions should not take long.
|
|
|
|
|
for c := 0; c < 1000; c++ {
|
2022-07-14 17:29:29 -04:00
|
|
|
if runningPanicDefers.Load() == 0 {
|
2017-04-19 07:32:34 -07:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
Gosched()
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-14 17:36:59 -04:00
|
|
|
if panicking.Load() != 0 {
|
2018-03-06 21:28:24 -08:00
|
|
|
gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1)
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
}
|
2021-10-08 11:51:40 -04:00
|
|
|
runExitHooks(0)
|
liblink, runtime: diagnose and fix C code running on Go stack
This CL contains compiler+runtime changes that detect C code
running on Go (not g0, not gsignal) stacks, and it contains
corrections for what it detected.
The detection works by changing the C prologue to use a different
stack guard word in the G than Go prologue does. On the g0 and
gsignal stacks, that stack guard word is set to the usual
stack guard value. But on ordinary Go stacks, that stack
guard word is set to ^0, which will make any stack split
check fail. The C prologue then calls morestackc instead
of morestack, and morestackc aborts the program with
a message about running C code on a Go stack.
This check catches all C code running on the Go stack
except NOSPLIT code. The NOSPLIT code is allowed,
so the check is complete. Since it is a dynamic check,
the code must execute to be caught. But unlike the static
checks we've been using in cmd/ld, the dynamic check
works with function pointers and other indirect calls.
For example it caught sigpanic being pushed onto Go
stacks in the signal handlers.
Fixes #8667.
LGTM=khr, iant
R=golang-codereviews, khr, iant
CC=golang-codereviews, r
https://golang.org/cl/133700043
2014-09-08 14:05:23 -04:00
|
|
|
|
|
|
|
|
exit(0)
|
|
|
|
|
for {
|
|
|
|
|
var x *int32
|
|
|
|
|
*x = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-10 17:26:26 +03:00
|
|
|
// os_beforeExit is called from os.Exit(0).
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2015-02-10 17:26:26 +03:00
|
|
|
//go:linkname os_beforeExit os.runtime_beforeExit
|
2021-10-08 11:51:40 -04:00
|
|
|
func os_beforeExit(exitCode int) {
|
|
|
|
|
runExitHooks(exitCode)
|
|
|
|
|
if exitCode == 0 && raceenabled {
|
2015-02-10 17:26:26 +03:00
|
|
|
racefini()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-29 11:08:10 +04:00
|
|
|
// start forcegc helper goroutine
|
|
|
|
|
func init() {
|
2014-09-02 19:18:46 -04:00
|
|
|
go forcegchelper()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func forcegchelper() {
|
|
|
|
|
forcegc.g = getg()
|
runtime: static lock ranking for the runtime (enabled by GOEXPERIMENT)
I took some of the infrastructure from Austin's lock logging CR
https://go-review.googlesource.com/c/go/+/192704 (with deadlock
detection from the logs), and developed a setup to give static lock
ranking for runtime locks.
Static lock ranking establishes a documented total ordering among locks,
and then reports an error if the total order is violated. This can
happen if a deadlock happens (by acquiring a sequence of locks in
different orders), or if just one side of a possible deadlock happens.
Lock ordering deadlocks cannot happen as long as the lock ordering is
followed.
Along the way, I found a deadlock involving the new timer code, which Ian fixed
via https://go-review.googlesource.com/c/go/+/207348, as well as two other
potential deadlocks.
See the constants at the top of runtime/lockrank.go to show the static
lock ranking that I ended up with, along with some comments. This is
great documentation of the current intended lock ordering when acquiring
multiple locks in the runtime.
I also added an array lockPartialOrder[] which shows and enforces the
current partial ordering among locks (which is embedded within the total
ordering). This is more specific about the dependencies among locks.
I don't try to check the ranking within a lock class with multiple locks
that can be acquired at the same time (i.e. check the ranking when
multiple hchan locks are acquired).
Currently, I am doing a lockInit() call to set the lock rank of most
locks. Any lock that is not otherwise initialized is assumed to be a
leaf lock (a very high rank lock), so that eliminates the need to do
anything for a bunch of locks (including all architecture-dependent
locks). For two locks, root.lock and notifyList.lock (only in the
runtime/sema.go file), it is not as easy to do lock initialization, so
instead, I am passing the lock rank with the lock calls.
For Windows compilation, I needed to increase the StackGuard size from
896 to 928 because of the new lock-rank checking functions.
Checking of the static lock ranking is enabled by setting
GOEXPERIMENT=staticlockranking before doing a run.
To make sure that the static lock ranking code has no overhead in memory
or CPU when not enabled by GOEXPERIMENT, I changed 'go build/install' so
that it defines a build tag (with the same name) whenever any experiment
has been baked into the toolchain (by checking Expstring()). This allows
me to avoid increasing the size of the 'mutex' type when static lock
ranking is not enabled.
Fixes #38029
Change-Id: I154217ff307c47051f8dae9c2a03b53081acd83a
Reviewed-on: https://go-review.googlesource.com/c/go/+/207619
Reviewed-by: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-11-13 17:34:47 -08:00
|
|
|
lockInit(&forcegc.lock, lockRankForcegc)
|
2014-09-02 19:18:46 -04:00
|
|
|
for {
|
|
|
|
|
lock(&forcegc.lock)
|
2022-08-25 03:03:35 +08:00
|
|
|
if forcegc.idle.Load() {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("forcegc: phase error")
|
2014-09-02 19:18:46 -04:00
|
|
|
}
|
2022-08-25 03:03:35 +08:00
|
|
|
forcegc.idle.Store(true)
|
2020-05-05 01:43:57 +00:00
|
|
|
goparkunlock(&forcegc.lock, waitReasonForceGCIdle, traceEvGoBlock, 1)
|
2014-09-02 19:18:46 -04:00
|
|
|
// this goroutine is explicitly resumed by sysmon
|
|
|
|
|
if debug.gctrace > 0 {
|
|
|
|
|
println("GC forced")
|
2014-08-29 11:08:10 +04:00
|
|
|
}
|
2017-01-09 11:35:42 -05:00
|
|
|
// Time-triggered, fully concurrent.
|
2018-08-13 16:14:19 -04:00
|
|
|
gcStart(gcTrigger{kind: gcTriggerTime, now: nanotime()})
|
2014-09-02 19:18:46 -04:00
|
|
|
}
|
2014-08-29 11:08:10 +04:00
|
|
|
}
|
|
|
|
|
|
2016-03-01 23:21:55 +00:00
|
|
|
// Gosched yields the processor, allowing other goroutines to run. It does not
|
2014-08-19 11:49:59 +04:00
|
|
|
// suspend the current goroutine, so execution resumes automatically.
|
2023-03-02 17:09:22 -05:00
|
|
|
//
|
|
|
|
|
//go:nosplit
|
2014-08-19 11:49:59 +04:00
|
|
|
func Gosched() {
|
2018-05-20 00:56:36 +02:00
|
|
|
checkTimeouts()
|
2014-09-03 11:35:22 -04:00
|
|
|
mcall(gosched_m)
|
2014-08-19 11:49:59 +04:00
|
|
|
}
|
2014-08-21 20:41:09 +04:00
|
|
|
|
2017-02-02 11:53:41 -05:00
|
|
|
// goschedguarded yields the processor like gosched, but also checks
|
|
|
|
|
// for forbidden states and opts out of the yield in those cases.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2017-02-02 11:53:41 -05:00
|
|
|
//go:nosplit
|
2016-11-10 16:03:47 -05:00
|
|
|
func goschedguarded() {
|
2017-02-02 11:53:41 -05:00
|
|
|
mcall(goschedguarded_m)
|
2016-11-10 16:03:47 -05:00
|
|
|
}
|
|
|
|
|
|
runtime: tweak bgsweep "low-priority" heuristic
Currently bgsweep attempts to be a low-priority background goroutine
that runs mainly when the application is mostly idle. To avoid
complicating the scheduler further, it achieves this with a simple
heuristic: call Gosched after each span swept. While this is somewhat
inefficient as there's scheduling overhead on each iteration, it's
mostly fine because it tends to just come out of idle time anyway. In a
busy system, the call to Gosched quickly puts bgsweep at the back of
scheduler queues.
However, what's problematic about this heuristic is the number of
tracing events it produces. Average span sweeping latencies have been
measured as low as 30 ns, so every 30 ns in the sweep phase, with
available idle time, there would be a few trace events emitted. This
could result in an overwhelming number, making traces much larger than
they need to be. It also pollutes other observability tools, like the
scheduling latencies runtime metric, because bgsweep stays runnable the
whole time.
This change fixes these problems with two modifications to the
heursitic:
1. Check if there are any idle Ps before yielding. If there are, don't
yield.
2. Sweep at least 10 spans before trying to yield.
(1) is doing most of the work here. This change assumes that the
presence of idle Ps means that there is available CPU time, so bgsweep
is already making use of idle time and there's no reason it should stop.
This will have the biggest impact on the aforementioned issues.
(2) is a mitigation for the case where GOMAXPROCS=1, because we won't
ever observe a zero idle P count. It does mean that bgsweep is a little
bit higher priority than before because it yields its time less often,
so it could interfere with goroutine scheduling latencies more. However,
by sweeping 10 spans before volunteering time, we directly reduce trace
event production by 90% in all cases. The impact on scheduling latencies
should be fairly minimal, as sweeping a span is already so fast, that
sweeping 10 is unlikely to make a dent in any meaningful end-to-end
latency. In fact, it may even improve application latencies overall by
freeing up spans and sweep work from goroutines allocating memory. It
may be worth considering pushing this number higher in the future.
Another reason to do (2) is to reduce contention on npidle, which will
be checked as part of (1), but this is a fairly minor concern. The main
reason is to capture the GOMAXPROCS=1 case.
Fixes #54767.
Change-Id: I4361400f17197b8ab84c01f56203f20575b29fc6
Reviewed-on: https://go-review.googlesource.com/c/go/+/429615
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
2022-09-08 20:59:02 +00:00
|
|
|
// goschedIfBusy yields the processor like gosched, but only does so if
|
|
|
|
|
// there are no idle Ps or if we're on the only P and there's nothing in
|
|
|
|
|
// the run queue. In both cases, there is freely available idle time.
|
|
|
|
|
//
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func goschedIfBusy() {
|
2022-11-01 17:27:41 -04:00
|
|
|
gp := getg()
|
|
|
|
|
// Call gosched if gp.preempt is set; we may be in a tight loop that
|
|
|
|
|
// doesn't otherwise yield.
|
|
|
|
|
if !gp.preempt && sched.npidle.Load() > 0 {
|
runtime: tweak bgsweep "low-priority" heuristic
Currently bgsweep attempts to be a low-priority background goroutine
that runs mainly when the application is mostly idle. To avoid
complicating the scheduler further, it achieves this with a simple
heuristic: call Gosched after each span swept. While this is somewhat
inefficient as there's scheduling overhead on each iteration, it's
mostly fine because it tends to just come out of idle time anyway. In a
busy system, the call to Gosched quickly puts bgsweep at the back of
scheduler queues.
However, what's problematic about this heuristic is the number of
tracing events it produces. Average span sweeping latencies have been
measured as low as 30 ns, so every 30 ns in the sweep phase, with
available idle time, there would be a few trace events emitted. This
could result in an overwhelming number, making traces much larger than
they need to be. It also pollutes other observability tools, like the
scheduling latencies runtime metric, because bgsweep stays runnable the
whole time.
This change fixes these problems with two modifications to the
heursitic:
1. Check if there are any idle Ps before yielding. If there are, don't
yield.
2. Sweep at least 10 spans before trying to yield.
(1) is doing most of the work here. This change assumes that the
presence of idle Ps means that there is available CPU time, so bgsweep
is already making use of idle time and there's no reason it should stop.
This will have the biggest impact on the aforementioned issues.
(2) is a mitigation for the case where GOMAXPROCS=1, because we won't
ever observe a zero idle P count. It does mean that bgsweep is a little
bit higher priority than before because it yields its time less often,
so it could interfere with goroutine scheduling latencies more. However,
by sweeping 10 spans before volunteering time, we directly reduce trace
event production by 90% in all cases. The impact on scheduling latencies
should be fairly minimal, as sweeping a span is already so fast, that
sweeping 10 is unlikely to make a dent in any meaningful end-to-end
latency. In fact, it may even improve application latencies overall by
freeing up spans and sweep work from goroutines allocating memory. It
may be worth considering pushing this number higher in the future.
Another reason to do (2) is to reduce contention on npidle, which will
be checked as part of (1), but this is a fairly minor concern. The main
reason is to capture the GOMAXPROCS=1 case.
Fixes #54767.
Change-Id: I4361400f17197b8ab84c01f56203f20575b29fc6
Reviewed-on: https://go-review.googlesource.com/c/go/+/429615
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
2022-09-08 20:59:02 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
mcall(gosched_m)
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-18 17:53:12 -04:00
|
|
|
// Puts the current goroutine into a waiting state and calls unlockf on the
|
|
|
|
|
// system stack.
|
|
|
|
|
//
|
2014-08-21 20:41:09 +04:00
|
|
|
// If unlockf returns false, the goroutine is resumed.
|
2020-09-18 17:53:12 -04:00
|
|
|
//
|
runtime: never pass stack pointers to gopark
gopark calls the unlock function after setting the G to _Gwaiting.
This means it's generally unsafe to access the G's stack from the
unlock function because the G may start running on another P. Once we
start shrinking stacks concurrently, a stack shrink could also move
the stack the moment after it enters _Gwaiting and before the unlock
function is called.
Document this restriction and fix the two places where we currently
violate it.
This is unlikely to be a problem in practice for these two places
right now, but they're already skating on thin ice. For example, the
following sequence could in principle cause corruption, deadlock, or a
panic in the select code:
On M1/P1:
1. G1 selects on channels A and B.
2. selectgoImpl calls gopark.
3. gopark puts G1 in _Gwaiting.
4. gopark calls selparkcommit.
5. selparkcommit releases the lock on channel A.
On M2/P2:
6. G2 sends to channel A.
7. The send puts G1 in _Grunnable and puts it on P2's run queue.
8. The scheduler runs, selects G1, puts it in _Grunning, and resumes G1.
9. On G1, the sellock immediately following the gopark gets called.
10. sellock grows and moves the stack.
On M1/P1:
11. selparkcommit continues to scan the lock order for the next
channel to unlock, but it's now reading from a freed (and possibly
reused) stack.
This shouldn't happen in practice because step 10 isn't the first call
to sellock, so the stack should already be big enough. However, once
we start shrinking stacks concurrently, this reasoning won't work any
more.
For #12967.
Change-Id: I3660c5be37e5be9f87433cb8141bdfdf37fadc4c
Reviewed-on: https://go-review.googlesource.com/20038
Reviewed-by: Rick Hudson <rlh@golang.org>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-02-26 10:50:54 -05:00
|
|
|
// unlockf must not access this G's stack, as it may be moved between
|
|
|
|
|
// the call to gopark and the call to unlockf.
|
2020-09-18 17:53:12 -04:00
|
|
|
//
|
|
|
|
|
// Note that because unlockf is called after putting the G into a waiting
|
|
|
|
|
// state, the G may have already been readied by the time unlockf is called
|
|
|
|
|
// unless there is external synchronization preventing the G from being
|
|
|
|
|
// readied. If unlockf returns false, it must guarantee that the G cannot be
|
|
|
|
|
// externally readied.
|
|
|
|
|
//
|
|
|
|
|
// 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.
|
2018-03-06 21:28:24 -08:00
|
|
|
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) {
|
2018-05-20 00:56:36 +02:00
|
|
|
if reason != waitReasonSleep {
|
|
|
|
|
checkTimeouts() // timeouts may expire while two goroutines keep the scheduler busy
|
|
|
|
|
}
|
2014-08-21 20:41:09 +04:00
|
|
|
mp := acquirem()
|
|
|
|
|
gp := mp.curg
|
2014-09-04 14:19:50 -04:00
|
|
|
status := readgstatus(gp)
|
|
|
|
|
if status != _Grunning && status != _Gscanrunning {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("gopark: bad g status")
|
2014-08-21 20:41:09 +04:00
|
|
|
}
|
|
|
|
|
mp.waitlock = lock
|
2019-05-13 11:26:26 -04:00
|
|
|
mp.waitunlockf = unlockf
|
2014-08-21 20:41:09 +04:00
|
|
|
gp.waitreason = reason
|
2014-12-12 18:41:57 +01:00
|
|
|
mp.waittraceev = traceEv
|
2015-02-21 21:01:40 +03:00
|
|
|
mp.waittraceskip = traceskip
|
2014-08-21 20:41:09 +04:00
|
|
|
releasem(mp)
|
|
|
|
|
// can't do anything that might move the G between Ms here.
|
2014-09-03 11:35:22 -04:00
|
|
|
mcall(park_m)
|
2014-08-21 20:41:09 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Puts the current goroutine into a waiting state and unlocks the lock.
|
|
|
|
|
// The goroutine can be made runnable again by calling goready(gp).
|
2018-03-06 21:28:24 -08:00
|
|
|
func goparkunlock(lock *mutex, reason waitReason, traceEv byte, traceskip int) {
|
2015-02-21 21:01:40 +03:00
|
|
|
gopark(parkunlock_c, unsafe.Pointer(lock), reason, traceEv, traceskip)
|
2014-08-21 20:41:09 +04:00
|
|
|
}
|
|
|
|
|
|
2015-02-21 21:01:40 +03:00
|
|
|
func goready(gp *g, traceskip int) {
|
[dev.cc] runtime: delete scalararg, ptrarg; rename onM to systemstack
Scalararg and ptrarg are not "signal safe".
Go code filling them out can be interrupted by a signal,
and then the signal handler runs, and if it also ends up
in Go code that uses scalararg or ptrarg, now the old
values have been smashed.
For the pieces of code that do need to run in a signal handler,
we introduced onM_signalok, which is really just onM
except that the _signalok is meant to convey that the caller
asserts that scalarg and ptrarg will be restored to their old
values after the call (instead of the usual behavior, zeroing them).
Scalararg and ptrarg are also untyped and therefore error-prone.
Go code can always pass a closure instead of using scalararg
and ptrarg; they were only really necessary for C code.
And there's no more C code.
For all these reasons, delete scalararg and ptrarg, converting
the few remaining references to use closures.
Once those are gone, there is no need for a distinction between
onM and onM_signalok, so replace both with a single function
equivalent to the current onM_signalok (that is, it can be called
on any of the curg, g0, and gsignal stacks).
The name onM and the phrase 'm stack' are misnomers,
because on most system an M has two system stacks:
the main thread stack and the signal handling stack.
Correct the misnomer by naming the replacement function systemstack.
Fix a few references to "M stack" in code.
The main motivation for this change is to eliminate scalararg/ptrarg.
Rick and I have already seen them cause problems because
the calling sequence m.ptrarg[0] = p is a heap pointer assignment,
so it gets a write barrier. The write barrier also uses onM, so it has
all the same problems as if it were being invoked by a signal handler.
We worked around this by saving and restoring the old values
and by calling onM_signalok, but there's no point in keeping this nice
home for bugs around any longer.
This CL also changes funcline to return the file name as a result
instead of filling in a passed-in *string. (The *string signature is
left over from when the code was written in and called from C.)
That's arguably an unrelated change, except that once I had done
the ptrarg/scalararg/onM cleanup I started getting false positives
about the *string argument escaping (not allowed in package runtime).
The compiler is wrong, but the easiest fix is to write the code like
Go code instead of like C code. I am a bit worried that the compiler
is wrong because of some use of uninitialized memory in the escape
analysis. If that's the reason, it will go away when we convert the
compiler to Go. (And if not, we'll debug it the next time.)
LGTM=khr
R=r, khr
CC=austin, golang-codereviews, iant, rlh
https://golang.org/cl/174950043
2014-11-12 14:54:31 -05:00
|
|
|
systemstack(func() {
|
2016-05-17 18:21:54 -04:00
|
|
|
ready(gp, traceskip, true)
|
2014-11-11 17:08:33 -05:00
|
|
|
})
|
2014-08-21 20:41:09 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func acquireSudog() *sudog {
|
2014-09-07 23:16:12 -04:00
|
|
|
// Delicate dance: the semaphore implementation calls
|
|
|
|
|
// acquireSudog, acquireSudog calls new(sudog),
|
|
|
|
|
// new calls malloc, malloc can call the garbage collector,
|
|
|
|
|
// and the garbage collector calls the semaphore implementation
|
2015-05-15 16:00:50 -04:00
|
|
|
// in stopTheWorld.
|
2014-09-07 23:16:12 -04:00
|
|
|
// Break the cycle by doing acquirem/releasem around new(sudog).
|
|
|
|
|
// The acquirem/releasem increments m.locks during new(sudog),
|
|
|
|
|
// which keeps the garbage collector from being invoked.
|
|
|
|
|
mp := acquirem()
|
2015-04-17 00:21:30 -04:00
|
|
|
pp := mp.p.ptr()
|
2015-02-03 00:33:02 +03:00
|
|
|
if len(pp.sudogcache) == 0 {
|
|
|
|
|
lock(&sched.sudoglock)
|
|
|
|
|
// First, try to grab a batch from central cache.
|
|
|
|
|
for len(pp.sudogcache) < cap(pp.sudogcache)/2 && sched.sudogcache != nil {
|
|
|
|
|
s := sched.sudogcache
|
|
|
|
|
sched.sudogcache = s.next
|
|
|
|
|
s.next = nil
|
|
|
|
|
pp.sudogcache = append(pp.sudogcache, s)
|
|
|
|
|
}
|
|
|
|
|
unlock(&sched.sudoglock)
|
|
|
|
|
// If the central cache is empty, allocate a new one.
|
|
|
|
|
if len(pp.sudogcache) == 0 {
|
|
|
|
|
pp.sudogcache = append(pp.sudogcache, new(sudog))
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-03-05 09:52:41 -05:00
|
|
|
n := len(pp.sudogcache)
|
|
|
|
|
s := pp.sudogcache[n-1]
|
|
|
|
|
pp.sudogcache[n-1] = nil
|
|
|
|
|
pp.sudogcache = pp.sudogcache[:n-1]
|
2015-02-03 00:33:02 +03:00
|
|
|
if s.elem != nil {
|
|
|
|
|
throw("acquireSudog: found s.elem != nil in cache")
|
2014-10-02 16:49:11 -04:00
|
|
|
}
|
2014-09-07 23:16:12 -04:00
|
|
|
releasem(mp)
|
2015-02-03 00:33:02 +03:00
|
|
|
return s
|
2014-08-21 20:41:09 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func releaseSudog(s *sudog) {
|
2014-10-02 16:49:11 -04:00
|
|
|
if s.elem != nil {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("runtime: sudog with non-nil elem")
|
2014-10-02 16:49:11 -04:00
|
|
|
}
|
2017-08-02 19:01:17 +01:00
|
|
|
if s.isSelect {
|
|
|
|
|
throw("runtime: sudog with non-false isSelect")
|
2014-10-03 15:33:29 -04:00
|
|
|
}
|
runtime: fix sudog leak
The SudoG used to sit on the stack, so it was cheap to allocated
and didn't need to be cleaned up when finished.
For the conversion to Go, we had to move sudog off the stack
for a few reasons, so we added a cache of recently used sudogs
to keep allocation cheap. But we didn't add any of the necessary
cleanup before adding a SudoG to the new cache, and so the cached
SudoGs had stale pointers inside them that have caused all sorts
of awful, hard to debug problems.
CL 155760043 made sure SudoG.elem is cleaned up.
CL 150520043 made sure SudoG.selectdone is cleaned up.
This CL makes sure SudoG.next, SudoG.prev, and SudoG.waitlink
are cleaned up. I should have done this when I did the other two
fields; instead I wasted a week tracking down a leak they caused.
A dangling SudoG.waitlink can point into a sudogcache list that
has been "forgotten" in order to let the GC collect it, but that
dangling .waitlink keeps the list from being collected.
And then the list holding the SudoG with the dangling waitlink
can find itself in the same situation, and so on. We end up
with lists of lists of unusable SudoGs that are still linked into
the object graph and never collected (given the right mix of
non-trivial selects and non-channel synchronization).
More details in golang.org/issue/9110.
Fixes #9110.
LGTM=r
R=r
CC=dvyukov, golang-codereviews, iant, khr
https://golang.org/cl/177870043
2014-11-16 16:44:45 -05:00
|
|
|
if s.next != nil {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("runtime: sudog with non-nil next")
|
runtime: fix sudog leak
The SudoG used to sit on the stack, so it was cheap to allocated
and didn't need to be cleaned up when finished.
For the conversion to Go, we had to move sudog off the stack
for a few reasons, so we added a cache of recently used sudogs
to keep allocation cheap. But we didn't add any of the necessary
cleanup before adding a SudoG to the new cache, and so the cached
SudoGs had stale pointers inside them that have caused all sorts
of awful, hard to debug problems.
CL 155760043 made sure SudoG.elem is cleaned up.
CL 150520043 made sure SudoG.selectdone is cleaned up.
This CL makes sure SudoG.next, SudoG.prev, and SudoG.waitlink
are cleaned up. I should have done this when I did the other two
fields; instead I wasted a week tracking down a leak they caused.
A dangling SudoG.waitlink can point into a sudogcache list that
has been "forgotten" in order to let the GC collect it, but that
dangling .waitlink keeps the list from being collected.
And then the list holding the SudoG with the dangling waitlink
can find itself in the same situation, and so on. We end up
with lists of lists of unusable SudoGs that are still linked into
the object graph and never collected (given the right mix of
non-trivial selects and non-channel synchronization).
More details in golang.org/issue/9110.
Fixes #9110.
LGTM=r
R=r
CC=dvyukov, golang-codereviews, iant, khr
https://golang.org/cl/177870043
2014-11-16 16:44:45 -05:00
|
|
|
}
|
|
|
|
|
if s.prev != nil {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("runtime: sudog with non-nil prev")
|
runtime: fix sudog leak
The SudoG used to sit on the stack, so it was cheap to allocated
and didn't need to be cleaned up when finished.
For the conversion to Go, we had to move sudog off the stack
for a few reasons, so we added a cache of recently used sudogs
to keep allocation cheap. But we didn't add any of the necessary
cleanup before adding a SudoG to the new cache, and so the cached
SudoGs had stale pointers inside them that have caused all sorts
of awful, hard to debug problems.
CL 155760043 made sure SudoG.elem is cleaned up.
CL 150520043 made sure SudoG.selectdone is cleaned up.
This CL makes sure SudoG.next, SudoG.prev, and SudoG.waitlink
are cleaned up. I should have done this when I did the other two
fields; instead I wasted a week tracking down a leak they caused.
A dangling SudoG.waitlink can point into a sudogcache list that
has been "forgotten" in order to let the GC collect it, but that
dangling .waitlink keeps the list from being collected.
And then the list holding the SudoG with the dangling waitlink
can find itself in the same situation, and so on. We end up
with lists of lists of unusable SudoGs that are still linked into
the object graph and never collected (given the right mix of
non-trivial selects and non-channel synchronization).
More details in golang.org/issue/9110.
Fixes #9110.
LGTM=r
R=r
CC=dvyukov, golang-codereviews, iant, khr
https://golang.org/cl/177870043
2014-11-16 16:44:45 -05:00
|
|
|
}
|
|
|
|
|
if s.waitlink != nil {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("runtime: sudog with non-nil waitlink")
|
runtime: fix sudog leak
The SudoG used to sit on the stack, so it was cheap to allocated
and didn't need to be cleaned up when finished.
For the conversion to Go, we had to move sudog off the stack
for a few reasons, so we added a cache of recently used sudogs
to keep allocation cheap. But we didn't add any of the necessary
cleanup before adding a SudoG to the new cache, and so the cached
SudoGs had stale pointers inside them that have caused all sorts
of awful, hard to debug problems.
CL 155760043 made sure SudoG.elem is cleaned up.
CL 150520043 made sure SudoG.selectdone is cleaned up.
This CL makes sure SudoG.next, SudoG.prev, and SudoG.waitlink
are cleaned up. I should have done this when I did the other two
fields; instead I wasted a week tracking down a leak they caused.
A dangling SudoG.waitlink can point into a sudogcache list that
has been "forgotten" in order to let the GC collect it, but that
dangling .waitlink keeps the list from being collected.
And then the list holding the SudoG with the dangling waitlink
can find itself in the same situation, and so on. We end up
with lists of lists of unusable SudoGs that are still linked into
the object graph and never collected (given the right mix of
non-trivial selects and non-channel synchronization).
More details in golang.org/issue/9110.
Fixes #9110.
LGTM=r
R=r
CC=dvyukov, golang-codereviews, iant, khr
https://golang.org/cl/177870043
2014-11-16 16:44:45 -05:00
|
|
|
}
|
2016-02-15 17:37:04 -05:00
|
|
|
if s.c != nil {
|
|
|
|
|
throw("runtime: sudog with non-nil c")
|
|
|
|
|
}
|
2014-10-02 16:49:11 -04:00
|
|
|
gp := getg()
|
|
|
|
|
if gp.param != nil {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("runtime: releaseSudog with non-nil gp.param")
|
2014-10-02 16:49:11 -04:00
|
|
|
}
|
2015-02-03 00:33:02 +03:00
|
|
|
mp := acquirem() // avoid rescheduling to another P
|
2015-04-17 00:21:30 -04:00
|
|
|
pp := mp.p.ptr()
|
2015-02-03 00:33:02 +03:00
|
|
|
if len(pp.sudogcache) == cap(pp.sudogcache) {
|
|
|
|
|
// Transfer half of local cache to the central cache.
|
|
|
|
|
var first, last *sudog
|
|
|
|
|
for len(pp.sudogcache) > cap(pp.sudogcache)/2 {
|
2015-03-05 09:52:41 -05:00
|
|
|
n := len(pp.sudogcache)
|
|
|
|
|
p := pp.sudogcache[n-1]
|
|
|
|
|
pp.sudogcache[n-1] = nil
|
|
|
|
|
pp.sudogcache = pp.sudogcache[:n-1]
|
2015-02-03 00:33:02 +03:00
|
|
|
if first == nil {
|
|
|
|
|
first = p
|
|
|
|
|
} else {
|
|
|
|
|
last.next = p
|
|
|
|
|
}
|
|
|
|
|
last = p
|
|
|
|
|
}
|
|
|
|
|
lock(&sched.sudoglock)
|
|
|
|
|
last.next = sched.sudogcache
|
|
|
|
|
sched.sudogcache = first
|
|
|
|
|
unlock(&sched.sudoglock)
|
|
|
|
|
}
|
|
|
|
|
pp.sudogcache = append(pp.sudogcache, s)
|
|
|
|
|
releasem(mp)
|
2014-08-21 20:41:09 +04:00
|
|
|
}
|
2014-09-03 11:10:38 -04:00
|
|
|
|
2022-11-11 19:22:35 +08:00
|
|
|
// called from assembly.
|
2014-09-04 21:12:31 -04:00
|
|
|
func badmcall(fn func(*g)) {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("runtime: mcall called on m->g0 stack")
|
2014-09-04 21:12:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func badmcall2(fn func(*g)) {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("runtime: mcall function returned")
|
2014-09-04 21:12:31 -04:00
|
|
|
}
|
2014-09-06 10:07:23 -07:00
|
|
|
|
2014-09-06 10:12:47 -07:00
|
|
|
func badreflectcall() {
|
2016-03-27 17:29:53 -07:00
|
|
|
panic(plainError("arg size to reflect.call more than 1GB"))
|
2014-09-06 10:12:47 -07:00
|
|
|
}
|
|
|
|
|
|
2016-10-13 10:44:57 -04:00
|
|
|
//go:nosplit
|
|
|
|
|
//go:nowritebarrierrec
|
|
|
|
|
func badmorestackg0() {
|
2022-11-01 12:33:59 -07:00
|
|
|
writeErrStr("fatal: morestack on g0\n")
|
2016-10-13 10:44:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:nosplit
|
|
|
|
|
//go:nowritebarrierrec
|
|
|
|
|
func badmorestackgsignal() {
|
2022-11-01 12:33:59 -07:00
|
|
|
writeErrStr("fatal: morestack on gsignal\n")
|
2016-10-13 10:44:57 -04:00
|
|
|
}
|
|
|
|
|
|
2016-10-19 16:16:40 -04:00
|
|
|
//go:nosplit
|
|
|
|
|
func badctxt() {
|
|
|
|
|
throw("ctxt != 0")
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-06 10:07:23 -07:00
|
|
|
func lockedOSThread() bool {
|
|
|
|
|
gp := getg()
|
2017-09-13 10:14:02 -07:00
|
|
|
return gp.lockedm != 0 && gp.m.lockedg != 0
|
2014-09-06 10:07:23 -07:00
|
|
|
}
|
2014-09-12 16:12:39 -04:00
|
|
|
|
2014-11-11 17:08:33 -05:00
|
|
|
var (
|
2020-11-17 11:55:53 -05:00
|
|
|
// allgs contains all Gs ever created (including dead Gs), and thus
|
|
|
|
|
// never shrinks.
|
|
|
|
|
//
|
|
|
|
|
// Access via the slice is protected by allglock or stop-the-world.
|
|
|
|
|
// Readers that cannot take the lock may (carefully!) use the atomic
|
|
|
|
|
// variables below.
|
2014-11-11 17:08:33 -05:00
|
|
|
allglock mutex
|
2020-11-17 11:55:53 -05:00
|
|
|
allgs []*g
|
|
|
|
|
|
2021-06-14 11:29:33 +00:00
|
|
|
// allglen and allgptr are atomic variables that contain len(allgs) and
|
|
|
|
|
// &allgs[0] respectively. Proper ordering depends on totally-ordered
|
2020-11-17 11:55:53 -05:00
|
|
|
// loads and stores. Writes are protected by allglock.
|
|
|
|
|
//
|
|
|
|
|
// allgptr is updated before allglen. Readers should read allglen
|
|
|
|
|
// before allgptr to ensure that allglen is always <= len(allgptr). New
|
|
|
|
|
// Gs appended during the race can be missed. For a consistent view of
|
|
|
|
|
// all Gs, allglock must be held.
|
|
|
|
|
//
|
|
|
|
|
// allgptr copies should always be stored as a concrete type or
|
|
|
|
|
// unsafe.Pointer, not uintptr, to ensure that GC can still reach it
|
|
|
|
|
// even if it points to a stale array.
|
|
|
|
|
allglen uintptr
|
|
|
|
|
allgptr **g
|
2014-11-11 17:08:33 -05:00
|
|
|
)
|
|
|
|
|
|
2014-09-12 16:12:39 -04:00
|
|
|
func allgadd(gp *g) {
|
|
|
|
|
if readgstatus(gp) == _Gidle {
|
2014-12-27 20:58:00 -08:00
|
|
|
throw("allgadd: bad status Gidle")
|
2014-09-12 16:12:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lock(&allglock)
|
|
|
|
|
allgs = append(allgs, gp)
|
2020-11-17 11:55:53 -05:00
|
|
|
if &allgs[0] != allgptr {
|
|
|
|
|
atomicstorep(unsafe.Pointer(&allgptr), unsafe.Pointer(&allgs[0]))
|
|
|
|
|
}
|
|
|
|
|
atomic.Storeuintptr(&allglen, uintptr(len(allgs)))
|
2014-09-12 16:12:39 -04:00
|
|
|
unlock(&allglock)
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
|
runtime: fix racy allgs access on weak memory architectures
Currently, markroot is very clever about accessing the allgs slice to
find stack roots. Unfortunately, on weak memory architectures, it's a
little too clever and can sometimes read a nil g, causing a fatal
panic.
Specifically, gcMarkRootPrepare snapshots the length of allgs during
STW and then markroot accesses allgs up to this length during
concurrent marking. During concurrent marking, allgadd can append to
allgs *without synchronizing with markroot*, but the argument is that
the markroot access should be safe because allgs only grows
monotonically and existing entries in allgs never change.
This reasoning is insufficient on weak memory architectures. Suppose
thread 1 calls allgadd during concurrent marking and that allgs is
already at capacity. On thread 1, append will allocate a new slice
that initially consists of all nils, then copy the old backing store
to the new slice (write A), then allgadd will publish the new slice to
the allgs global (write B). Meanwhile, on thread 2, markroot reads the
allgs slice base pointer (read A), computes an offset from that base
pointer, and reads the value at that offset (read B). On a weak memory
machine, thread 2 can observe write B *before* write A. If the order
of events from thread 2's perspective is write B, read A, read B,
write A, then markroot on thread 2 will read a nil g and then panic.
Fix this by taking a snapshot of the allgs slice header in
gcMarkRootPrepare while the world is stopped and using that snapshot
as the list of stack roots in markroot. This eliminates all read/write
concurrency around the access in markroot.
Alternatively, we could make markroot use the atomicAllGs API to
atomically access the allgs list, but in my opinion it's much less
subtle to just eliminate all of the interesting concurrency around the
allgs access.
Fixes #49686.
Fixes #48845.
Fixes #43824.
(These are all just different paths to the same ultimate issue.)
Change-Id: I472b4934a637bbe88c8a080a280aa30212acf984
Reviewed-on: https://go-review.googlesource.com/c/go/+/368134
Trust: Austin Clements <austin@google.com>
Trust: Bryan C. Mills <bcmills@google.com>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
2021-12-01 08:56:19 -05:00
|
|
|
// allGsSnapshot returns a snapshot of the slice of all Gs.
|
|
|
|
|
//
|
|
|
|
|
// The world must be stopped or allglock must be held.
|
|
|
|
|
func allGsSnapshot() []*g {
|
|
|
|
|
assertWorldStoppedOrLockHeld(&allglock)
|
|
|
|
|
|
|
|
|
|
// Because the world is stopped or allglock is held, allgadd
|
|
|
|
|
// cannot happen concurrently with this. allgs grows
|
|
|
|
|
// monotonically and existing entries never change, so we can
|
|
|
|
|
// simply return a copy of the slice header. For added safety,
|
|
|
|
|
// we trim everything past len because that can still change.
|
|
|
|
|
return allgs[:len(allgs):len(allgs)]
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-17 11:55:53 -05:00
|
|
|
// atomicAllG returns &allgs[0] and len(allgs) for use with atomicAllGIndex.
|
|
|
|
|
func atomicAllG() (**g, uintptr) {
|
|
|
|
|
length := atomic.Loaduintptr(&allglen)
|
|
|
|
|
ptr := (**g)(atomic.Loadp(unsafe.Pointer(&allgptr)))
|
|
|
|
|
return ptr, length
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// atomicAllGIndex returns ptr[i] with the allgptr returned from atomicAllG.
|
|
|
|
|
func atomicAllGIndex(ptr **g, i uintptr) *g {
|
2021-06-16 23:05:44 +00:00
|
|
|
return *(**g)(add(unsafe.Pointer(ptr), i*goarch.PtrSize))
|
2020-11-17 11:55:53 -05:00
|
|
|
}
|
|
|
|
|
|
2020-12-23 15:05:37 -05:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
const (
|
|
|
|
|
// 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.
|
|
|
|
|
_GoidCacheBatch = 16
|
|
|
|
|
)
|
|
|
|
|
|
2022-10-19 14:51:15 -04:00
|
|
|
// cpuinit sets up CPU feature flags and calls internal/cpu.Initialize. env should be the complete
|
|
|
|
|
// value of the GODEBUG environment variable.
|
|
|
|
|
func cpuinit(env string) {
|
2018-10-28 15:37:13 +01:00
|
|
|
switch GOOS {
|
2020-09-16 16:59:58 -04:00
|
|
|
case "aix", "darwin", "ios", "dragonfly", "freebsd", "netbsd", "openbsd", "illumos", "solaris", "linux":
|
2018-08-08 17:26:23 -07:00
|
|
|
cpu.DebugOptions = true
|
2022-10-19 14:51:15 -04:00
|
|
|
}
|
|
|
|
|
cpu.Initialize(env)
|
|
|
|
|
|
|
|
|
|
// Support cpu feature variables are used in code generated by the compiler
|
|
|
|
|
// to guard execution of instructions that can not be assumed to be always supported.
|
|
|
|
|
switch GOARCH {
|
|
|
|
|
case "386", "amd64":
|
|
|
|
|
x86HasPOPCNT = cpu.X86.HasPOPCNT
|
|
|
|
|
x86HasSSE41 = cpu.X86.HasSSE41
|
|
|
|
|
x86HasFMA = cpu.X86.HasFMA
|
|
|
|
|
|
|
|
|
|
case "arm":
|
|
|
|
|
armHasVFPv4 = cpu.ARM.HasVFPv4
|
|
|
|
|
|
|
|
|
|
case "arm64":
|
|
|
|
|
arm64HasATOMICS = cpu.ARM64.HasATOMICS
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-26 12:14:27 +01:00
|
|
|
|
2022-10-19 14:51:15 -04:00
|
|
|
// getGodebugEarly extracts the environment variable GODEBUG from the environment on
|
|
|
|
|
// Unix-like operating systems and returns it. This function exists to extract GODEBUG
|
|
|
|
|
// early before much of the runtime is initialized.
|
|
|
|
|
func getGodebugEarly() string {
|
|
|
|
|
const prefix = "GODEBUG="
|
|
|
|
|
var env string
|
|
|
|
|
switch GOOS {
|
|
|
|
|
case "aix", "darwin", "ios", "dragonfly", "freebsd", "netbsd", "openbsd", "illumos", "solaris", "linux":
|
2018-01-26 12:14:27 +01:00
|
|
|
// Similar to goenv_unix but extracts the environment value for
|
2018-11-14 20:48:40 +01:00
|
|
|
// GODEBUG directly.
|
2018-01-26 12:14:27 +01:00
|
|
|
// TODO(moehrmann): remove when general goenvs() can be called before cpuinit()
|
|
|
|
|
n := int32(0)
|
|
|
|
|
for argv_index(argv, argc+1+n) != nil {
|
|
|
|
|
n++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := int32(0); i < n; i++ {
|
|
|
|
|
p := argv_index(argv, argc+1+i)
|
2022-09-03 14:29:35 +08:00
|
|
|
s := unsafe.String(p, findnull(p))
|
2018-01-26 12:14:27 +01:00
|
|
|
|
2018-06-01 19:25:57 +02:00
|
|
|
if hasPrefix(s, prefix) {
|
2018-01-26 12:14:27 +01:00
|
|
|
env = gostring(p)[len(prefix):]
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-10-19 14:51:15 -04:00
|
|
|
return env
|
2018-01-26 12:14:27 +01:00
|
|
|
}
|
2018-04-10 22:33:03 +08:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// The bootstrap sequence is:
|
|
|
|
|
//
|
|
|
|
|
// call osinit
|
|
|
|
|
// call schedinit
|
|
|
|
|
// make & queue new G
|
|
|
|
|
// call runtime·mstart
|
|
|
|
|
//
|
|
|
|
|
// The new G calls runtime·main.
|
|
|
|
|
func schedinit() {
|
runtime: static lock ranking for the runtime (enabled by GOEXPERIMENT)
I took some of the infrastructure from Austin's lock logging CR
https://go-review.googlesource.com/c/go/+/192704 (with deadlock
detection from the logs), and developed a setup to give static lock
ranking for runtime locks.
Static lock ranking establishes a documented total ordering among locks,
and then reports an error if the total order is violated. This can
happen if a deadlock happens (by acquiring a sequence of locks in
different orders), or if just one side of a possible deadlock happens.
Lock ordering deadlocks cannot happen as long as the lock ordering is
followed.
Along the way, I found a deadlock involving the new timer code, which Ian fixed
via https://go-review.googlesource.com/c/go/+/207348, as well as two other
potential deadlocks.
See the constants at the top of runtime/lockrank.go to show the static
lock ranking that I ended up with, along with some comments. This is
great documentation of the current intended lock ordering when acquiring
multiple locks in the runtime.
I also added an array lockPartialOrder[] which shows and enforces the
current partial ordering among locks (which is embedded within the total
ordering). This is more specific about the dependencies among locks.
I don't try to check the ranking within a lock class with multiple locks
that can be acquired at the same time (i.e. check the ranking when
multiple hchan locks are acquired).
Currently, I am doing a lockInit() call to set the lock rank of most
locks. Any lock that is not otherwise initialized is assumed to be a
leaf lock (a very high rank lock), so that eliminates the need to do
anything for a bunch of locks (including all architecture-dependent
locks). For two locks, root.lock and notifyList.lock (only in the
runtime/sema.go file), it is not as easy to do lock initialization, so
instead, I am passing the lock rank with the lock calls.
For Windows compilation, I needed to increase the StackGuard size from
896 to 928 because of the new lock-rank checking functions.
Checking of the static lock ranking is enabled by setting
GOEXPERIMENT=staticlockranking before doing a run.
To make sure that the static lock ranking code has no overhead in memory
or CPU when not enabled by GOEXPERIMENT, I changed 'go build/install' so
that it defines a build tag (with the same name) whenever any experiment
has been baked into the toolchain (by checking Expstring()). This allows
me to avoid increasing the size of the 'mutex' type when static lock
ranking is not enabled.
Fixes #38029
Change-Id: I154217ff307c47051f8dae9c2a03b53081acd83a
Reviewed-on: https://go-review.googlesource.com/c/go/+/207619
Reviewed-by: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-11-13 17:34:47 -08:00
|
|
|
lockInit(&sched.lock, lockRankSched)
|
2020-05-19 16:33:17 +00:00
|
|
|
lockInit(&sched.sysmonlock, lockRankSysmon)
|
runtime: static lock ranking for the runtime (enabled by GOEXPERIMENT)
I took some of the infrastructure from Austin's lock logging CR
https://go-review.googlesource.com/c/go/+/192704 (with deadlock
detection from the logs), and developed a setup to give static lock
ranking for runtime locks.
Static lock ranking establishes a documented total ordering among locks,
and then reports an error if the total order is violated. This can
happen if a deadlock happens (by acquiring a sequence of locks in
different orders), or if just one side of a possible deadlock happens.
Lock ordering deadlocks cannot happen as long as the lock ordering is
followed.
Along the way, I found a deadlock involving the new timer code, which Ian fixed
via https://go-review.googlesource.com/c/go/+/207348, as well as two other
potential deadlocks.
See the constants at the top of runtime/lockrank.go to show the static
lock ranking that I ended up with, along with some comments. This is
great documentation of the current intended lock ordering when acquiring
multiple locks in the runtime.
I also added an array lockPartialOrder[] which shows and enforces the
current partial ordering among locks (which is embedded within the total
ordering). This is more specific about the dependencies among locks.
I don't try to check the ranking within a lock class with multiple locks
that can be acquired at the same time (i.e. check the ranking when
multiple hchan locks are acquired).
Currently, I am doing a lockInit() call to set the lock rank of most
locks. Any lock that is not otherwise initialized is assumed to be a
leaf lock (a very high rank lock), so that eliminates the need to do
anything for a bunch of locks (including all architecture-dependent
locks). For two locks, root.lock and notifyList.lock (only in the
runtime/sema.go file), it is not as easy to do lock initialization, so
instead, I am passing the lock rank with the lock calls.
For Windows compilation, I needed to increase the StackGuard size from
896 to 928 because of the new lock-rank checking functions.
Checking of the static lock ranking is enabled by setting
GOEXPERIMENT=staticlockranking before doing a run.
To make sure that the static lock ranking code has no overhead in memory
or CPU when not enabled by GOEXPERIMENT, I changed 'go build/install' so
that it defines a build tag (with the same name) whenever any experiment
has been baked into the toolchain (by checking Expstring()). This allows
me to avoid increasing the size of the 'mutex' type when static lock
ranking is not enabled.
Fixes #38029
Change-Id: I154217ff307c47051f8dae9c2a03b53081acd83a
Reviewed-on: https://go-review.googlesource.com/c/go/+/207619
Reviewed-by: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-11-13 17:34:47 -08:00
|
|
|
lockInit(&sched.deferlock, lockRankDefer)
|
|
|
|
|
lockInit(&sched.sudoglock, lockRankSudog)
|
|
|
|
|
lockInit(&deadlock, lockRankDeadlock)
|
|
|
|
|
lockInit(&paniclk, lockRankPanic)
|
|
|
|
|
lockInit(&allglock, lockRankAllg)
|
|
|
|
|
lockInit(&allpLock, lockRankAllp)
|
|
|
|
|
lockInit(&reflectOffs.lock, lockRankReflectOffs)
|
|
|
|
|
lockInit(&finlock, lockRankFin)
|
|
|
|
|
lockInit(&trace.bufLock, lockRankTraceBuf)
|
|
|
|
|
lockInit(&trace.stringsLock, lockRankTraceStrings)
|
|
|
|
|
lockInit(&trace.lock, lockRankTrace)
|
|
|
|
|
lockInit(&cpuprof.lock, lockRankCpuprof)
|
|
|
|
|
lockInit(&trace.stackTab.lock, lockRankTraceStackTab)
|
2020-11-02 19:03:16 +00:00
|
|
|
// Enforce that this lock is always a leaf lock.
|
|
|
|
|
// All of this lock's critical sections should be
|
|
|
|
|
// extremely short.
|
|
|
|
|
lockInit(&memstats.heapStats.noPLock, lockRankLeafRank)
|
runtime: static lock ranking for the runtime (enabled by GOEXPERIMENT)
I took some of the infrastructure from Austin's lock logging CR
https://go-review.googlesource.com/c/go/+/192704 (with deadlock
detection from the logs), and developed a setup to give static lock
ranking for runtime locks.
Static lock ranking establishes a documented total ordering among locks,
and then reports an error if the total order is violated. This can
happen if a deadlock happens (by acquiring a sequence of locks in
different orders), or if just one side of a possible deadlock happens.
Lock ordering deadlocks cannot happen as long as the lock ordering is
followed.
Along the way, I found a deadlock involving the new timer code, which Ian fixed
via https://go-review.googlesource.com/c/go/+/207348, as well as two other
potential deadlocks.
See the constants at the top of runtime/lockrank.go to show the static
lock ranking that I ended up with, along with some comments. This is
great documentation of the current intended lock ordering when acquiring
multiple locks in the runtime.
I also added an array lockPartialOrder[] which shows and enforces the
current partial ordering among locks (which is embedded within the total
ordering). This is more specific about the dependencies among locks.
I don't try to check the ranking within a lock class with multiple locks
that can be acquired at the same time (i.e. check the ranking when
multiple hchan locks are acquired).
Currently, I am doing a lockInit() call to set the lock rank of most
locks. Any lock that is not otherwise initialized is assumed to be a
leaf lock (a very high rank lock), so that eliminates the need to do
anything for a bunch of locks (including all architecture-dependent
locks). For two locks, root.lock and notifyList.lock (only in the
runtime/sema.go file), it is not as easy to do lock initialization, so
instead, I am passing the lock rank with the lock calls.
For Windows compilation, I needed to increase the StackGuard size from
896 to 928 because of the new lock-rank checking functions.
Checking of the static lock ranking is enabled by setting
GOEXPERIMENT=staticlockranking before doing a run.
To make sure that the static lock ranking code has no overhead in memory
or CPU when not enabled by GOEXPERIMENT, I changed 'go build/install' so
that it defines a build tag (with the same name) whenever any experiment
has been baked into the toolchain (by checking Expstring()). This allows
me to avoid increasing the size of the 'mutex' type when static lock
ranking is not enabled.
Fixes #38029
Change-Id: I154217ff307c47051f8dae9c2a03b53081acd83a
Reviewed-on: https://go-review.googlesource.com/c/go/+/207619
Reviewed-by: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-11-13 17:34:47 -08:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// raceinit must be the first call to race detector.
|
|
|
|
|
// In particular, it must be done before mallocinit below calls racemapshadow.
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
if raceenabled {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.racectx, raceprocctx0 = raceinit()
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sched.maxmcount = 10000
|
|
|
|
|
|
2020-10-28 18:06:05 -04:00
|
|
|
// The world starts stopped.
|
|
|
|
|
worldStopped()
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
moduledataverify()
|
|
|
|
|
stackinit()
|
|
|
|
|
mallocinit()
|
2022-10-19 14:51:15 -04:00
|
|
|
godebug := getGodebugEarly()
|
|
|
|
|
initPageTrace(godebug) // must run after mallocinit but before anything allocates
|
|
|
|
|
cpuinit(godebug) // must run before alginit
|
|
|
|
|
alginit() // maps, hash, fastrand must not be used before this call
|
|
|
|
|
fastrandinit() // must run before mcommoninit
|
2021-02-11 11:15:53 -05:00
|
|
|
mcommoninit(gp.m, -1)
|
2018-01-26 12:14:27 +01:00
|
|
|
modulesinit() // provides activeModules
|
|
|
|
|
typelinksinit() // uses maps, activeModules
|
|
|
|
|
itabsinit() // uses activeModules
|
2021-09-27 14:27:20 -07:00
|
|
|
stkobjinit() // must run before GC starts
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
sigsave(&gp.m.sigmask)
|
|
|
|
|
initSigmask = gp.m.sigmask
|
2015-12-19 10:17:10 -08:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
goargs()
|
|
|
|
|
goenvs()
|
|
|
|
|
parsedebugvars()
|
2016-11-02 09:10:29 -04:00
|
|
|
gcinit()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2022-09-16 18:56:48 +08:00
|
|
|
// 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
|
|
|
|
|
// safe to set MemProfileRate to 0.
|
|
|
|
|
if disableMemoryProfiling {
|
|
|
|
|
MemProfileRate = 0
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 11:51:25 -04:00
|
|
|
lock(&sched.lock)
|
2022-07-20 17:39:12 -04:00
|
|
|
sched.lastpoll.Store(nanotime())
|
2016-10-30 01:54:19 +02:00
|
|
|
procs := ncpu
|
|
|
|
|
if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
|
|
|
|
|
procs = n
|
|
|
|
|
}
|
|
|
|
|
if procresize(procs) != nil {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("unknown runnable goroutine during bootstrap")
|
|
|
|
|
}
|
2020-08-21 11:51:25 -04:00
|
|
|
unlock(&sched.lock)
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2020-10-28 18:06:05 -04:00
|
|
|
// World is effectively started now, as P's can run.
|
|
|
|
|
worldStarted()
|
|
|
|
|
|
2015-12-06 17:35:12 -05:00
|
|
|
if buildVersion == "" {
|
2016-03-01 23:21:55 +00:00
|
|
|
// Condition should never trigger. This code just serves
|
2015-10-18 17:04:05 -07:00
|
|
|
// to ensure runtime·buildVersion is kept in the resulting binary.
|
2015-12-06 17:35:12 -05:00
|
|
|
buildVersion = "unknown"
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2019-04-22 23:01:26 -04:00
|
|
|
if len(modinfo) == 1 {
|
|
|
|
|
// Condition should never trigger. This code just serves
|
|
|
|
|
// to ensure runtime·modinfo is kept in the resulting binary.
|
|
|
|
|
modinfo = ""
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func dumpgstatus(gp *g) {
|
2021-02-10 12:46:09 -05:00
|
|
|
thisg := getg()
|
|
|
|
|
print("runtime: gp: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
|
|
|
|
|
print("runtime: getg: g=", thisg, ", goid=", thisg.goid, ", g->atomicstatus=", readgstatus(thisg), "\n")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2020-08-21 11:51:25 -04:00
|
|
|
// sched.lock must be held.
|
2015-10-18 17:04:05 -07:00
|
|
|
func checkmcount() {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
2017-10-05 21:28:01 -04:00
|
|
|
if mcount() > sched.maxmcount {
|
2015-10-18 17:04:05 -07:00
|
|
|
print("runtime: program exceeds ", sched.maxmcount, "-thread limit\n")
|
|
|
|
|
throw("thread exhaustion")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
// mReserveID returns the next ID to use for a new m. This new m is immediately
|
|
|
|
|
// considered 'running' by checkdead.
|
|
|
|
|
//
|
|
|
|
|
// sched.lock must be held.
|
|
|
|
|
func mReserveID() int64 {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
if sched.mnext+1 < sched.mnext {
|
|
|
|
|
throw("runtime: thread ID overflow")
|
|
|
|
|
}
|
|
|
|
|
id := sched.mnext
|
|
|
|
|
sched.mnext++
|
|
|
|
|
checkmcount()
|
|
|
|
|
return id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pre-allocated ID may be passed as 'id', or omitted by passing -1.
|
|
|
|
|
func mcommoninit(mp *m, id int64) {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// g0 stack won't make sense for user (and is not necessary unwindable).
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp != gp.m.g0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
callers(1, mp.createstack[:])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lock(&sched.lock)
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
|
|
|
|
|
if id >= 0 {
|
|
|
|
|
mp.id = id
|
|
|
|
|
} else {
|
|
|
|
|
mp.id = mReserveID()
|
2017-10-05 21:28:01 -04:00
|
|
|
}
|
2017-09-09 14:59:06 +02:00
|
|
|
|
2022-02-07 17:07:44 -08:00
|
|
|
lo := uint32(int64Hash(uint64(mp.id), fastrandseed))
|
|
|
|
|
hi := uint32(int64Hash(uint64(cputicks()), ^fastrandseed))
|
|
|
|
|
if lo|hi == 0 {
|
|
|
|
|
hi = 1
|
|
|
|
|
}
|
|
|
|
|
// Same behavior as for 1.17.
|
2023-02-07 09:09:24 +00:00
|
|
|
// TODO: Simplify this.
|
2022-02-07 17:07:44 -08:00
|
|
|
if goarch.BigEndian {
|
|
|
|
|
mp.fastrand = uint64(lo)<<32 | uint64(hi)
|
|
|
|
|
} else {
|
|
|
|
|
mp.fastrand = uint64(hi)<<32 | uint64(lo)
|
|
|
|
|
}
|
2017-09-09 14:59:06 +02:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
mpreinit(mp)
|
|
|
|
|
if mp.gsignal != nil {
|
|
|
|
|
mp.gsignal.stackguard1 = mp.gsignal.stack.lo + _StackGuard
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add to allm so garbage collector doesn't free g->m
|
|
|
|
|
// when it is just in a register or thread-local storage.
|
|
|
|
|
mp.alllink = allm
|
|
|
|
|
|
|
|
|
|
// NumCgoCall() iterates over allm w/o schedlock,
|
|
|
|
|
// so we need to publish it safely.
|
|
|
|
|
atomicstorep(unsafe.Pointer(&allm), unsafe.Pointer(mp))
|
|
|
|
|
unlock(&sched.lock)
|
2016-04-01 15:06:25 -07:00
|
|
|
|
|
|
|
|
// Allocate memory to hold a cgo traceback if the cgo call crashes.
|
2019-04-29 13:50:49 +00:00
|
|
|
if iscgo || GOOS == "solaris" || GOOS == "illumos" || GOOS == "windows" {
|
2016-04-01 15:06:25 -07:00
|
|
|
mp.cgoCallers = new(cgoCallers)
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2022-03-01 15:06:37 -05:00
|
|
|
func (mp *m) becomeSpinning() {
|
|
|
|
|
mp.spinning = true
|
|
|
|
|
sched.nmspinning.Add(1)
|
|
|
|
|
sched.needspinning.Store(0)
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-13 10:18:36 +01:00
|
|
|
func (mp *m) incgocallback() bool {
|
|
|
|
|
return (!mp.incgo && mp.ncgo > 0) || mp.isextra
|
|
|
|
|
}
|
|
|
|
|
|
runtime: consistently seed fastrand state across archs
Some, but not all, architectures mix in OS-provided random seeds when
initializing the fastrand state. The others have TODOs saying we need
to do the same. Lift that logic up in the architecture-independent
part, and use memhash to mix the seed instead of a simple addition.
Previously, dumping the fastrand state at initialization would yield
something like the following on linux-amd64, where the values in the
first column do not change between runs (as thread IDs are sequential
and always start at 0), and the values in the second column, while
changing every run, are pretty correlated:
first run:
0x0 0x44d82f1c
0x5f356495 0x44f339de
0xbe6ac92a 0x44f91cd8
0x1da02dbf 0x44fd91bc
0x7cd59254 0x44fee8a4
0xdc0af6e9 0x4547a1e0
0x3b405b7e 0x474c76fc
0x9a75c013 0x475309dc
0xf9ab24a8 0x4bffd075
second run:
0x0 0xa63fc3eb
0x5f356495 0xa6648dc2
0xbe6ac92a 0xa66c1c59
0x1da02dbf 0xa671bce8
0x7cd59254 0xa70e8287
0xdc0af6e9 0xa7129d2e
0x3b405b7e 0xa7379e2d
0x9a75c013 0xa7e4c64c
0xf9ab24a8 0xa7ecce07
With this change, we get initial states that appear to be much more
unpredictable, both within the same run as well as between runs:
0x11bddad7 0x97241c63
0x553dacc6 0x2bcd8523
0x62c01085 0x16413d92
0x6f40e9e6 0x7a138de6
0xa4898053 0x70d816f0
0x5ca5b433 0x188a395b
0x62778ca9 0xd462c3b5
0xd6e160e4 0xac9b4bd
0xb9571d65 0x597a981d
Change-Id: Ib22c530157d74200df0083f830e0408fd4aaea58
Reviewed-on: https://go-review.googlesource.com/c/go/+/203439
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2019-10-26 16:26:59 +09:00
|
|
|
var fastrandseed uintptr
|
|
|
|
|
|
|
|
|
|
func fastrandinit() {
|
|
|
|
|
s := (*[unsafe.Sizeof(fastrandseed)]byte)(unsafe.Pointer(&fastrandseed))[:]
|
|
|
|
|
getRandomData(s)
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Mark gp ready to run.
|
2016-05-17 18:21:54 -04:00
|
|
|
func ready(gp *g, traceskip int, next bool) {
|
2015-10-18 17:04:05 -07:00
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoUnpark(gp, traceskip)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status := readgstatus(gp)
|
|
|
|
|
|
|
|
|
|
// Mark runnable.
|
2019-03-25 21:16:33 -04:00
|
|
|
mp := acquirem() // disable preemption because it can be holding p in a local var
|
2015-10-18 17:04:05 -07:00
|
|
|
if status&^_Gscan != _Gwaiting {
|
|
|
|
|
dumpgstatus(gp)
|
|
|
|
|
throw("bad g->status in ready")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// status is Gwaiting or Gscanwaiting, make Grunnable and put on runq
|
|
|
|
|
casgstatus(gp, _Gwaiting, _Grunnable)
|
2021-02-10 12:46:09 -05:00
|
|
|
runqput(mp.p.ptr(), gp, next)
|
2020-04-28 20:54:31 -04:00
|
|
|
wakep()
|
2019-03-25 21:16:33 -04:00
|
|
|
releasem(mp)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// freezeStopWait is a large value that freezetheworld sets
|
|
|
|
|
// sched.stopwait to in order to request that all Gs permanently stop.
|
|
|
|
|
const freezeStopWait = 0x7fffffff
|
|
|
|
|
|
2016-12-19 22:43:38 -05:00
|
|
|
// freezing is set to non-zero if the runtime is trying to freeze the
|
|
|
|
|
// world.
|
2022-08-17 17:28:58 +07:00
|
|
|
var freezing atomic.Bool
|
2016-12-19 22:43:38 -05:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Similar to stopTheWorld but best-effort and can be called several times.
|
|
|
|
|
// There is no reverse operation, used during crashing.
|
|
|
|
|
// This function must not lock any mutexes.
|
|
|
|
|
func freezetheworld() {
|
2022-08-17 17:28:58 +07:00
|
|
|
freezing.Store(true)
|
2015-10-18 17:04:05 -07:00
|
|
|
// stopwait and preemption requests can be lost
|
|
|
|
|
// due to races with concurrently executing threads,
|
|
|
|
|
// so try several times
|
|
|
|
|
for i := 0; i < 5; i++ {
|
|
|
|
|
// this should tell the scheduler to not start any new goroutines
|
|
|
|
|
sched.stopwait = freezeStopWait
|
2022-07-25 15:31:03 -04:00
|
|
|
sched.gcwaiting.Store(true)
|
2015-10-18 17:04:05 -07:00
|
|
|
// this should stop running goroutines
|
|
|
|
|
if !preemptall() {
|
|
|
|
|
break // no running goroutines
|
|
|
|
|
}
|
|
|
|
|
usleep(1000)
|
|
|
|
|
}
|
|
|
|
|
// to be sure
|
|
|
|
|
usleep(1000)
|
|
|
|
|
preemptall()
|
|
|
|
|
usleep(1000)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All reads and writes of g's status go through readgstatus, casgstatus
|
|
|
|
|
// castogscanstatus, casfrom_Gscanstatus.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
|
|
|
|
func readgstatus(gp *g) uint32 {
|
2022-08-25 03:27:02 +08:00
|
|
|
return gp.atomicstatus.Load()
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The Gscanstatuses are acting like locks and this releases them.
|
|
|
|
|
// If it proves to be a performance hit we should be able to make these
|
|
|
|
|
// simple atomic stores but for now we are going to throw if
|
|
|
|
|
// we see an inconsistent state.
|
|
|
|
|
func casfrom_Gscanstatus(gp *g, oldval, newval uint32) {
|
|
|
|
|
success := false
|
|
|
|
|
|
|
|
|
|
// Check that transition is valid.
|
|
|
|
|
switch oldval {
|
|
|
|
|
default:
|
|
|
|
|
print("runtime: casfrom_Gscanstatus bad oldval gp=", gp, ", oldval=", hex(oldval), ", newval=", hex(newval), "\n")
|
|
|
|
|
dumpgstatus(gp)
|
|
|
|
|
throw("casfrom_Gscanstatus:top gp->status is not in scan state")
|
|
|
|
|
case _Gscanrunnable,
|
|
|
|
|
_Gscanwaiting,
|
|
|
|
|
_Gscanrunning,
|
2019-09-27 12:27:51 -04:00
|
|
|
_Gscansyscall,
|
|
|
|
|
_Gscanpreempted:
|
2015-10-18 17:04:05 -07:00
|
|
|
if newval == oldval&^_Gscan {
|
2022-08-25 03:27:02 +08:00
|
|
|
success = gp.atomicstatus.CompareAndSwap(oldval, newval)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !success {
|
|
|
|
|
print("runtime: casfrom_Gscanstatus failed gp=", gp, ", oldval=", hex(oldval), ", newval=", hex(newval), "\n")
|
|
|
|
|
dumpgstatus(gp)
|
|
|
|
|
throw("casfrom_Gscanstatus: gp->status is not in scan state")
|
|
|
|
|
}
|
2020-04-15 12:35:24 -07:00
|
|
|
releaseLockRank(lockRankGscan)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This will return false if the gp is not in the expected status and the cas fails.
|
|
|
|
|
// This acts like a lock acquire while the casfromgstatus acts like a lock release.
|
|
|
|
|
func castogscanstatus(gp *g, oldval, newval uint32) bool {
|
|
|
|
|
switch oldval {
|
|
|
|
|
case _Grunnable,
|
2016-02-18 09:38:49 -05:00
|
|
|
_Grunning,
|
2015-10-18 17:04:05 -07:00
|
|
|
_Gwaiting,
|
|
|
|
|
_Gsyscall:
|
|
|
|
|
if newval == oldval|_Gscan {
|
2022-08-25 03:27:02 +08:00
|
|
|
r := gp.atomicstatus.CompareAndSwap(oldval, newval)
|
2020-04-15 12:35:24 -07:00
|
|
|
if r {
|
|
|
|
|
acquireLockRank(lockRankGscan)
|
|
|
|
|
}
|
|
|
|
|
return r
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
print("runtime: castogscanstatus oldval=", hex(oldval), " newval=", hex(newval), "\n")
|
|
|
|
|
throw("castogscanstatus")
|
|
|
|
|
panic("not reached")
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-31 21:34:23 +00:00
|
|
|
// casgstatusAlwaysTrack is a debug flag that causes casgstatus to always track
|
|
|
|
|
// various latencies on every transition instead of sampling them.
|
|
|
|
|
var casgstatusAlwaysTrack = false
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// If asked to move to or from a Gscanstatus this will throw. Use the castogscanstatus
|
|
|
|
|
// and casfrom_Gscanstatus instead.
|
|
|
|
|
// casgstatus will loop if the g->atomicstatus is in a Gscan status until the routine that
|
|
|
|
|
// put it in the Gscan state is finished.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
|
|
|
|
func casgstatus(gp *g, oldval, newval uint32) {
|
|
|
|
|
if (oldval&_Gscan != 0) || (newval&_Gscan != 0) || oldval == newval {
|
|
|
|
|
systemstack(func() {
|
|
|
|
|
print("runtime: casgstatus: oldval=", hex(oldval), " newval=", hex(newval), "\n")
|
|
|
|
|
throw("casgstatus: bad incoming values")
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-15 12:35:24 -07:00
|
|
|
acquireLockRank(lockRankGscan)
|
|
|
|
|
releaseLockRank(lockRankGscan)
|
|
|
|
|
|
2018-06-01 17:29:59 -03:00
|
|
|
// See https://golang.org/cl/21503 for justification of the yield delay.
|
runtime: don't burn CPU unnecessarily
Two GC-related functions, scang and casgstatus, wait in an active spin loop.
Active spinning is never a good idea in user-space. Once we wait several
times more than the expected wait time, something unexpected is happenning
(e.g. the thread we are waiting for is descheduled or handling a page fault)
and we need to yield to OS scheduler. Moreover, the expected wait time is
very high for these functions: scang wait time can be tens of milliseconds,
casgstatus can be hundreds of microseconds. It does not make sense to spin
even for that time.
go install -a std profile on a 4-core machine shows that 11% of time is spent
in the active spin in scang:
6.12% compile compile [.] runtime.scang
3.27% compile compile [.] runtime.readgstatus
1.72% compile compile [.] runtime/internal/atomic.Load
The active spin also increases tail latency in the case of the slightest
oversubscription: GC goroutines spend whole quantum in the loop instead of
executing user code.
Here is scang wait time histogram during go install -a std:
13707.0000 - 1815442.7667 [ 118]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎...
1815442.7667 - 3617178.5333 [ 9]: ∎∎∎∎∎∎∎∎∎
3617178.5333 - 5418914.3000 [ 11]: ∎∎∎∎∎∎∎∎∎∎∎
5418914.3000 - 7220650.0667 [ 5]: ∎∎∎∎∎
7220650.0667 - 9022385.8333 [ 12]: ∎∎∎∎∎∎∎∎∎∎∎∎
9022385.8333 - 10824121.6000 [ 13]: ∎∎∎∎∎∎∎∎∎∎∎∎∎
10824121.6000 - 12625857.3667 [ 15]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
12625857.3667 - 14427593.1333 [ 18]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
14427593.1333 - 16229328.9000 [ 18]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
16229328.9000 - 18031064.6667 [ 32]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
18031064.6667 - 19832800.4333 [ 28]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
19832800.4333 - 21634536.2000 [ 6]: ∎∎∎∎∎∎
21634536.2000 - 23436271.9667 [ 15]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
23436271.9667 - 25238007.7333 [ 11]: ∎∎∎∎∎∎∎∎∎∎∎
25238007.7333 - 27039743.5000 [ 27]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
27039743.5000 - 28841479.2667 [ 20]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
28841479.2667 - 30643215.0333 [ 10]: ∎∎∎∎∎∎∎∎∎∎
30643215.0333 - 32444950.8000 [ 7]: ∎∎∎∎∎∎∎
32444950.8000 - 34246686.5667 [ 4]: ∎∎∎∎
34246686.5667 - 36048422.3333 [ 4]: ∎∎∎∎
36048422.3333 - 37850158.1000 [ 1]: ∎
37850158.1000 - 39651893.8667 [ 5]: ∎∎∎∎∎
39651893.8667 - 41453629.6333 [ 2]: ∎∎
41453629.6333 - 43255365.4000 [ 2]: ∎∎
43255365.4000 - 45057101.1667 [ 2]: ∎∎
45057101.1667 - 46858836.9333 [ 1]: ∎
46858836.9333 - 48660572.7000 [ 2]: ∎∎
48660572.7000 - 50462308.4667 [ 3]: ∎∎∎
50462308.4667 - 52264044.2333 [ 2]: ∎∎
52264044.2333 - 54065780.0000 [ 2]: ∎∎
and the zoomed-in first part:
13707.0000 - 19916.7667 [ 2]: ∎∎
19916.7667 - 26126.5333 [ 2]: ∎∎
26126.5333 - 32336.3000 [ 9]: ∎∎∎∎∎∎∎∎∎
32336.3000 - 38546.0667 [ 8]: ∎∎∎∎∎∎∎∎
38546.0667 - 44755.8333 [ 12]: ∎∎∎∎∎∎∎∎∎∎∎∎
44755.8333 - 50965.6000 [ 10]: ∎∎∎∎∎∎∎∎∎∎
50965.6000 - 57175.3667 [ 5]: ∎∎∎∎∎
57175.3667 - 63385.1333 [ 6]: ∎∎∎∎∎∎
63385.1333 - 69594.9000 [ 5]: ∎∎∎∎∎
69594.9000 - 75804.6667 [ 6]: ∎∎∎∎∎∎
75804.6667 - 82014.4333 [ 6]: ∎∎∎∎∎∎
82014.4333 - 88224.2000 [ 4]: ∎∎∎∎
88224.2000 - 94433.9667 [ 1]: ∎
94433.9667 - 100643.7333 [ 1]: ∎
100643.7333 - 106853.5000 [ 2]: ∎∎
106853.5000 - 113063.2667 [ 0]:
113063.2667 - 119273.0333 [ 2]: ∎∎
119273.0333 - 125482.8000 [ 2]: ∎∎
125482.8000 - 131692.5667 [ 1]: ∎
131692.5667 - 137902.3333 [ 1]: ∎
137902.3333 - 144112.1000 [ 0]:
144112.1000 - 150321.8667 [ 2]: ∎∎
150321.8667 - 156531.6333 [ 1]: ∎
156531.6333 - 162741.4000 [ 1]: ∎
162741.4000 - 168951.1667 [ 0]:
168951.1667 - 175160.9333 [ 0]:
175160.9333 - 181370.7000 [ 1]: ∎
181370.7000 - 187580.4667 [ 1]: ∎
187580.4667 - 193790.2333 [ 2]: ∎∎
193790.2333 - 200000.0000 [ 0]:
Here is casgstatus wait time histogram:
631.0000 - 5276.6333 [ 3]: ∎∎∎
5276.6333 - 9922.2667 [ 5]: ∎∎∎∎∎
9922.2667 - 14567.9000 [ 2]: ∎∎
14567.9000 - 19213.5333 [ 6]: ∎∎∎∎∎∎
19213.5333 - 23859.1667 [ 5]: ∎∎∎∎∎
23859.1667 - 28504.8000 [ 6]: ∎∎∎∎∎∎
28504.8000 - 33150.4333 [ 6]: ∎∎∎∎∎∎
33150.4333 - 37796.0667 [ 2]: ∎∎
37796.0667 - 42441.7000 [ 1]: ∎
42441.7000 - 47087.3333 [ 3]: ∎∎∎
47087.3333 - 51732.9667 [ 0]:
51732.9667 - 56378.6000 [ 1]: ∎
56378.6000 - 61024.2333 [ 0]:
61024.2333 - 65669.8667 [ 0]:
65669.8667 - 70315.5000 [ 0]:
70315.5000 - 74961.1333 [ 1]: ∎
74961.1333 - 79606.7667 [ 0]:
79606.7667 - 84252.4000 [ 0]:
84252.4000 - 88898.0333 [ 0]:
88898.0333 - 93543.6667 [ 0]:
93543.6667 - 98189.3000 [ 0]:
98189.3000 - 102834.9333 [ 0]:
102834.9333 - 107480.5667 [ 1]: ∎
107480.5667 - 112126.2000 [ 0]:
112126.2000 - 116771.8333 [ 0]:
116771.8333 - 121417.4667 [ 0]:
121417.4667 - 126063.1000 [ 0]:
126063.1000 - 130708.7333 [ 0]:
130708.7333 - 135354.3667 [ 0]:
135354.3667 - 140000.0000 [ 1]: ∎
Ideally we eliminate the waiting by switching to async
state machine for GC, but for now just yield to OS scheduler
after a reasonable wait time.
To choose yielding parameters I've measured
golang.org/x/benchmarks/http tail latencies with different yield
delays and oversubscription levels.
With no oversubscription (to the degree possible):
scang yield delay = 1, casgstatus yield delay = 1
Latency-50 1.41ms ±15% 1.41ms ± 5% ~ (p=0.611 n=13+12)
Latency-95 5.21ms ± 2% 5.15ms ± 2% -1.15% (p=0.012 n=13+13)
Latency-99 7.16ms ± 2% 7.05ms ± 2% -1.54% (p=0.002 n=13+13)
Latency-999 10.7ms ± 9% 10.2ms ±10% -5.46% (p=0.004 n=12+13)
scang yield delay = 5000, casgstatus yield delay = 3000
Latency-50 1.41ms ±15% 1.41ms ± 8% ~ (p=0.511 n=13+13)
Latency-95 5.21ms ± 2% 5.14ms ± 2% -1.23% (p=0.006 n=13+13)
Latency-99 7.16ms ± 2% 7.02ms ± 2% -1.94% (p=0.000 n=13+13)
Latency-999 10.7ms ± 9% 10.1ms ± 8% -6.14% (p=0.000 n=12+13)
scang yield delay = 10000, casgstatus yield delay = 5000
Latency-50 1.41ms ±15% 1.45ms ± 6% ~ (p=0.724 n=13+13)
Latency-95 5.21ms ± 2% 5.18ms ± 1% ~ (p=0.287 n=13+13)
Latency-99 7.16ms ± 2% 7.05ms ± 2% -1.64% (p=0.002 n=13+13)
Latency-999 10.7ms ± 9% 10.0ms ± 5% -6.72% (p=0.000 n=12+13)
scang yield delay = 30000, casgstatus yield delay = 10000
Latency-50 1.41ms ±15% 1.51ms ± 7% +6.57% (p=0.002 n=13+13)
Latency-95 5.21ms ± 2% 5.21ms ± 2% ~ (p=0.960 n=13+13)
Latency-99 7.16ms ± 2% 7.06ms ± 2% -1.50% (p=0.012 n=13+13)
Latency-999 10.7ms ± 9% 10.0ms ± 6% -6.49% (p=0.000 n=12+13)
scang yield delay = 100000, casgstatus yield delay = 50000
Latency-50 1.41ms ±15% 1.53ms ± 6% +8.48% (p=0.000 n=13+12)
Latency-95 5.21ms ± 2% 5.23ms ± 2% ~ (p=0.287 n=13+13)
Latency-99 7.16ms ± 2% 7.08ms ± 2% -1.21% (p=0.004 n=13+13)
Latency-999 10.7ms ± 9% 9.9ms ± 3% -7.99% (p=0.000 n=12+12)
scang yield delay = 200000, casgstatus yield delay = 100000
Latency-50 1.41ms ±15% 1.47ms ± 5% ~ (p=0.072 n=13+13)
Latency-95 5.21ms ± 2% 5.17ms ± 2% ~ (p=0.091 n=13+13)
Latency-99 7.16ms ± 2% 7.02ms ± 2% -1.99% (p=0.000 n=13+13)
Latency-999 10.7ms ± 9% 9.9ms ± 5% -7.86% (p=0.000 n=12+13)
With slight oversubscription (another instance of http benchmark
was running in background with reduced GOMAXPROCS):
scang yield delay = 1, casgstatus yield delay = 1
Latency-50 840µs ± 3% 804µs ± 3% -4.37% (p=0.000 n=15+18)
Latency-95 6.52ms ± 4% 6.03ms ± 4% -7.51% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 10.0ms ± 4% -7.33% (p=0.000 n=18+14)
Latency-999 18.0ms ± 9% 16.8ms ± 7% -6.84% (p=0.000 n=18+18)
scang yield delay = 5000, casgstatus yield delay = 3000
Latency-50 840µs ± 3% 809µs ± 3% -3.71% (p=0.000 n=15+17)
Latency-95 6.52ms ± 4% 6.11ms ± 4% -6.29% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 9.9ms ± 6% -7.55% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.5ms ±11% -8.49% (p=0.000 n=18+18)
scang yield delay = 10000, casgstatus yield delay = 5000
Latency-50 840µs ± 3% 823µs ± 5% -2.06% (p=0.002 n=15+18)
Latency-95 6.52ms ± 4% 6.32ms ± 3% -3.05% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 4% -5.22% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.7ms ±10% -7.09% (p=0.000 n=18+18)
scang yield delay = 30000, casgstatus yield delay = 10000
Latency-50 840µs ± 3% 836µs ± 5% ~ (p=0.442 n=15+18)
Latency-95 6.52ms ± 4% 6.39ms ± 3% -2.00% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 6% -5.15% (p=0.000 n=18+17)
Latency-999 18.0ms ± 9% 16.6ms ± 8% -7.48% (p=0.000 n=18+18)
scang yield delay = 100000, casgstatus yield delay = 50000
Latency-50 840µs ± 3% 836µs ± 6% ~ (p=0.401 n=15+18)
Latency-95 6.52ms ± 4% 6.40ms ± 4% -1.79% (p=0.010 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 5% -4.95% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.5ms ±14% -8.17% (p=0.000 n=18+18)
scang yield delay = 200000, casgstatus yield delay = 100000
Latency-50 840µs ± 3% 828µs ± 2% -1.49% (p=0.001 n=15+17)
Latency-95 6.52ms ± 4% 6.38ms ± 4% -2.04% (p=0.001 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 4% -4.77% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.9ms ± 9% -6.23% (p=0.000 n=18+18)
With significant oversubscription (background http benchmark
was running with full GOMAXPROCS):
scang yield delay = 1, casgstatus yield delay = 1
Latency-50 1.32ms ±12% 1.30ms ±13% ~ (p=0.454 n=14+14)
Latency-95 16.3ms ±10% 15.3ms ± 7% -6.29% (p=0.001 n=14+14)
Latency-99 29.4ms ±10% 27.9ms ± 5% -5.04% (p=0.001 n=14+12)
Latency-999 49.9ms ±19% 45.9ms ± 5% -8.00% (p=0.008 n=14+13)
scang yield delay = 5000, casgstatus yield delay = 3000
Latency-50 1.32ms ±12% 1.29ms ± 9% ~ (p=0.227 n=14+14)
Latency-95 16.3ms ±10% 15.4ms ± 5% -5.27% (p=0.002 n=14+14)
Latency-99 29.4ms ±10% 27.9ms ± 6% -5.16% (p=0.001 n=14+14)
Latency-999 49.9ms ±19% 46.8ms ± 8% -6.21% (p=0.050 n=14+14)
scang yield delay = 10000, casgstatus yield delay = 5000
Latency-50 1.32ms ±12% 1.35ms ± 9% ~ (p=0.401 n=14+14)
Latency-95 16.3ms ±10% 15.0ms ± 4% -7.67% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.4ms ± 5% -6.98% (p=0.000 n=14+14)
Latency-999 49.9ms ±19% 44.7ms ± 5% -10.56% (p=0.000 n=14+11)
scang yield delay = 30000, casgstatus yield delay = 10000
Latency-50 1.32ms ±12% 1.36ms ±10% ~ (p=0.246 n=14+14)
Latency-95 16.3ms ±10% 14.9ms ± 5% -8.31% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.4ms ± 7% -6.70% (p=0.000 n=14+14)
Latency-999 49.9ms ±19% 44.9ms ±15% -10.13% (p=0.003 n=14+14)
scang yield delay = 100000, casgstatus yield delay = 50000
Latency-50 1.32ms ±12% 1.41ms ± 9% +6.37% (p=0.008 n=14+13)
Latency-95 16.3ms ±10% 15.1ms ± 8% -7.45% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.5ms ±12% -6.67% (p=0.002 n=14+14)
Latency-999 49.9ms ±19% 45.9ms ±16% -8.06% (p=0.019 n=14+14)
scang yield delay = 200000, casgstatus yield delay = 100000
Latency-50 1.32ms ±12% 1.42ms ±10% +7.21% (p=0.003 n=14+14)
Latency-95 16.3ms ±10% 15.0ms ± 7% -7.59% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.3ms ± 8% -7.20% (p=0.000 n=14+14)
Latency-999 49.9ms ±19% 44.8ms ± 8% -10.21% (p=0.001 n=14+13)
All numbers are on 8 cores and with GOGC=10 (http benchmark has
tiny heap, few goroutines and low allocation rate, so by default
GC barely affects tail latency).
10us/5us yield delays seem to provide a reasonable compromise
and give 5-10% tail latency reduction. That's what used in this change.
go install -a std results on 4 core machine:
name old time/op new time/op delta
Time 8.39s ± 2% 7.94s ± 2% -5.34% (p=0.000 n=47+49)
UserTime 24.6s ± 2% 22.9s ± 2% -6.76% (p=0.000 n=49+49)
SysTime 1.77s ± 9% 1.89s ±11% +7.00% (p=0.000 n=49+49)
CpuLoad 315ns ± 2% 313ns ± 1% -0.59% (p=0.000 n=49+48) # %CPU
MaxRSS 97.1ms ± 4% 97.5ms ± 9% ~ (p=0.838 n=46+49) # bytes
Update #14396
Update #14189
Change-Id: I3f4109bf8f7fd79b39c466576690a778232055a2
Reviewed-on: https://go-review.googlesource.com/21503
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rick Hudson <rlh@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2016-04-04 16:22:38 +02:00
|
|
|
const yieldDelay = 5 * 1000
|
|
|
|
|
var nextYield int64
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// loop if gp->atomicstatus is in a scan state giving
|
|
|
|
|
// GC time to finish and change the state to oldval.
|
2022-08-25 03:27:02 +08:00
|
|
|
for i := 0; !gp.atomicstatus.CompareAndSwap(oldval, newval); i++ {
|
|
|
|
|
if oldval == _Gwaiting && gp.atomicstatus.Load() == _Grunnable {
|
2018-01-12 12:39:22 -05:00
|
|
|
throw("casgstatus: waiting for Gwaiting but is Grunnable")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
runtime: don't burn CPU unnecessarily
Two GC-related functions, scang and casgstatus, wait in an active spin loop.
Active spinning is never a good idea in user-space. Once we wait several
times more than the expected wait time, something unexpected is happenning
(e.g. the thread we are waiting for is descheduled or handling a page fault)
and we need to yield to OS scheduler. Moreover, the expected wait time is
very high for these functions: scang wait time can be tens of milliseconds,
casgstatus can be hundreds of microseconds. It does not make sense to spin
even for that time.
go install -a std profile on a 4-core machine shows that 11% of time is spent
in the active spin in scang:
6.12% compile compile [.] runtime.scang
3.27% compile compile [.] runtime.readgstatus
1.72% compile compile [.] runtime/internal/atomic.Load
The active spin also increases tail latency in the case of the slightest
oversubscription: GC goroutines spend whole quantum in the loop instead of
executing user code.
Here is scang wait time histogram during go install -a std:
13707.0000 - 1815442.7667 [ 118]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎...
1815442.7667 - 3617178.5333 [ 9]: ∎∎∎∎∎∎∎∎∎
3617178.5333 - 5418914.3000 [ 11]: ∎∎∎∎∎∎∎∎∎∎∎
5418914.3000 - 7220650.0667 [ 5]: ∎∎∎∎∎
7220650.0667 - 9022385.8333 [ 12]: ∎∎∎∎∎∎∎∎∎∎∎∎
9022385.8333 - 10824121.6000 [ 13]: ∎∎∎∎∎∎∎∎∎∎∎∎∎
10824121.6000 - 12625857.3667 [ 15]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
12625857.3667 - 14427593.1333 [ 18]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
14427593.1333 - 16229328.9000 [ 18]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
16229328.9000 - 18031064.6667 [ 32]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
18031064.6667 - 19832800.4333 [ 28]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
19832800.4333 - 21634536.2000 [ 6]: ∎∎∎∎∎∎
21634536.2000 - 23436271.9667 [ 15]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
23436271.9667 - 25238007.7333 [ 11]: ∎∎∎∎∎∎∎∎∎∎∎
25238007.7333 - 27039743.5000 [ 27]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
27039743.5000 - 28841479.2667 [ 20]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
28841479.2667 - 30643215.0333 [ 10]: ∎∎∎∎∎∎∎∎∎∎
30643215.0333 - 32444950.8000 [ 7]: ∎∎∎∎∎∎∎
32444950.8000 - 34246686.5667 [ 4]: ∎∎∎∎
34246686.5667 - 36048422.3333 [ 4]: ∎∎∎∎
36048422.3333 - 37850158.1000 [ 1]: ∎
37850158.1000 - 39651893.8667 [ 5]: ∎∎∎∎∎
39651893.8667 - 41453629.6333 [ 2]: ∎∎
41453629.6333 - 43255365.4000 [ 2]: ∎∎
43255365.4000 - 45057101.1667 [ 2]: ∎∎
45057101.1667 - 46858836.9333 [ 1]: ∎
46858836.9333 - 48660572.7000 [ 2]: ∎∎
48660572.7000 - 50462308.4667 [ 3]: ∎∎∎
50462308.4667 - 52264044.2333 [ 2]: ∎∎
52264044.2333 - 54065780.0000 [ 2]: ∎∎
and the zoomed-in first part:
13707.0000 - 19916.7667 [ 2]: ∎∎
19916.7667 - 26126.5333 [ 2]: ∎∎
26126.5333 - 32336.3000 [ 9]: ∎∎∎∎∎∎∎∎∎
32336.3000 - 38546.0667 [ 8]: ∎∎∎∎∎∎∎∎
38546.0667 - 44755.8333 [ 12]: ∎∎∎∎∎∎∎∎∎∎∎∎
44755.8333 - 50965.6000 [ 10]: ∎∎∎∎∎∎∎∎∎∎
50965.6000 - 57175.3667 [ 5]: ∎∎∎∎∎
57175.3667 - 63385.1333 [ 6]: ∎∎∎∎∎∎
63385.1333 - 69594.9000 [ 5]: ∎∎∎∎∎
69594.9000 - 75804.6667 [ 6]: ∎∎∎∎∎∎
75804.6667 - 82014.4333 [ 6]: ∎∎∎∎∎∎
82014.4333 - 88224.2000 [ 4]: ∎∎∎∎
88224.2000 - 94433.9667 [ 1]: ∎
94433.9667 - 100643.7333 [ 1]: ∎
100643.7333 - 106853.5000 [ 2]: ∎∎
106853.5000 - 113063.2667 [ 0]:
113063.2667 - 119273.0333 [ 2]: ∎∎
119273.0333 - 125482.8000 [ 2]: ∎∎
125482.8000 - 131692.5667 [ 1]: ∎
131692.5667 - 137902.3333 [ 1]: ∎
137902.3333 - 144112.1000 [ 0]:
144112.1000 - 150321.8667 [ 2]: ∎∎
150321.8667 - 156531.6333 [ 1]: ∎
156531.6333 - 162741.4000 [ 1]: ∎
162741.4000 - 168951.1667 [ 0]:
168951.1667 - 175160.9333 [ 0]:
175160.9333 - 181370.7000 [ 1]: ∎
181370.7000 - 187580.4667 [ 1]: ∎
187580.4667 - 193790.2333 [ 2]: ∎∎
193790.2333 - 200000.0000 [ 0]:
Here is casgstatus wait time histogram:
631.0000 - 5276.6333 [ 3]: ∎∎∎
5276.6333 - 9922.2667 [ 5]: ∎∎∎∎∎
9922.2667 - 14567.9000 [ 2]: ∎∎
14567.9000 - 19213.5333 [ 6]: ∎∎∎∎∎∎
19213.5333 - 23859.1667 [ 5]: ∎∎∎∎∎
23859.1667 - 28504.8000 [ 6]: ∎∎∎∎∎∎
28504.8000 - 33150.4333 [ 6]: ∎∎∎∎∎∎
33150.4333 - 37796.0667 [ 2]: ∎∎
37796.0667 - 42441.7000 [ 1]: ∎
42441.7000 - 47087.3333 [ 3]: ∎∎∎
47087.3333 - 51732.9667 [ 0]:
51732.9667 - 56378.6000 [ 1]: ∎
56378.6000 - 61024.2333 [ 0]:
61024.2333 - 65669.8667 [ 0]:
65669.8667 - 70315.5000 [ 0]:
70315.5000 - 74961.1333 [ 1]: ∎
74961.1333 - 79606.7667 [ 0]:
79606.7667 - 84252.4000 [ 0]:
84252.4000 - 88898.0333 [ 0]:
88898.0333 - 93543.6667 [ 0]:
93543.6667 - 98189.3000 [ 0]:
98189.3000 - 102834.9333 [ 0]:
102834.9333 - 107480.5667 [ 1]: ∎
107480.5667 - 112126.2000 [ 0]:
112126.2000 - 116771.8333 [ 0]:
116771.8333 - 121417.4667 [ 0]:
121417.4667 - 126063.1000 [ 0]:
126063.1000 - 130708.7333 [ 0]:
130708.7333 - 135354.3667 [ 0]:
135354.3667 - 140000.0000 [ 1]: ∎
Ideally we eliminate the waiting by switching to async
state machine for GC, but for now just yield to OS scheduler
after a reasonable wait time.
To choose yielding parameters I've measured
golang.org/x/benchmarks/http tail latencies with different yield
delays and oversubscription levels.
With no oversubscription (to the degree possible):
scang yield delay = 1, casgstatus yield delay = 1
Latency-50 1.41ms ±15% 1.41ms ± 5% ~ (p=0.611 n=13+12)
Latency-95 5.21ms ± 2% 5.15ms ± 2% -1.15% (p=0.012 n=13+13)
Latency-99 7.16ms ± 2% 7.05ms ± 2% -1.54% (p=0.002 n=13+13)
Latency-999 10.7ms ± 9% 10.2ms ±10% -5.46% (p=0.004 n=12+13)
scang yield delay = 5000, casgstatus yield delay = 3000
Latency-50 1.41ms ±15% 1.41ms ± 8% ~ (p=0.511 n=13+13)
Latency-95 5.21ms ± 2% 5.14ms ± 2% -1.23% (p=0.006 n=13+13)
Latency-99 7.16ms ± 2% 7.02ms ± 2% -1.94% (p=0.000 n=13+13)
Latency-999 10.7ms ± 9% 10.1ms ± 8% -6.14% (p=0.000 n=12+13)
scang yield delay = 10000, casgstatus yield delay = 5000
Latency-50 1.41ms ±15% 1.45ms ± 6% ~ (p=0.724 n=13+13)
Latency-95 5.21ms ± 2% 5.18ms ± 1% ~ (p=0.287 n=13+13)
Latency-99 7.16ms ± 2% 7.05ms ± 2% -1.64% (p=0.002 n=13+13)
Latency-999 10.7ms ± 9% 10.0ms ± 5% -6.72% (p=0.000 n=12+13)
scang yield delay = 30000, casgstatus yield delay = 10000
Latency-50 1.41ms ±15% 1.51ms ± 7% +6.57% (p=0.002 n=13+13)
Latency-95 5.21ms ± 2% 5.21ms ± 2% ~ (p=0.960 n=13+13)
Latency-99 7.16ms ± 2% 7.06ms ± 2% -1.50% (p=0.012 n=13+13)
Latency-999 10.7ms ± 9% 10.0ms ± 6% -6.49% (p=0.000 n=12+13)
scang yield delay = 100000, casgstatus yield delay = 50000
Latency-50 1.41ms ±15% 1.53ms ± 6% +8.48% (p=0.000 n=13+12)
Latency-95 5.21ms ± 2% 5.23ms ± 2% ~ (p=0.287 n=13+13)
Latency-99 7.16ms ± 2% 7.08ms ± 2% -1.21% (p=0.004 n=13+13)
Latency-999 10.7ms ± 9% 9.9ms ± 3% -7.99% (p=0.000 n=12+12)
scang yield delay = 200000, casgstatus yield delay = 100000
Latency-50 1.41ms ±15% 1.47ms ± 5% ~ (p=0.072 n=13+13)
Latency-95 5.21ms ± 2% 5.17ms ± 2% ~ (p=0.091 n=13+13)
Latency-99 7.16ms ± 2% 7.02ms ± 2% -1.99% (p=0.000 n=13+13)
Latency-999 10.7ms ± 9% 9.9ms ± 5% -7.86% (p=0.000 n=12+13)
With slight oversubscription (another instance of http benchmark
was running in background with reduced GOMAXPROCS):
scang yield delay = 1, casgstatus yield delay = 1
Latency-50 840µs ± 3% 804µs ± 3% -4.37% (p=0.000 n=15+18)
Latency-95 6.52ms ± 4% 6.03ms ± 4% -7.51% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 10.0ms ± 4% -7.33% (p=0.000 n=18+14)
Latency-999 18.0ms ± 9% 16.8ms ± 7% -6.84% (p=0.000 n=18+18)
scang yield delay = 5000, casgstatus yield delay = 3000
Latency-50 840µs ± 3% 809µs ± 3% -3.71% (p=0.000 n=15+17)
Latency-95 6.52ms ± 4% 6.11ms ± 4% -6.29% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 9.9ms ± 6% -7.55% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.5ms ±11% -8.49% (p=0.000 n=18+18)
scang yield delay = 10000, casgstatus yield delay = 5000
Latency-50 840µs ± 3% 823µs ± 5% -2.06% (p=0.002 n=15+18)
Latency-95 6.52ms ± 4% 6.32ms ± 3% -3.05% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 4% -5.22% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.7ms ±10% -7.09% (p=0.000 n=18+18)
scang yield delay = 30000, casgstatus yield delay = 10000
Latency-50 840µs ± 3% 836µs ± 5% ~ (p=0.442 n=15+18)
Latency-95 6.52ms ± 4% 6.39ms ± 3% -2.00% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 6% -5.15% (p=0.000 n=18+17)
Latency-999 18.0ms ± 9% 16.6ms ± 8% -7.48% (p=0.000 n=18+18)
scang yield delay = 100000, casgstatus yield delay = 50000
Latency-50 840µs ± 3% 836µs ± 6% ~ (p=0.401 n=15+18)
Latency-95 6.52ms ± 4% 6.40ms ± 4% -1.79% (p=0.010 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 5% -4.95% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.5ms ±14% -8.17% (p=0.000 n=18+18)
scang yield delay = 200000, casgstatus yield delay = 100000
Latency-50 840µs ± 3% 828µs ± 2% -1.49% (p=0.001 n=15+17)
Latency-95 6.52ms ± 4% 6.38ms ± 4% -2.04% (p=0.001 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 4% -4.77% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.9ms ± 9% -6.23% (p=0.000 n=18+18)
With significant oversubscription (background http benchmark
was running with full GOMAXPROCS):
scang yield delay = 1, casgstatus yield delay = 1
Latency-50 1.32ms ±12% 1.30ms ±13% ~ (p=0.454 n=14+14)
Latency-95 16.3ms ±10% 15.3ms ± 7% -6.29% (p=0.001 n=14+14)
Latency-99 29.4ms ±10% 27.9ms ± 5% -5.04% (p=0.001 n=14+12)
Latency-999 49.9ms ±19% 45.9ms ± 5% -8.00% (p=0.008 n=14+13)
scang yield delay = 5000, casgstatus yield delay = 3000
Latency-50 1.32ms ±12% 1.29ms ± 9% ~ (p=0.227 n=14+14)
Latency-95 16.3ms ±10% 15.4ms ± 5% -5.27% (p=0.002 n=14+14)
Latency-99 29.4ms ±10% 27.9ms ± 6% -5.16% (p=0.001 n=14+14)
Latency-999 49.9ms ±19% 46.8ms ± 8% -6.21% (p=0.050 n=14+14)
scang yield delay = 10000, casgstatus yield delay = 5000
Latency-50 1.32ms ±12% 1.35ms ± 9% ~ (p=0.401 n=14+14)
Latency-95 16.3ms ±10% 15.0ms ± 4% -7.67% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.4ms ± 5% -6.98% (p=0.000 n=14+14)
Latency-999 49.9ms ±19% 44.7ms ± 5% -10.56% (p=0.000 n=14+11)
scang yield delay = 30000, casgstatus yield delay = 10000
Latency-50 1.32ms ±12% 1.36ms ±10% ~ (p=0.246 n=14+14)
Latency-95 16.3ms ±10% 14.9ms ± 5% -8.31% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.4ms ± 7% -6.70% (p=0.000 n=14+14)
Latency-999 49.9ms ±19% 44.9ms ±15% -10.13% (p=0.003 n=14+14)
scang yield delay = 100000, casgstatus yield delay = 50000
Latency-50 1.32ms ±12% 1.41ms ± 9% +6.37% (p=0.008 n=14+13)
Latency-95 16.3ms ±10% 15.1ms ± 8% -7.45% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.5ms ±12% -6.67% (p=0.002 n=14+14)
Latency-999 49.9ms ±19% 45.9ms ±16% -8.06% (p=0.019 n=14+14)
scang yield delay = 200000, casgstatus yield delay = 100000
Latency-50 1.32ms ±12% 1.42ms ±10% +7.21% (p=0.003 n=14+14)
Latency-95 16.3ms ±10% 15.0ms ± 7% -7.59% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.3ms ± 8% -7.20% (p=0.000 n=14+14)
Latency-999 49.9ms ±19% 44.8ms ± 8% -10.21% (p=0.001 n=14+13)
All numbers are on 8 cores and with GOGC=10 (http benchmark has
tiny heap, few goroutines and low allocation rate, so by default
GC barely affects tail latency).
10us/5us yield delays seem to provide a reasonable compromise
and give 5-10% tail latency reduction. That's what used in this change.
go install -a std results on 4 core machine:
name old time/op new time/op delta
Time 8.39s ± 2% 7.94s ± 2% -5.34% (p=0.000 n=47+49)
UserTime 24.6s ± 2% 22.9s ± 2% -6.76% (p=0.000 n=49+49)
SysTime 1.77s ± 9% 1.89s ±11% +7.00% (p=0.000 n=49+49)
CpuLoad 315ns ± 2% 313ns ± 1% -0.59% (p=0.000 n=49+48) # %CPU
MaxRSS 97.1ms ± 4% 97.5ms ± 9% ~ (p=0.838 n=46+49) # bytes
Update #14396
Update #14189
Change-Id: I3f4109bf8f7fd79b39c466576690a778232055a2
Reviewed-on: https://go-review.googlesource.com/21503
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rick Hudson <rlh@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2016-04-04 16:22:38 +02:00
|
|
|
if i == 0 {
|
|
|
|
|
nextYield = nanotime() + yieldDelay
|
|
|
|
|
}
|
|
|
|
|
if nanotime() < nextYield {
|
2022-08-25 03:27:02 +08:00
|
|
|
for x := 0; x < 10 && gp.atomicstatus.Load() != oldval; x++ {
|
runtime: don't burn CPU unnecessarily
Two GC-related functions, scang and casgstatus, wait in an active spin loop.
Active spinning is never a good idea in user-space. Once we wait several
times more than the expected wait time, something unexpected is happenning
(e.g. the thread we are waiting for is descheduled or handling a page fault)
and we need to yield to OS scheduler. Moreover, the expected wait time is
very high for these functions: scang wait time can be tens of milliseconds,
casgstatus can be hundreds of microseconds. It does not make sense to spin
even for that time.
go install -a std profile on a 4-core machine shows that 11% of time is spent
in the active spin in scang:
6.12% compile compile [.] runtime.scang
3.27% compile compile [.] runtime.readgstatus
1.72% compile compile [.] runtime/internal/atomic.Load
The active spin also increases tail latency in the case of the slightest
oversubscription: GC goroutines spend whole quantum in the loop instead of
executing user code.
Here is scang wait time histogram during go install -a std:
13707.0000 - 1815442.7667 [ 118]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎...
1815442.7667 - 3617178.5333 [ 9]: ∎∎∎∎∎∎∎∎∎
3617178.5333 - 5418914.3000 [ 11]: ∎∎∎∎∎∎∎∎∎∎∎
5418914.3000 - 7220650.0667 [ 5]: ∎∎∎∎∎
7220650.0667 - 9022385.8333 [ 12]: ∎∎∎∎∎∎∎∎∎∎∎∎
9022385.8333 - 10824121.6000 [ 13]: ∎∎∎∎∎∎∎∎∎∎∎∎∎
10824121.6000 - 12625857.3667 [ 15]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
12625857.3667 - 14427593.1333 [ 18]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
14427593.1333 - 16229328.9000 [ 18]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
16229328.9000 - 18031064.6667 [ 32]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
18031064.6667 - 19832800.4333 [ 28]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
19832800.4333 - 21634536.2000 [ 6]: ∎∎∎∎∎∎
21634536.2000 - 23436271.9667 [ 15]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
23436271.9667 - 25238007.7333 [ 11]: ∎∎∎∎∎∎∎∎∎∎∎
25238007.7333 - 27039743.5000 [ 27]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
27039743.5000 - 28841479.2667 [ 20]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
28841479.2667 - 30643215.0333 [ 10]: ∎∎∎∎∎∎∎∎∎∎
30643215.0333 - 32444950.8000 [ 7]: ∎∎∎∎∎∎∎
32444950.8000 - 34246686.5667 [ 4]: ∎∎∎∎
34246686.5667 - 36048422.3333 [ 4]: ∎∎∎∎
36048422.3333 - 37850158.1000 [ 1]: ∎
37850158.1000 - 39651893.8667 [ 5]: ∎∎∎∎∎
39651893.8667 - 41453629.6333 [ 2]: ∎∎
41453629.6333 - 43255365.4000 [ 2]: ∎∎
43255365.4000 - 45057101.1667 [ 2]: ∎∎
45057101.1667 - 46858836.9333 [ 1]: ∎
46858836.9333 - 48660572.7000 [ 2]: ∎∎
48660572.7000 - 50462308.4667 [ 3]: ∎∎∎
50462308.4667 - 52264044.2333 [ 2]: ∎∎
52264044.2333 - 54065780.0000 [ 2]: ∎∎
and the zoomed-in first part:
13707.0000 - 19916.7667 [ 2]: ∎∎
19916.7667 - 26126.5333 [ 2]: ∎∎
26126.5333 - 32336.3000 [ 9]: ∎∎∎∎∎∎∎∎∎
32336.3000 - 38546.0667 [ 8]: ∎∎∎∎∎∎∎∎
38546.0667 - 44755.8333 [ 12]: ∎∎∎∎∎∎∎∎∎∎∎∎
44755.8333 - 50965.6000 [ 10]: ∎∎∎∎∎∎∎∎∎∎
50965.6000 - 57175.3667 [ 5]: ∎∎∎∎∎
57175.3667 - 63385.1333 [ 6]: ∎∎∎∎∎∎
63385.1333 - 69594.9000 [ 5]: ∎∎∎∎∎
69594.9000 - 75804.6667 [ 6]: ∎∎∎∎∎∎
75804.6667 - 82014.4333 [ 6]: ∎∎∎∎∎∎
82014.4333 - 88224.2000 [ 4]: ∎∎∎∎
88224.2000 - 94433.9667 [ 1]: ∎
94433.9667 - 100643.7333 [ 1]: ∎
100643.7333 - 106853.5000 [ 2]: ∎∎
106853.5000 - 113063.2667 [ 0]:
113063.2667 - 119273.0333 [ 2]: ∎∎
119273.0333 - 125482.8000 [ 2]: ∎∎
125482.8000 - 131692.5667 [ 1]: ∎
131692.5667 - 137902.3333 [ 1]: ∎
137902.3333 - 144112.1000 [ 0]:
144112.1000 - 150321.8667 [ 2]: ∎∎
150321.8667 - 156531.6333 [ 1]: ∎
156531.6333 - 162741.4000 [ 1]: ∎
162741.4000 - 168951.1667 [ 0]:
168951.1667 - 175160.9333 [ 0]:
175160.9333 - 181370.7000 [ 1]: ∎
181370.7000 - 187580.4667 [ 1]: ∎
187580.4667 - 193790.2333 [ 2]: ∎∎
193790.2333 - 200000.0000 [ 0]:
Here is casgstatus wait time histogram:
631.0000 - 5276.6333 [ 3]: ∎∎∎
5276.6333 - 9922.2667 [ 5]: ∎∎∎∎∎
9922.2667 - 14567.9000 [ 2]: ∎∎
14567.9000 - 19213.5333 [ 6]: ∎∎∎∎∎∎
19213.5333 - 23859.1667 [ 5]: ∎∎∎∎∎
23859.1667 - 28504.8000 [ 6]: ∎∎∎∎∎∎
28504.8000 - 33150.4333 [ 6]: ∎∎∎∎∎∎
33150.4333 - 37796.0667 [ 2]: ∎∎
37796.0667 - 42441.7000 [ 1]: ∎
42441.7000 - 47087.3333 [ 3]: ∎∎∎
47087.3333 - 51732.9667 [ 0]:
51732.9667 - 56378.6000 [ 1]: ∎
56378.6000 - 61024.2333 [ 0]:
61024.2333 - 65669.8667 [ 0]:
65669.8667 - 70315.5000 [ 0]:
70315.5000 - 74961.1333 [ 1]: ∎
74961.1333 - 79606.7667 [ 0]:
79606.7667 - 84252.4000 [ 0]:
84252.4000 - 88898.0333 [ 0]:
88898.0333 - 93543.6667 [ 0]:
93543.6667 - 98189.3000 [ 0]:
98189.3000 - 102834.9333 [ 0]:
102834.9333 - 107480.5667 [ 1]: ∎
107480.5667 - 112126.2000 [ 0]:
112126.2000 - 116771.8333 [ 0]:
116771.8333 - 121417.4667 [ 0]:
121417.4667 - 126063.1000 [ 0]:
126063.1000 - 130708.7333 [ 0]:
130708.7333 - 135354.3667 [ 0]:
135354.3667 - 140000.0000 [ 1]: ∎
Ideally we eliminate the waiting by switching to async
state machine for GC, but for now just yield to OS scheduler
after a reasonable wait time.
To choose yielding parameters I've measured
golang.org/x/benchmarks/http tail latencies with different yield
delays and oversubscription levels.
With no oversubscription (to the degree possible):
scang yield delay = 1, casgstatus yield delay = 1
Latency-50 1.41ms ±15% 1.41ms ± 5% ~ (p=0.611 n=13+12)
Latency-95 5.21ms ± 2% 5.15ms ± 2% -1.15% (p=0.012 n=13+13)
Latency-99 7.16ms ± 2% 7.05ms ± 2% -1.54% (p=0.002 n=13+13)
Latency-999 10.7ms ± 9% 10.2ms ±10% -5.46% (p=0.004 n=12+13)
scang yield delay = 5000, casgstatus yield delay = 3000
Latency-50 1.41ms ±15% 1.41ms ± 8% ~ (p=0.511 n=13+13)
Latency-95 5.21ms ± 2% 5.14ms ± 2% -1.23% (p=0.006 n=13+13)
Latency-99 7.16ms ± 2% 7.02ms ± 2% -1.94% (p=0.000 n=13+13)
Latency-999 10.7ms ± 9% 10.1ms ± 8% -6.14% (p=0.000 n=12+13)
scang yield delay = 10000, casgstatus yield delay = 5000
Latency-50 1.41ms ±15% 1.45ms ± 6% ~ (p=0.724 n=13+13)
Latency-95 5.21ms ± 2% 5.18ms ± 1% ~ (p=0.287 n=13+13)
Latency-99 7.16ms ± 2% 7.05ms ± 2% -1.64% (p=0.002 n=13+13)
Latency-999 10.7ms ± 9% 10.0ms ± 5% -6.72% (p=0.000 n=12+13)
scang yield delay = 30000, casgstatus yield delay = 10000
Latency-50 1.41ms ±15% 1.51ms ± 7% +6.57% (p=0.002 n=13+13)
Latency-95 5.21ms ± 2% 5.21ms ± 2% ~ (p=0.960 n=13+13)
Latency-99 7.16ms ± 2% 7.06ms ± 2% -1.50% (p=0.012 n=13+13)
Latency-999 10.7ms ± 9% 10.0ms ± 6% -6.49% (p=0.000 n=12+13)
scang yield delay = 100000, casgstatus yield delay = 50000
Latency-50 1.41ms ±15% 1.53ms ± 6% +8.48% (p=0.000 n=13+12)
Latency-95 5.21ms ± 2% 5.23ms ± 2% ~ (p=0.287 n=13+13)
Latency-99 7.16ms ± 2% 7.08ms ± 2% -1.21% (p=0.004 n=13+13)
Latency-999 10.7ms ± 9% 9.9ms ± 3% -7.99% (p=0.000 n=12+12)
scang yield delay = 200000, casgstatus yield delay = 100000
Latency-50 1.41ms ±15% 1.47ms ± 5% ~ (p=0.072 n=13+13)
Latency-95 5.21ms ± 2% 5.17ms ± 2% ~ (p=0.091 n=13+13)
Latency-99 7.16ms ± 2% 7.02ms ± 2% -1.99% (p=0.000 n=13+13)
Latency-999 10.7ms ± 9% 9.9ms ± 5% -7.86% (p=0.000 n=12+13)
With slight oversubscription (another instance of http benchmark
was running in background with reduced GOMAXPROCS):
scang yield delay = 1, casgstatus yield delay = 1
Latency-50 840µs ± 3% 804µs ± 3% -4.37% (p=0.000 n=15+18)
Latency-95 6.52ms ± 4% 6.03ms ± 4% -7.51% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 10.0ms ± 4% -7.33% (p=0.000 n=18+14)
Latency-999 18.0ms ± 9% 16.8ms ± 7% -6.84% (p=0.000 n=18+18)
scang yield delay = 5000, casgstatus yield delay = 3000
Latency-50 840µs ± 3% 809µs ± 3% -3.71% (p=0.000 n=15+17)
Latency-95 6.52ms ± 4% 6.11ms ± 4% -6.29% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 9.9ms ± 6% -7.55% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.5ms ±11% -8.49% (p=0.000 n=18+18)
scang yield delay = 10000, casgstatus yield delay = 5000
Latency-50 840µs ± 3% 823µs ± 5% -2.06% (p=0.002 n=15+18)
Latency-95 6.52ms ± 4% 6.32ms ± 3% -3.05% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 4% -5.22% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.7ms ±10% -7.09% (p=0.000 n=18+18)
scang yield delay = 30000, casgstatus yield delay = 10000
Latency-50 840µs ± 3% 836µs ± 5% ~ (p=0.442 n=15+18)
Latency-95 6.52ms ± 4% 6.39ms ± 3% -2.00% (p=0.000 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 6% -5.15% (p=0.000 n=18+17)
Latency-999 18.0ms ± 9% 16.6ms ± 8% -7.48% (p=0.000 n=18+18)
scang yield delay = 100000, casgstatus yield delay = 50000
Latency-50 840µs ± 3% 836µs ± 6% ~ (p=0.401 n=15+18)
Latency-95 6.52ms ± 4% 6.40ms ± 4% -1.79% (p=0.010 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 5% -4.95% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.5ms ±14% -8.17% (p=0.000 n=18+18)
scang yield delay = 200000, casgstatus yield delay = 100000
Latency-50 840µs ± 3% 828µs ± 2% -1.49% (p=0.001 n=15+17)
Latency-95 6.52ms ± 4% 6.38ms ± 4% -2.04% (p=0.001 n=18+18)
Latency-99 10.8ms ± 7% 10.2ms ± 4% -4.77% (p=0.000 n=18+18)
Latency-999 18.0ms ± 9% 16.9ms ± 9% -6.23% (p=0.000 n=18+18)
With significant oversubscription (background http benchmark
was running with full GOMAXPROCS):
scang yield delay = 1, casgstatus yield delay = 1
Latency-50 1.32ms ±12% 1.30ms ±13% ~ (p=0.454 n=14+14)
Latency-95 16.3ms ±10% 15.3ms ± 7% -6.29% (p=0.001 n=14+14)
Latency-99 29.4ms ±10% 27.9ms ± 5% -5.04% (p=0.001 n=14+12)
Latency-999 49.9ms ±19% 45.9ms ± 5% -8.00% (p=0.008 n=14+13)
scang yield delay = 5000, casgstatus yield delay = 3000
Latency-50 1.32ms ±12% 1.29ms ± 9% ~ (p=0.227 n=14+14)
Latency-95 16.3ms ±10% 15.4ms ± 5% -5.27% (p=0.002 n=14+14)
Latency-99 29.4ms ±10% 27.9ms ± 6% -5.16% (p=0.001 n=14+14)
Latency-999 49.9ms ±19% 46.8ms ± 8% -6.21% (p=0.050 n=14+14)
scang yield delay = 10000, casgstatus yield delay = 5000
Latency-50 1.32ms ±12% 1.35ms ± 9% ~ (p=0.401 n=14+14)
Latency-95 16.3ms ±10% 15.0ms ± 4% -7.67% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.4ms ± 5% -6.98% (p=0.000 n=14+14)
Latency-999 49.9ms ±19% 44.7ms ± 5% -10.56% (p=0.000 n=14+11)
scang yield delay = 30000, casgstatus yield delay = 10000
Latency-50 1.32ms ±12% 1.36ms ±10% ~ (p=0.246 n=14+14)
Latency-95 16.3ms ±10% 14.9ms ± 5% -8.31% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.4ms ± 7% -6.70% (p=0.000 n=14+14)
Latency-999 49.9ms ±19% 44.9ms ±15% -10.13% (p=0.003 n=14+14)
scang yield delay = 100000, casgstatus yield delay = 50000
Latency-50 1.32ms ±12% 1.41ms ± 9% +6.37% (p=0.008 n=14+13)
Latency-95 16.3ms ±10% 15.1ms ± 8% -7.45% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.5ms ±12% -6.67% (p=0.002 n=14+14)
Latency-999 49.9ms ±19% 45.9ms ±16% -8.06% (p=0.019 n=14+14)
scang yield delay = 200000, casgstatus yield delay = 100000
Latency-50 1.32ms ±12% 1.42ms ±10% +7.21% (p=0.003 n=14+14)
Latency-95 16.3ms ±10% 15.0ms ± 7% -7.59% (p=0.000 n=14+14)
Latency-99 29.4ms ±10% 27.3ms ± 8% -7.20% (p=0.000 n=14+14)
Latency-999 49.9ms ±19% 44.8ms ± 8% -10.21% (p=0.001 n=14+13)
All numbers are on 8 cores and with GOGC=10 (http benchmark has
tiny heap, few goroutines and low allocation rate, so by default
GC barely affects tail latency).
10us/5us yield delays seem to provide a reasonable compromise
and give 5-10% tail latency reduction. That's what used in this change.
go install -a std results on 4 core machine:
name old time/op new time/op delta
Time 8.39s ± 2% 7.94s ± 2% -5.34% (p=0.000 n=47+49)
UserTime 24.6s ± 2% 22.9s ± 2% -6.76% (p=0.000 n=49+49)
SysTime 1.77s ± 9% 1.89s ±11% +7.00% (p=0.000 n=49+49)
CpuLoad 315ns ± 2% 313ns ± 1% -0.59% (p=0.000 n=49+48) # %CPU
MaxRSS 97.1ms ± 4% 97.5ms ± 9% ~ (p=0.838 n=46+49) # bytes
Update #14396
Update #14189
Change-Id: I3f4109bf8f7fd79b39c466576690a778232055a2
Reviewed-on: https://go-review.googlesource.com/21503
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rick Hudson <rlh@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2016-04-04 16:22:38 +02:00
|
|
|
procyield(1)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
osyield()
|
|
|
|
|
nextYield = nanotime() + yieldDelay/2
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2020-08-07 16:28:35 +00:00
|
|
|
|
|
|
|
|
if oldval == _Grunning {
|
2022-08-31 21:34:23 +00:00
|
|
|
// Track every gTrackingPeriod time a goroutine transitions out of running.
|
|
|
|
|
if casgstatusAlwaysTrack || gp.trackingSeq%gTrackingPeriod == 0 {
|
2020-08-07 16:28:35 +00:00
|
|
|
gp.tracking = true
|
|
|
|
|
}
|
|
|
|
|
gp.trackingSeq++
|
|
|
|
|
}
|
2022-08-31 21:34:23 +00:00
|
|
|
if !gp.tracking {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle various kinds of tracking.
|
|
|
|
|
//
|
|
|
|
|
// Currently:
|
|
|
|
|
// - Time spent in runnable.
|
|
|
|
|
// - Time spent blocked on a sync.Mutex or sync.RWMutex.
|
|
|
|
|
switch oldval {
|
|
|
|
|
case _Grunnable:
|
|
|
|
|
// We transitioned out of runnable, so measure how much
|
|
|
|
|
// time we spent in this state and add it to
|
|
|
|
|
// runnableTime.
|
|
|
|
|
now := nanotime()
|
|
|
|
|
gp.runnableTime += now - gp.trackingStamp
|
|
|
|
|
gp.trackingStamp = 0
|
|
|
|
|
case _Gwaiting:
|
|
|
|
|
if !gp.waitreason.isMutexWait() {
|
|
|
|
|
// Not blocking on a lock.
|
|
|
|
|
break
|
2020-08-07 16:28:35 +00:00
|
|
|
}
|
2022-08-31 21:34:23 +00:00
|
|
|
// Blocking on a lock, measure it. Note that because we're
|
|
|
|
|
// sampling, we have to multiply by our sampling period to get
|
|
|
|
|
// a more representative estimate of the absolute value.
|
|
|
|
|
// gTrackingPeriod also represents an accurate sampling period
|
|
|
|
|
// because we can only enter this state from _Grunning.
|
|
|
|
|
now := nanotime()
|
|
|
|
|
sched.totalMutexWaitTime.Add((now - gp.trackingStamp) * gTrackingPeriod)
|
|
|
|
|
gp.trackingStamp = 0
|
|
|
|
|
}
|
|
|
|
|
switch newval {
|
|
|
|
|
case _Gwaiting:
|
|
|
|
|
if !gp.waitreason.isMutexWait() {
|
|
|
|
|
// Not blocking on a lock.
|
|
|
|
|
break
|
2020-08-07 16:28:35 +00:00
|
|
|
}
|
2022-08-31 21:34:23 +00:00
|
|
|
// Blocking on a lock. Write down the timestamp.
|
|
|
|
|
now := nanotime()
|
|
|
|
|
gp.trackingStamp = now
|
|
|
|
|
case _Grunnable:
|
|
|
|
|
// We just transitioned into runnable, so record what
|
|
|
|
|
// time that happened.
|
|
|
|
|
now := nanotime()
|
|
|
|
|
gp.trackingStamp = now
|
|
|
|
|
case _Grunning:
|
|
|
|
|
// We're transitioning into running, so turn off
|
|
|
|
|
// tracking and record how much time we spent in
|
|
|
|
|
// runnable.
|
|
|
|
|
gp.tracking = false
|
|
|
|
|
sched.timeToRun.record(gp.runnableTime)
|
|
|
|
|
gp.runnableTime = 0
|
2020-08-07 16:28:35 +00:00
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
runtime: set G wait reason more consistently
Currently, wait reasons are set somewhat inconsistently. In a follow-up
CL, we're going to want to rely on the wait reason being there for
casgstatus, so the status quo isn't really going to work for that. Plus
this inconsistency means there are a whole bunch of cases where we could
be more specific about the G's status but aren't.
So, this change adds a new function, casGToWaiting which is like
casgstatus but also sets the wait reason. The goal is that by using this
API it'll be harder to forget to set a wait reason (or the lack thereof
will at least be explicit). This change then updates all casgstatus(gp,
..., _Gwaiting) calls to casGToWaiting(gp, ..., waitReasonX) instead.
For a number of these cases, we're missing a wait reason, and it
wouldn't hurt to add a wait reason for them, so this change also adds
those wait reasons.
For #49881.
Change-Id: Ia95e06ecb74ed17bb7bb94f1a362ebfe6bec1518
Reviewed-on: https://go-review.googlesource.com/c/go/+/427617
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-08-31 18:21:48 +00:00
|
|
|
// casGToWaiting transitions gp from old to _Gwaiting, and sets the wait reason.
|
|
|
|
|
//
|
|
|
|
|
// Use this over casgstatus when possible to ensure that a waitreason is set.
|
|
|
|
|
func casGToWaiting(gp *g, old uint32, reason waitReason) {
|
2022-08-31 21:34:23 +00:00
|
|
|
// Set the wait reason before calling casgstatus, because casgstatus will use it.
|
runtime: set G wait reason more consistently
Currently, wait reasons are set somewhat inconsistently. In a follow-up
CL, we're going to want to rely on the wait reason being there for
casgstatus, so the status quo isn't really going to work for that. Plus
this inconsistency means there are a whole bunch of cases where we could
be more specific about the G's status but aren't.
So, this change adds a new function, casGToWaiting which is like
casgstatus but also sets the wait reason. The goal is that by using this
API it'll be harder to forget to set a wait reason (or the lack thereof
will at least be explicit). This change then updates all casgstatus(gp,
..., _Gwaiting) calls to casGToWaiting(gp, ..., waitReasonX) instead.
For a number of these cases, we're missing a wait reason, and it
wouldn't hurt to add a wait reason for them, so this change also adds
those wait reasons.
For #49881.
Change-Id: Ia95e06ecb74ed17bb7bb94f1a362ebfe6bec1518
Reviewed-on: https://go-review.googlesource.com/c/go/+/427617
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-08-31 18:21:48 +00:00
|
|
|
gp.waitreason = reason
|
|
|
|
|
casgstatus(gp, old, _Gwaiting)
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// casgstatus(gp, oldstatus, Gcopystack), assuming oldstatus is Gwaiting or Grunnable.
|
|
|
|
|
// Returns old status. Cannot call casgstatus directly, because we are racing with an
|
|
|
|
|
// async wakeup that might come in from netpoll. If we see Gwaiting from the readgstatus,
|
|
|
|
|
// it might have become Grunnable by the time we get to the cas. If we called casgstatus,
|
|
|
|
|
// it would loop waiting for the status to go back to Gwaiting, which it never will.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
|
|
|
|
func casgcopystack(gp *g) uint32 {
|
|
|
|
|
for {
|
|
|
|
|
oldstatus := readgstatus(gp) &^ _Gscan
|
|
|
|
|
if oldstatus != _Gwaiting && oldstatus != _Grunnable {
|
|
|
|
|
throw("copystack: bad status, not Gwaiting or Grunnable")
|
|
|
|
|
}
|
2022-08-25 03:27:02 +08:00
|
|
|
if gp.atomicstatus.CompareAndSwap(oldstatus, _Gcopystack) {
|
2015-10-18 17:04:05 -07:00
|
|
|
return oldstatus
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-27 12:27:51 -04:00
|
|
|
// casGToPreemptScan transitions gp from _Grunning to _Gscan|_Gpreempted.
|
|
|
|
|
//
|
|
|
|
|
// TODO(austin): This is the only status operation that both changes
|
|
|
|
|
// the status and locks the _Gscan bit. Rethink this.
|
|
|
|
|
func casGToPreemptScan(gp *g, old, new uint32) {
|
|
|
|
|
if old != _Grunning || new != _Gscan|_Gpreempted {
|
|
|
|
|
throw("bad g transition")
|
|
|
|
|
}
|
2020-04-15 12:35:24 -07:00
|
|
|
acquireLockRank(lockRankGscan)
|
2022-08-25 03:27:02 +08:00
|
|
|
for !gp.atomicstatus.CompareAndSwap(_Grunning, _Gscan|_Gpreempted) {
|
2019-09-27 12:27:51 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// casGFromPreempted attempts to transition gp from _Gpreempted to
|
|
|
|
|
// _Gwaiting. If successful, the caller is responsible for
|
|
|
|
|
// re-scheduling gp.
|
|
|
|
|
func casGFromPreempted(gp *g, old, new uint32) bool {
|
|
|
|
|
if old != _Gpreempted || new != _Gwaiting {
|
|
|
|
|
throw("bad g transition")
|
|
|
|
|
}
|
runtime: set G wait reason more consistently
Currently, wait reasons are set somewhat inconsistently. In a follow-up
CL, we're going to want to rely on the wait reason being there for
casgstatus, so the status quo isn't really going to work for that. Plus
this inconsistency means there are a whole bunch of cases where we could
be more specific about the G's status but aren't.
So, this change adds a new function, casGToWaiting which is like
casgstatus but also sets the wait reason. The goal is that by using this
API it'll be harder to forget to set a wait reason (or the lack thereof
will at least be explicit). This change then updates all casgstatus(gp,
..., _Gwaiting) calls to casGToWaiting(gp, ..., waitReasonX) instead.
For a number of these cases, we're missing a wait reason, and it
wouldn't hurt to add a wait reason for them, so this change also adds
those wait reasons.
For #49881.
Change-Id: Ia95e06ecb74ed17bb7bb94f1a362ebfe6bec1518
Reviewed-on: https://go-review.googlesource.com/c/go/+/427617
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-08-31 18:21:48 +00:00
|
|
|
gp.waitreason = waitReasonPreempted
|
2022-08-25 03:27:02 +08:00
|
|
|
return gp.atomicstatus.CompareAndSwap(_Gpreempted, _Gwaiting)
|
2019-09-27 12:27:51 -04:00
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// 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.
|
|
|
|
|
// stopTheWorld must not be called from a system stack and the caller
|
|
|
|
|
// must not hold worldsema. The caller must call startTheWorld when
|
|
|
|
|
// other P's should resume execution.
|
|
|
|
|
//
|
|
|
|
|
// stopTheWorld is safe for multiple goroutines to call at the
|
|
|
|
|
// same time. Each will execute its own stop, and the stops will
|
|
|
|
|
// be serialized.
|
|
|
|
|
//
|
|
|
|
|
// 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) {
|
2016-12-13 16:45:55 +01:00
|
|
|
semacquire(&worldsema)
|
runtime: don't hold worldsema across mark phase
This change makes it so that worldsema isn't held across the mark phase.
This means that various operations like ReadMemStats may now stop the
world during the mark phase, reducing latency on such operations.
Only three such operations are still no longer allowed to occur during
marking: GOMAXPROCS, StartTrace, and StopTrace.
For the former it's because any change to GOMAXPROCS impacts GC mark
background worker scheduling and the details there are tricky.
For the latter two it's because tracing needs to observe consistent GC
start and GC end events, and if StartTrace or StopTrace may stop the
world during marking, then it's possible for it to see a GC end event
without a start or GC start event without an end, respectively.
To ensure that GOMAXPROCS and StartTrace/StopTrace cannot proceed until
marking is complete, the runtime now holds a new semaphore, gcsema,
across the mark phase just like it used to with worldsema.
This change is being landed once more after being reverted in the Go
1.14 release cycle, since CL 215157 allows it to have a positive
effect on system performance.
For the benchmark BenchmarkReadMemStatsLatency in the runtime, which
measures ReadMemStats latencies while the GC is exercised, the tail of
these latencies reduced dramatically on an 8-core machine:
name old 50%tile-ns new 50%tile-ns delta
ReadMemStatsLatency-8 4.40M ±74% 0.12M ± 2% -97.35% (p=0.008 n=5+5)
name old 90%tile-ns new 90%tile-ns delta
ReadMemStatsLatency-8 102M ± 6% 0M ±14% -99.79% (p=0.008 n=5+5)
name old 99%tile-ns new 99%tile-ns delta
ReadMemStatsLatency-8 147M ±18% 4M ±57% -97.43% (p=0.008 n=5+5)
Fixes #19812.
Change-Id: If66c3c97d171524ae29f0e7af4bd33509d9fd0bb
Reviewed-on: https://go-review.googlesource.com/c/go/+/216557
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-06-17 19:03:09 +00:00
|
|
|
gp := getg()
|
|
|
|
|
gp.m.preemptoff = reason
|
|
|
|
|
systemstack(func() {
|
|
|
|
|
// Mark the goroutine which called stopTheWorld preemptible so its
|
|
|
|
|
// stack may be scanned.
|
|
|
|
|
// This lets a mark worker scan us while we try to stop the world
|
|
|
|
|
// since otherwise we could get in a mutual preemption deadlock.
|
|
|
|
|
// We must not modify anything on the G stack because a stack shrink
|
|
|
|
|
// may occur. A stack shrink is otherwise OK though because in order
|
|
|
|
|
// to return from this function (and to leave the system stack) we
|
|
|
|
|
// must have preempted all goroutines, including any attempting
|
|
|
|
|
// to scan our stack, in which case, any stack shrinking will
|
|
|
|
|
// have already completed by the time we exit.
|
runtime: set G wait reason more consistently
Currently, wait reasons are set somewhat inconsistently. In a follow-up
CL, we're going to want to rely on the wait reason being there for
casgstatus, so the status quo isn't really going to work for that. Plus
this inconsistency means there are a whole bunch of cases where we could
be more specific about the G's status but aren't.
So, this change adds a new function, casGToWaiting which is like
casgstatus but also sets the wait reason. The goal is that by using this
API it'll be harder to forget to set a wait reason (or the lack thereof
will at least be explicit). This change then updates all casgstatus(gp,
..., _Gwaiting) calls to casGToWaiting(gp, ..., waitReasonX) instead.
For a number of these cases, we're missing a wait reason, and it
wouldn't hurt to add a wait reason for them, so this change also adds
those wait reasons.
For #49881.
Change-Id: Ia95e06ecb74ed17bb7bb94f1a362ebfe6bec1518
Reviewed-on: https://go-review.googlesource.com/c/go/+/427617
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-08-31 18:21:48 +00:00
|
|
|
// Don't provide a wait reason because we're still executing.
|
|
|
|
|
casGToWaiting(gp, _Grunning, waitReasonStoppingTheWorld)
|
runtime: don't hold worldsema across mark phase
This change makes it so that worldsema isn't held across the mark phase.
This means that various operations like ReadMemStats may now stop the
world during the mark phase, reducing latency on such operations.
Only three such operations are still no longer allowed to occur during
marking: GOMAXPROCS, StartTrace, and StopTrace.
For the former it's because any change to GOMAXPROCS impacts GC mark
background worker scheduling and the details there are tricky.
For the latter two it's because tracing needs to observe consistent GC
start and GC end events, and if StartTrace or StopTrace may stop the
world during marking, then it's possible for it to see a GC end event
without a start or GC start event without an end, respectively.
To ensure that GOMAXPROCS and StartTrace/StopTrace cannot proceed until
marking is complete, the runtime now holds a new semaphore, gcsema,
across the mark phase just like it used to with worldsema.
This change is being landed once more after being reverted in the Go
1.14 release cycle, since CL 215157 allows it to have a positive
effect on system performance.
For the benchmark BenchmarkReadMemStatsLatency in the runtime, which
measures ReadMemStats latencies while the GC is exercised, the tail of
these latencies reduced dramatically on an 8-core machine:
name old 50%tile-ns new 50%tile-ns delta
ReadMemStatsLatency-8 4.40M ±74% 0.12M ± 2% -97.35% (p=0.008 n=5+5)
name old 90%tile-ns new 90%tile-ns delta
ReadMemStatsLatency-8 102M ± 6% 0M ±14% -99.79% (p=0.008 n=5+5)
name old 99%tile-ns new 99%tile-ns delta
ReadMemStatsLatency-8 147M ±18% 4M ±57% -97.43% (p=0.008 n=5+5)
Fixes #19812.
Change-Id: If66c3c97d171524ae29f0e7af4bd33509d9fd0bb
Reviewed-on: https://go-review.googlesource.com/c/go/+/216557
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-06-17 19:03:09 +00:00
|
|
|
stopTheWorldWithSema()
|
|
|
|
|
casgstatus(gp, _Gwaiting, _Grunning)
|
|
|
|
|
})
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// startTheWorld undoes the effects of stopTheWorld.
|
|
|
|
|
func startTheWorld() {
|
2017-07-21 14:25:28 -04:00
|
|
|
systemstack(func() { startTheWorldWithSema(false) })
|
2020-07-20 18:19:56 +00:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// worldsema must be held over startTheWorldWithSema to ensure
|
|
|
|
|
// gomaxprocs cannot change while worldsema is held.
|
2020-07-20 18:19:56 +00:00
|
|
|
//
|
|
|
|
|
// Release worldsema with direct handoff to the next waiter, but
|
|
|
|
|
// acquirem so that semrelease1 doesn't try to yield our time.
|
|
|
|
|
//
|
|
|
|
|
// Otherwise if e.g. ReadMemStats is being called in a loop,
|
|
|
|
|
// it might stomp on other attempts to stop the world, such as
|
|
|
|
|
// for starting or ending GC. The operation this blocks is
|
|
|
|
|
// so heavy-weight that we should just try to be as fair as
|
|
|
|
|
// possible here.
|
|
|
|
|
//
|
|
|
|
|
// We don't want to just allow us to get preempted between now
|
|
|
|
|
// and releasing the semaphore because then we keep everyone
|
|
|
|
|
// (including, for example, GCs) waiting longer.
|
|
|
|
|
mp := acquirem()
|
|
|
|
|
mp.preemptoff = ""
|
|
|
|
|
semrelease1(&worldsema, true, 0)
|
|
|
|
|
releasem(mp)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
runtime: don't hold worldsema across mark phase
This change makes it so that worldsema isn't held across the mark phase.
This means that various operations like ReadMemStats may now stop the
world during the mark phase, reducing latency on such operations.
Only three such operations are still no longer allowed to occur during
marking: GOMAXPROCS, StartTrace, and StopTrace.
For the former it's because any change to GOMAXPROCS impacts GC mark
background worker scheduling and the details there are tricky.
For the latter two it's because tracing needs to observe consistent GC
start and GC end events, and if StartTrace or StopTrace may stop the
world during marking, then it's possible for it to see a GC end event
without a start or GC start event without an end, respectively.
To ensure that GOMAXPROCS and StartTrace/StopTrace cannot proceed until
marking is complete, the runtime now holds a new semaphore, gcsema,
across the mark phase just like it used to with worldsema.
This change is being landed once more after being reverted in the Go
1.14 release cycle, since CL 215157 allows it to have a positive
effect on system performance.
For the benchmark BenchmarkReadMemStatsLatency in the runtime, which
measures ReadMemStats latencies while the GC is exercised, the tail of
these latencies reduced dramatically on an 8-core machine:
name old 50%tile-ns new 50%tile-ns delta
ReadMemStatsLatency-8 4.40M ±74% 0.12M ± 2% -97.35% (p=0.008 n=5+5)
name old 90%tile-ns new 90%tile-ns delta
ReadMemStatsLatency-8 102M ± 6% 0M ±14% -99.79% (p=0.008 n=5+5)
name old 99%tile-ns new 99%tile-ns delta
ReadMemStatsLatency-8 147M ±18% 4M ±57% -97.43% (p=0.008 n=5+5)
Fixes #19812.
Change-Id: If66c3c97d171524ae29f0e7af4bd33509d9fd0bb
Reviewed-on: https://go-review.googlesource.com/c/go/+/216557
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-06-17 19:03:09 +00:00
|
|
|
// 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) {
|
|
|
|
|
semacquire(&gcsema)
|
|
|
|
|
stopTheWorld(reason)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// startTheWorldGC undoes the effects of stopTheWorldGC.
|
|
|
|
|
func startTheWorldGC() {
|
|
|
|
|
startTheWorld()
|
|
|
|
|
semrelease(&gcsema)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Holding worldsema grants an M the right to try to stop the world.
|
2015-10-18 17:04:05 -07:00
|
|
|
var worldsema uint32 = 1
|
|
|
|
|
|
runtime: don't hold worldsema across mark phase
This change makes it so that worldsema isn't held across the mark phase.
This means that various operations like ReadMemStats may now stop the
world during the mark phase, reducing latency on such operations.
Only three such operations are still no longer allowed to occur during
marking: GOMAXPROCS, StartTrace, and StopTrace.
For the former it's because any change to GOMAXPROCS impacts GC mark
background worker scheduling and the details there are tricky.
For the latter two it's because tracing needs to observe consistent GC
start and GC end events, and if StartTrace or StopTrace may stop the
world during marking, then it's possible for it to see a GC end event
without a start or GC start event without an end, respectively.
To ensure that GOMAXPROCS and StartTrace/StopTrace cannot proceed until
marking is complete, the runtime now holds a new semaphore, gcsema,
across the mark phase just like it used to with worldsema.
This change is being landed once more after being reverted in the Go
1.14 release cycle, since CL 215157 allows it to have a positive
effect on system performance.
For the benchmark BenchmarkReadMemStatsLatency in the runtime, which
measures ReadMemStats latencies while the GC is exercised, the tail of
these latencies reduced dramatically on an 8-core machine:
name old 50%tile-ns new 50%tile-ns delta
ReadMemStatsLatency-8 4.40M ±74% 0.12M ± 2% -97.35% (p=0.008 n=5+5)
name old 90%tile-ns new 90%tile-ns delta
ReadMemStatsLatency-8 102M ± 6% 0M ±14% -99.79% (p=0.008 n=5+5)
name old 99%tile-ns new 99%tile-ns delta
ReadMemStatsLatency-8 147M ±18% 4M ±57% -97.43% (p=0.008 n=5+5)
Fixes #19812.
Change-Id: If66c3c97d171524ae29f0e7af4bd33509d9fd0bb
Reviewed-on: https://go-review.googlesource.com/c/go/+/216557
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2019-06-17 19:03:09 +00:00
|
|
|
// Holding gcsema grants the M the right to block a GC, and blocks
|
|
|
|
|
// until the current GC is done. In particular, it prevents gomaxprocs
|
|
|
|
|
// from changing concurrently.
|
|
|
|
|
//
|
|
|
|
|
// TODO(mknyszek): Once gomaxprocs and the execution tracer can handle
|
|
|
|
|
// being changed/enabled during a GC, remove this.
|
|
|
|
|
var gcsema uint32 = 1
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// stopTheWorldWithSema is the core implementation of stopTheWorld.
|
|
|
|
|
// The caller is responsible for acquiring worldsema and disabling
|
|
|
|
|
// preemption first and then should stopTheWorldWithSema on the system
|
|
|
|
|
// stack:
|
|
|
|
|
//
|
2016-09-22 09:48:30 -04:00
|
|
|
// semacquire(&worldsema, 0)
|
2015-10-18 17:04:05 -07:00
|
|
|
// m.preemptoff = "reason"
|
|
|
|
|
// systemstack(stopTheWorldWithSema)
|
|
|
|
|
//
|
|
|
|
|
// When finished, the caller must either call startTheWorld or undo
|
|
|
|
|
// these three operations separately:
|
|
|
|
|
//
|
|
|
|
|
// m.preemptoff = ""
|
|
|
|
|
// systemstack(startTheWorldWithSema)
|
|
|
|
|
// semrelease(&worldsema)
|
|
|
|
|
//
|
|
|
|
|
// It is allowed to acquire worldsema once and then execute multiple
|
|
|
|
|
// startTheWorldWithSema/stopTheWorldWithSema pairs.
|
|
|
|
|
// Other P's are able to execute between successive calls to
|
|
|
|
|
// startTheWorldWithSema and stopTheWorldWithSema.
|
|
|
|
|
// Holding worldsema causes any other goroutines invoking
|
|
|
|
|
// stopTheWorld to block.
|
|
|
|
|
func stopTheWorldWithSema() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// If we hold a lock, then we won't be able to stop another M
|
|
|
|
|
// that is blocked trying to acquire the lock.
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.locks > 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("stopTheWorld: holding locks")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
sched.stopwait = gomaxprocs
|
2022-07-25 15:31:03 -04:00
|
|
|
sched.gcwaiting.Store(true)
|
2015-10-18 17:04:05 -07:00
|
|
|
preemptall()
|
|
|
|
|
// stop current P
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.p.ptr().status = _Pgcstop // Pgcstop is only diagnostic.
|
2015-10-18 17:04:05 -07:00
|
|
|
sched.stopwait--
|
|
|
|
|
// try to retake all P's in Psyscall status
|
2021-02-09 15:48:41 -05:00
|
|
|
for _, pp := range allp {
|
|
|
|
|
s := pp.status
|
|
|
|
|
if s == _Psyscall && atomic.Cas(&pp.status, s, _Pgcstop) {
|
2015-10-18 17:04:05 -07:00
|
|
|
if trace.enabled {
|
2021-02-09 15:48:41 -05:00
|
|
|
traceGoSysBlock(pp)
|
|
|
|
|
traceProcStop(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.syscalltick++
|
2015-10-18 17:04:05 -07:00
|
|
|
sched.stopwait--
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// stop idle P's
|
2022-06-02 21:26:49 +00:00
|
|
|
now := nanotime()
|
2015-10-18 17:04:05 -07:00
|
|
|
for {
|
2021-02-09 15:48:41 -05:00
|
|
|
pp, _ := pidleget(now)
|
|
|
|
|
if pp == nil {
|
2015-10-18 17:04:05 -07:00
|
|
|
break
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.status = _Pgcstop
|
2015-10-18 17:04:05 -07:00
|
|
|
sched.stopwait--
|
|
|
|
|
}
|
|
|
|
|
wait := sched.stopwait > 0
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
|
|
|
|
// wait for remaining P's to stop voluntarily
|
|
|
|
|
if wait {
|
|
|
|
|
for {
|
|
|
|
|
// wait for 100us, then try to re-preempt in case of any races
|
|
|
|
|
if notetsleep(&sched.stopnote, 100*1000) {
|
|
|
|
|
noteclear(&sched.stopnote)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
preemptall()
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-19 22:43:38 -05:00
|
|
|
|
|
|
|
|
// sanity checks
|
|
|
|
|
bad := ""
|
2015-10-18 17:04:05 -07:00
|
|
|
if sched.stopwait != 0 {
|
2016-12-19 22:43:38 -05:00
|
|
|
bad = "stopTheWorld: not stopped (stopwait != 0)"
|
|
|
|
|
} else {
|
2021-02-09 15:48:41 -05:00
|
|
|
for _, pp := range allp {
|
|
|
|
|
if pp.status != _Pgcstop {
|
2016-12-19 22:43:38 -05:00
|
|
|
bad = "stopTheWorld: not stopped (status != _Pgcstop)"
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
2022-08-17 17:28:58 +07:00
|
|
|
if freezing.Load() {
|
2016-12-19 22:43:38 -05:00
|
|
|
// Some other thread is panicking. This can cause the
|
|
|
|
|
// sanity checks above to fail if the panic happens in
|
|
|
|
|
// the signal handler on a stopped thread. Either way,
|
|
|
|
|
// we should halt this thread.
|
|
|
|
|
lock(&deadlock)
|
|
|
|
|
lock(&deadlock)
|
|
|
|
|
}
|
|
|
|
|
if bad != "" {
|
|
|
|
|
throw(bad)
|
|
|
|
|
}
|
2020-10-28 18:06:05 -04:00
|
|
|
|
|
|
|
|
worldStopped()
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2017-07-21 14:25:28 -04:00
|
|
|
func startTheWorldWithSema(emitTraceEvent bool) int64 {
|
2020-10-28 18:06:05 -04:00
|
|
|
assertWorldStopped()
|
|
|
|
|
|
2019-03-25 21:16:33 -04:00
|
|
|
mp := acquirem() // disable preemption because it can be holding p in a local var
|
2017-11-06 20:51:36 -08:00
|
|
|
if netpollinited() {
|
2019-04-02 20:27:35 -07:00
|
|
|
list := netpoll(0) // non-blocking
|
2018-08-10 10:33:05 -04:00
|
|
|
injectglist(&list)
|
2017-11-06 20:51:36 -08:00
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
lock(&sched.lock)
|
|
|
|
|
|
|
|
|
|
procs := gomaxprocs
|
|
|
|
|
if newprocs != 0 {
|
|
|
|
|
procs = newprocs
|
|
|
|
|
newprocs = 0
|
|
|
|
|
}
|
|
|
|
|
p1 := procresize(procs)
|
2022-07-25 15:31:03 -04:00
|
|
|
sched.gcwaiting.Store(false)
|
2022-07-25 15:39:07 -04:00
|
|
|
if sched.sysmonwait.Load() {
|
|
|
|
|
sched.sysmonwait.Store(false)
|
2015-10-18 17:04:05 -07:00
|
|
|
notewakeup(&sched.sysmonnote)
|
|
|
|
|
}
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
2020-10-28 18:06:05 -04:00
|
|
|
worldStarted()
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
for p1 != nil {
|
|
|
|
|
p := p1
|
|
|
|
|
p1 = p1.link.ptr()
|
|
|
|
|
if p.m != 0 {
|
|
|
|
|
mp := p.m.ptr()
|
|
|
|
|
p.m = 0
|
|
|
|
|
if mp.nextp != 0 {
|
|
|
|
|
throw("startTheWorld: inconsistent mp->nextp")
|
|
|
|
|
}
|
|
|
|
|
mp.nextp.set(p)
|
|
|
|
|
notewakeup(&mp.park)
|
|
|
|
|
} else {
|
|
|
|
|
// Start M to run P. Do not start another M below.
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
newm(nil, p, -1)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-24 16:06:10 -04:00
|
|
|
// Capture start-the-world time before doing clean-up tasks.
|
|
|
|
|
startTime := nanotime()
|
2017-07-21 14:25:28 -04:00
|
|
|
if emitTraceEvent {
|
|
|
|
|
traceGCSTWDone()
|
|
|
|
|
}
|
2017-07-24 16:06:10 -04:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Wakeup an additional proc in case we have excessive runnable goroutines
|
|
|
|
|
// in local queues or in the global queue. If we don't, the proc will park itself.
|
|
|
|
|
// If we have lots of excessive work, resetspinning will unpark additional procs as necessary.
|
2020-04-28 20:54:31 -04:00
|
|
|
wakep()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2019-03-25 21:16:33 -04:00
|
|
|
releasem(mp)
|
2017-07-24 16:06:10 -04:00
|
|
|
|
|
|
|
|
return startTime
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2020-10-04 01:44:41 +10:00
|
|
|
// usesLibcall indicates whether this runtime performs system calls
|
|
|
|
|
// via libcall.
|
|
|
|
|
func usesLibcall() bool {
|
|
|
|
|
switch GOOS {
|
|
|
|
|
case "aix", "darwin", "illumos", "ios", "solaris", "windows":
|
|
|
|
|
return true
|
2020-11-15 23:28:57 +11:00
|
|
|
case "openbsd":
|
2021-02-03 18:17:35 +11:00
|
|
|
return GOARCH == "386" || GOARCH == "amd64" || GOARCH == "arm" || GOARCH == "arm64"
|
2020-10-04 01:44:41 +10:00
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-24 18:04:13 +10:00
|
|
|
// mStackIsSystemAllocated indicates whether this runtime starts on a
|
|
|
|
|
// system-allocated stack.
|
|
|
|
|
func mStackIsSystemAllocated() bool {
|
|
|
|
|
switch GOOS {
|
|
|
|
|
case "aix", "darwin", "plan9", "illumos", "ios", "solaris", "windows":
|
|
|
|
|
return true
|
2020-08-24 03:13:54 +10:00
|
|
|
case "openbsd":
|
|
|
|
|
switch GOARCH {
|
2021-01-31 04:21:47 +11:00
|
|
|
case "386", "amd64", "arm", "arm64":
|
2020-08-24 03:13:54 +10:00
|
|
|
return true
|
|
|
|
|
}
|
2020-08-24 18:04:13 +10:00
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-19 15:56:37 -04:00
|
|
|
// mstart is the entry-point for new Ms.
|
2021-03-29 17:38:20 -04:00
|
|
|
// It is written in assembly, uses ABI0, is marked TOPFRAME, and calls mstart0.
|
2021-01-28 16:22:52 -05:00
|
|
|
func mstart()
|
|
|
|
|
|
|
|
|
|
// mstart0 is the Go entry-point for new Ms.
|
2017-06-15 15:02:32 -04:00
|
|
|
// This must not split the stack because we may not even have stack
|
|
|
|
|
// bounds set up yet.
|
|
|
|
|
//
|
|
|
|
|
// May run during STW (because it doesn't have a P yet), so write
|
|
|
|
|
// barriers are not allowed.
|
|
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
2017-06-15 15:02:32 -04:00
|
|
|
//go:nowritebarrierrec
|
2021-01-28 16:22:52 -05:00
|
|
|
func mstart0() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
osStack := gp.stack.lo == 0
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
if osStack {
|
2015-10-18 17:04:05 -07:00
|
|
|
// Initialize stack bounds from system stack.
|
|
|
|
|
// Cgo may have left stack size in stack.hi.
|
runtime: query thread stack size from OS on Windows
Currently, on Windows, the thread stack size is set or assumed in many
different places. In non-cgo binaries, both the Go linker and the
runtime have a copy of the stack size, the Go linker sets the size of
the main thread stack, and the runtime sets the size of other thread
stacks. In cgo binaries, the external linker sets the main thread
stack size, the runtime assumes the size of the main thread stack will
be the same as used by the Go linker, and the cgo entry code assumes
the same.
Furthermore, users can change the main thread stack size using
editbin, so the runtime doesn't even really know what size it is, and
user C code can create threads with unknown thread stack sizes, which
we also assume have the same default stack size.
This is all a mess.
Fix the corner cases of this and the duplication of knowledge between
the linker and the runtime by querying the OS for the stack bounds
during thread setup. Furthermore, we unify all of this into just
runtime.minit for both cgo and non-cgo binaries and for the main
thread, other runtime-created threads, and C-created threads.
Updates #20975.
Change-Id: I45dbee2b5ea2ae721a85a27680737ff046f9d464
Reviewed-on: https://go-review.googlesource.com/120336
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
2018-06-21 12:08:36 -04:00
|
|
|
// minit may update the stack bounds.
|
runtime: check for g0 stack last in signal handler
In the signal handler, we adjust gsingal's stack to the stack
where the signal is delivered. TSAN may deliver signals to the
g0 stack, so we have a special case for the g0 stack. However,
we don't have very good accuracy in determining the g0 stack's
bounds, as it is system allocated and we don't know where it is
exactly. If g0.stack.lo is too low, the condition may be
triggered incorrectly, where we thought the signal is delivered to
the g0 stack but it is actually not. In this case, as the stack
bounds is actually wrong, when the stack grows, it may go below
the (inaccurate) lower bound, causing "morestack on gsignal"
crash.
Check for g0 stack last to avoid this situation. There could still
be false positives, but for those cases we'll crash either way.
(If we could in some way determine the g0 stack bounds accurately,
this would not matter (but probably doesn't hurt).)
Fixes #43853.
Change-Id: I759717c5aa2b0deb83ffb23e57b7625a6b249ee8
Reviewed-on: https://go-review.googlesource.com/c/go/+/285772
Trust: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
2021-01-22 09:47:59 -05:00
|
|
|
//
|
|
|
|
|
// Note: these bounds may not be very accurate.
|
|
|
|
|
// We set hi to &size, but there are things above
|
|
|
|
|
// it. The 1024 is supposed to compensate this,
|
|
|
|
|
// but is somewhat arbitrary.
|
2021-02-11 11:15:53 -05:00
|
|
|
size := gp.stack.hi
|
2015-10-18 17:04:05 -07:00
|
|
|
if size == 0 {
|
2015-11-11 12:39:30 -05:00
|
|
|
size = 8192 * sys.StackGuardMultiplier
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.stack.hi = uintptr(noescape(unsafe.Pointer(&size)))
|
|
|
|
|
gp.stack.lo = gp.stack.hi - size + 1024
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2019-04-19 15:56:37 -04:00
|
|
|
// Initialize stack guard so that we can start calling regular
|
|
|
|
|
// Go code.
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.stackguard0 = gp.stack.lo + _StackGuard
|
2019-04-19 15:56:37 -04:00
|
|
|
// This is the g0, so we can also call go:systemstack
|
|
|
|
|
// functions, which check stackguard1.
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.stackguard1 = gp.stackguard0
|
2018-04-26 14:06:08 -04:00
|
|
|
mstart1()
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
|
|
|
|
|
// Exit this thread.
|
2020-08-24 18:04:13 +10:00
|
|
|
if mStackIsSystemAllocated() {
|
2019-04-29 13:50:49 +00:00
|
|
|
// Windows, Solaris, illumos, Darwin, AIX and Plan 9 always system-allocate
|
2021-02-11 11:15:53 -05:00
|
|
|
// the stack, but put it in gp.stack before mstart,
|
2017-10-12 01:39:46 +02:00
|
|
|
// so the logic above hasn't set osStack yet.
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
osStack = true
|
|
|
|
|
}
|
|
|
|
|
mexit(osStack)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2021-02-15 09:25:55 -05:00
|
|
|
// The go:noinline is to guarantee the getcallerpc/getcallersp below are safe,
|
|
|
|
|
// so that we can set up g0.sched to return to the call of mstart1 above.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2021-02-15 09:25:55 -05:00
|
|
|
//go:noinline
|
2018-04-26 14:06:08 -04:00
|
|
|
func mstart1() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp != gp.m.g0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("bad runtime·mstart")
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-13 11:25:15 +00:00
|
|
|
// Set up m.g0.sched as a label returning to just
|
2021-02-15 09:25:55 -05:00
|
|
|
// after the mstart1 call in mstart0 above, for use by goexit0 and mcall.
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
// We're never coming back to mstart1 after we call schedule,
|
|
|
|
|
// so other calls can reuse the current frame.
|
2021-02-15 09:25:55 -05:00
|
|
|
// And goexit0 does a gogo that needs to return from mstart1
|
|
|
|
|
// and let mstart0 exit the thread.
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.sched.g = guintptr(unsafe.Pointer(gp))
|
|
|
|
|
gp.sched.pc = getcallerpc()
|
|
|
|
|
gp.sched.sp = getcallersp()
|
2021-02-15 09:25:55 -05:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
asminit()
|
|
|
|
|
minit()
|
|
|
|
|
|
|
|
|
|
// Install signal handlers; after minit so that minit can
|
|
|
|
|
// prepare the thread to be able to handle the signals.
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m == &m0 {
|
2017-06-15 15:02:32 -04:00
|
|
|
mstartm0()
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
if fn := gp.m.mstartfn; fn != nil {
|
2015-10-18 17:04:05 -07:00
|
|
|
fn()
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m != &m0 {
|
|
|
|
|
acquirep(gp.m.nextp.ptr())
|
|
|
|
|
gp.m.nextp = 0
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
schedule()
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-15 15:02:32 -04:00
|
|
|
// mstartm0 implements part of mstart1 that only runs on the m0.
|
|
|
|
|
//
|
|
|
|
|
// Write barriers are allowed here because we know the GC can't be
|
|
|
|
|
// running yet, so they'll be no-ops.
|
|
|
|
|
//
|
|
|
|
|
//go:yeswritebarrierrec
|
|
|
|
|
func mstartm0() {
|
|
|
|
|
// Create an extra M for callbacks on threads not created by Go.
|
2018-06-03 17:25:29 +00:00
|
|
|
// An extra M is also needed on Windows for callbacks created by
|
|
|
|
|
// syscall.NewCallback. See issue #6751 for details.
|
|
|
|
|
if (iscgo || GOOS == "windows") && !cgoHasExtraM {
|
2017-06-15 15:02:32 -04:00
|
|
|
cgoHasExtraM = true
|
|
|
|
|
newextram()
|
|
|
|
|
}
|
|
|
|
|
initsig(false)
|
|
|
|
|
}
|
|
|
|
|
|
runtime, syscall: reimplement AllThreadsSyscall using only signals.
In issue 50113, we see that a thread blocked in a system call can result
in a hang of AllThreadsSyscall. To resolve this, we must send a signal
to these threads to knock them out of the system call long enough to run
the per-thread syscall.
Stepping back, if we need to send signals anyway, it should be possible
to implement this entire mechanism on top of signals. This CL does so,
vastly simplifying the mechanism, both as a direct result of
newly-unnecessary code as well as some ancillary simplifications to make
things simpler to follow.
Major changes:
* The rest of the mechanism is moved to os_linux.go, with fields in mOS
instead of m itself.
* 'Fixup' fields and functions are renamed to 'perThreadSyscall' so they
are more precise about their purpose.
* Rather than getting passed a closure, doAllThreadsSyscall takes the
syscall number and arguments. This avoids a lot of hairy behavior:
* The closure may potentially only be live in fields in the M,
hidden from the GC. Not necessary with no closure.
* The need to loan out the race context. A direct RawSyscall6 call
does not require any race context.
* The closure previously conditionally panicked in strange
locations, like a signal handler. Now we simply throw.
* All manual fixup synchronization with mPark, sysmon, templateThread,
sigqueue, etc is gone. The core approach is much simpler:
doAllThreadsSyscall sends a signal to every thread in allm, which
executes the system call from the signal handler. We use (SIGRTMIN +
1), aka SIGSETXID, the same signal used by glibc for this purpose. As
such, we are careful to only handle this signal on non-cgo binaries.
Synchronization with thread creation is a key part of this CL. The
comment near the top of doAllThreadsSyscall describes the required
synchronization semantics and how they are achieved.
Note that current use of allocmLock protects the state mutations of allm
that are also protected by sched.lock. allocmLock is used instead of
sched.lock simply to avoid holding sched.lock for so long.
Fixes #50113
Change-Id: Ic7ea856dc66cf711731540a54996e08fc986ce84
Reviewed-on: https://go-review.googlesource.com/c/go/+/383434
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-02-04 17:15:28 -05:00
|
|
|
// mPark causes a thread to park itself, returning once woken.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
syscall: support POSIX semantics for Linux syscalls
This change adds two new methods for invoking system calls
under Linux: syscall.AllThreadsSyscall() and
syscall.AllThreadsSyscall6().
These system call wrappers ensure that all OSThreads mirror
a common system call. The wrappers serialize execution of the
runtime to ensure no race conditions where any Go code observes
a non-atomic OS state change. As such, the syscalls have
higher runtime overhead than regular system calls, and only
need to be used where such thread (or 'm' in the parlance
of the runtime sources) consistency is required.
The new support is used to enable these functions under Linux:
syscall.Setegid(), syscall.Seteuid(), syscall.Setgroups(),
syscall.Setgid(), syscall.Setregid(), syscall.Setreuid(),
syscall.Setresgid(), syscall.Setresuid() and syscall.Setuid().
They work identically to their glibc counterparts.
Extensive discussion of the background issue addressed in this
patch can be found here:
https://github.com/golang/go/issues/1435
In the case where cgo is used, the C runtime can launch pthreads that
are not managed by the Go runtime. As such, the added
syscall.AllThreadsSyscall*() return ENOTSUP when cgo is enabled.
However, for the 9 syscall.Set*() functions listed above, when cgo is
active, these functions redirect to invoke their C.set*() equivalents
in glibc, which wraps the raw system calls with a nptl:setxid fixup
mechanism. This achieves POSIX semantics for these functions in the
combined Go and C runtime.
As a side note, the glibc/nptl:setxid support (2019-11-30) does not
extend to all security related system calls under Linux so using
native Go (CGO_ENABLED=0) and these AllThreadsSyscall*()s, where
needed, will yield more well defined/consistent behavior over all
threads of a Go program. That is, using the
syscall.AllThreadsSyscall*() wrappers for things like setting state
through SYS_PRCTL and SYS_CAPSET etc.
Fixes #1435
Change-Id: Ib1a3e16b9180f64223196a32fc0f9dce14d9105c
Reviewed-on: https://go-review.googlesource.com/c/go/+/210639
Trust: Emmanuel Odeke <emm.odeke@gmail.com>
Trust: Ian Lance Taylor <iant@golang.org>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Austin Clements <austin@google.com>
2019-12-09 21:50:16 -08:00
|
|
|
//go:nosplit
|
|
|
|
|
func mPark() {
|
runtime, syscall: reimplement AllThreadsSyscall using only signals.
In issue 50113, we see that a thread blocked in a system call can result
in a hang of AllThreadsSyscall. To resolve this, we must send a signal
to these threads to knock them out of the system call long enough to run
the per-thread syscall.
Stepping back, if we need to send signals anyway, it should be possible
to implement this entire mechanism on top of signals. This CL does so,
vastly simplifying the mechanism, both as a direct result of
newly-unnecessary code as well as some ancillary simplifications to make
things simpler to follow.
Major changes:
* The rest of the mechanism is moved to os_linux.go, with fields in mOS
instead of m itself.
* 'Fixup' fields and functions are renamed to 'perThreadSyscall' so they
are more precise about their purpose.
* Rather than getting passed a closure, doAllThreadsSyscall takes the
syscall number and arguments. This avoids a lot of hairy behavior:
* The closure may potentially only be live in fields in the M,
hidden from the GC. Not necessary with no closure.
* The need to loan out the race context. A direct RawSyscall6 call
does not require any race context.
* The closure previously conditionally panicked in strange
locations, like a signal handler. Now we simply throw.
* All manual fixup synchronization with mPark, sysmon, templateThread,
sigqueue, etc is gone. The core approach is much simpler:
doAllThreadsSyscall sends a signal to every thread in allm, which
executes the system call from the signal handler. We use (SIGRTMIN +
1), aka SIGSETXID, the same signal used by glibc for this purpose. As
such, we are careful to only handle this signal on non-cgo binaries.
Synchronization with thread creation is a key part of this CL. The
comment near the top of doAllThreadsSyscall describes the required
synchronization semantics and how they are achieved.
Note that current use of allocmLock protects the state mutations of allm
that are also protected by sched.lock. allocmLock is used instead of
sched.lock simply to avoid holding sched.lock for so long.
Fixes #50113
Change-Id: Ic7ea856dc66cf711731540a54996e08fc986ce84
Reviewed-on: https://go-review.googlesource.com/c/go/+/383434
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-02-04 17:15:28 -05:00
|
|
|
gp := getg()
|
|
|
|
|
notesleep(&gp.m.park)
|
|
|
|
|
noteclear(&gp.m.park)
|
syscall: support POSIX semantics for Linux syscalls
This change adds two new methods for invoking system calls
under Linux: syscall.AllThreadsSyscall() and
syscall.AllThreadsSyscall6().
These system call wrappers ensure that all OSThreads mirror
a common system call. The wrappers serialize execution of the
runtime to ensure no race conditions where any Go code observes
a non-atomic OS state change. As such, the syscalls have
higher runtime overhead than regular system calls, and only
need to be used where such thread (or 'm' in the parlance
of the runtime sources) consistency is required.
The new support is used to enable these functions under Linux:
syscall.Setegid(), syscall.Seteuid(), syscall.Setgroups(),
syscall.Setgid(), syscall.Setregid(), syscall.Setreuid(),
syscall.Setresgid(), syscall.Setresuid() and syscall.Setuid().
They work identically to their glibc counterparts.
Extensive discussion of the background issue addressed in this
patch can be found here:
https://github.com/golang/go/issues/1435
In the case where cgo is used, the C runtime can launch pthreads that
are not managed by the Go runtime. As such, the added
syscall.AllThreadsSyscall*() return ENOTSUP when cgo is enabled.
However, for the 9 syscall.Set*() functions listed above, when cgo is
active, these functions redirect to invoke their C.set*() equivalents
in glibc, which wraps the raw system calls with a nptl:setxid fixup
mechanism. This achieves POSIX semantics for these functions in the
combined Go and C runtime.
As a side note, the glibc/nptl:setxid support (2019-11-30) does not
extend to all security related system calls under Linux so using
native Go (CGO_ENABLED=0) and these AllThreadsSyscall*()s, where
needed, will yield more well defined/consistent behavior over all
threads of a Go program. That is, using the
syscall.AllThreadsSyscall*() wrappers for things like setting state
through SYS_PRCTL and SYS_CAPSET etc.
Fixes #1435
Change-Id: Ib1a3e16b9180f64223196a32fc0f9dce14d9105c
Reviewed-on: https://go-review.googlesource.com/c/go/+/210639
Trust: Emmanuel Odeke <emm.odeke@gmail.com>
Trust: Ian Lance Taylor <iant@golang.org>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Austin Clements <austin@google.com>
2019-12-09 21:50:16 -08:00
|
|
|
}
|
|
|
|
|
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
// mexit tears down and exits the current thread.
|
|
|
|
|
//
|
|
|
|
|
// Don't call this directly to exit the thread, since it must run at
|
2021-02-11 11:15:53 -05:00
|
|
|
// the top of the thread stack. Instead, use gogo(&gp.m.g0.sched) to
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
// unwind the stack to the point that exits the thread.
|
|
|
|
|
//
|
|
|
|
|
// It is entered with m.p != nil, so write barriers are allowed. It
|
|
|
|
|
// will release the P before exiting.
|
|
|
|
|
//
|
|
|
|
|
//go:yeswritebarrierrec
|
|
|
|
|
func mexit(osStack bool) {
|
2022-07-20 13:31:10 -04:00
|
|
|
mp := getg().m
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
|
2022-07-20 13:31:10 -04:00
|
|
|
if mp == &m0 {
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
// This is the main thread. Just wedge it.
|
|
|
|
|
//
|
|
|
|
|
// On Linux, exiting the main thread puts the process
|
|
|
|
|
// into a non-waitable zombie state. On Plan 9,
|
|
|
|
|
// exiting the main thread unblocks wait even though
|
|
|
|
|
// other threads are still running. On Solaris we can
|
|
|
|
|
// neither exitThread nor return from mstart. Other
|
|
|
|
|
// bad things probably happen on other platforms.
|
|
|
|
|
//
|
|
|
|
|
// We could try to clean up this M more before wedging
|
|
|
|
|
// it, but that complicates signal handling.
|
|
|
|
|
handoffp(releasep())
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
sched.nmfreed++
|
|
|
|
|
checkdead()
|
|
|
|
|
unlock(&sched.lock)
|
syscall: support POSIX semantics for Linux syscalls
This change adds two new methods for invoking system calls
under Linux: syscall.AllThreadsSyscall() and
syscall.AllThreadsSyscall6().
These system call wrappers ensure that all OSThreads mirror
a common system call. The wrappers serialize execution of the
runtime to ensure no race conditions where any Go code observes
a non-atomic OS state change. As such, the syscalls have
higher runtime overhead than regular system calls, and only
need to be used where such thread (or 'm' in the parlance
of the runtime sources) consistency is required.
The new support is used to enable these functions under Linux:
syscall.Setegid(), syscall.Seteuid(), syscall.Setgroups(),
syscall.Setgid(), syscall.Setregid(), syscall.Setreuid(),
syscall.Setresgid(), syscall.Setresuid() and syscall.Setuid().
They work identically to their glibc counterparts.
Extensive discussion of the background issue addressed in this
patch can be found here:
https://github.com/golang/go/issues/1435
In the case where cgo is used, the C runtime can launch pthreads that
are not managed by the Go runtime. As such, the added
syscall.AllThreadsSyscall*() return ENOTSUP when cgo is enabled.
However, for the 9 syscall.Set*() functions listed above, when cgo is
active, these functions redirect to invoke their C.set*() equivalents
in glibc, which wraps the raw system calls with a nptl:setxid fixup
mechanism. This achieves POSIX semantics for these functions in the
combined Go and C runtime.
As a side note, the glibc/nptl:setxid support (2019-11-30) does not
extend to all security related system calls under Linux so using
native Go (CGO_ENABLED=0) and these AllThreadsSyscall*()s, where
needed, will yield more well defined/consistent behavior over all
threads of a Go program. That is, using the
syscall.AllThreadsSyscall*() wrappers for things like setting state
through SYS_PRCTL and SYS_CAPSET etc.
Fixes #1435
Change-Id: Ib1a3e16b9180f64223196a32fc0f9dce14d9105c
Reviewed-on: https://go-review.googlesource.com/c/go/+/210639
Trust: Emmanuel Odeke <emm.odeke@gmail.com>
Trust: Ian Lance Taylor <iant@golang.org>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Austin Clements <austin@google.com>
2019-12-09 21:50:16 -08:00
|
|
|
mPark()
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
throw("locked m0 woke up")
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-12 21:19:52 -08:00
|
|
|
sigblock(true)
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
unminit()
|
|
|
|
|
|
|
|
|
|
// Free the gsignal stack.
|
2022-07-20 13:31:10 -04:00
|
|
|
if mp.gsignal != nil {
|
|
|
|
|
stackfree(mp.gsignal.stack)
|
2019-10-29 17:07:21 -04:00
|
|
|
// On some platforms, when calling into VDSO (e.g. nanotime)
|
|
|
|
|
// we store our g on the gsignal stack, if there is one.
|
|
|
|
|
// Now the stack is freed, unlink it from the m, so we
|
|
|
|
|
// won't write to it when calling VDSO code.
|
2022-07-20 13:31:10 -04:00
|
|
|
mp.gsignal = nil
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove m from allm.
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
for pprev := &allm; *pprev != nil; pprev = &(*pprev).alllink {
|
2022-07-20 13:31:10 -04:00
|
|
|
if *pprev == mp {
|
|
|
|
|
*pprev = mp.alllink
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
goto found
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
throw("m not found in allm")
|
|
|
|
|
found:
|
2022-10-18 12:01:18 -04:00
|
|
|
// Delay reaping m until it's done with the stack.
|
|
|
|
|
//
|
|
|
|
|
// Put mp on the free list, though it will not be reaped while freeWait
|
|
|
|
|
// is freeMWait. mp is no longer reachable via allm, so even if it is
|
|
|
|
|
// on an OS stack, we must keep a reference to mp alive so that the GC
|
|
|
|
|
// doesn't free mp while we are still using it.
|
|
|
|
|
//
|
|
|
|
|
// Note that the free list must not be linked through alllink because
|
|
|
|
|
// some functions walk allm without locking, so may be using alllink.
|
|
|
|
|
mp.freeWait.Store(freeMWait)
|
|
|
|
|
mp.freelink = sched.freem
|
|
|
|
|
sched.freem = mp
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
2022-09-01 18:07:33 +00:00
|
|
|
atomic.Xadd64(&ncgocall, int64(mp.ncgocall))
|
2021-06-22 00:24:05 +00:00
|
|
|
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
// Release the P.
|
|
|
|
|
handoffp(releasep())
|
|
|
|
|
// After this point we must not have write barriers.
|
|
|
|
|
|
|
|
|
|
// Invoke the deadlock detector. This must happen after
|
|
|
|
|
// handoffp because it may have started a new M to take our
|
|
|
|
|
// P's work.
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
sched.nmfreed++
|
|
|
|
|
checkdead()
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
2020-12-03 16:53:30 -05:00
|
|
|
if GOOS == "darwin" || GOOS == "ios" {
|
2020-10-15 14:39:12 -07:00
|
|
|
// Make sure pendingPreemptSignals is correct when an M exits.
|
|
|
|
|
// For #41702.
|
2022-08-17 17:33:39 +07:00
|
|
|
if mp.signalPending.Load() != 0 {
|
2022-07-14 16:58:25 -04:00
|
|
|
pendingPreemptSignals.Add(-1)
|
2020-10-15 14:39:12 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-15 13:01:37 +01:00
|
|
|
// Destroy all allocated resources. After this is called, we may no
|
|
|
|
|
// longer take any locks.
|
2022-07-20 13:31:10 -04:00
|
|
|
mdestroy(mp)
|
2021-01-15 13:01:37 +01:00
|
|
|
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
if osStack {
|
2022-10-18 12:01:18 -04:00
|
|
|
// No more uses of mp, so it is safe to drop the reference.
|
|
|
|
|
mp.freeWait.Store(freeMRef)
|
|
|
|
|
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
// Return from mstart and let the system thread
|
|
|
|
|
// library free the g0 stack and terminate the thread.
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mstart is the thread's entry point, so there's nothing to
|
|
|
|
|
// return to. Exit the thread directly. exitThread will clear
|
|
|
|
|
// m.freeWait when it's done with the stack and the m can be
|
|
|
|
|
// reaped.
|
2022-07-20 13:31:10 -04:00
|
|
|
exitThread(&mp.freeWait)
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// forEachP calls fn(p) for every P p when p reaches a GC safe point.
|
|
|
|
|
// If a P is currently executing code, this will bring the P to a GC
|
|
|
|
|
// safe point and execute fn on that P. If the P is not executing code
|
|
|
|
|
// (it is idle or in a syscall), this will call fn(p) directly while
|
|
|
|
|
// preventing the P from exiting its state. This does not ensure that
|
|
|
|
|
// fn will run on every CPU executing Go code, but it acts as a global
|
|
|
|
|
// memory barrier. GC uses this as a "ragged barrier."
|
|
|
|
|
//
|
|
|
|
|
// The caller must hold worldsema.
|
2015-10-26 11:27:37 -04:00
|
|
|
//
|
|
|
|
|
//go:systemstack
|
2015-10-18 17:04:05 -07:00
|
|
|
func forEachP(fn func(*p)) {
|
|
|
|
|
mp := acquirem()
|
2021-02-09 15:48:41 -05:00
|
|
|
pp := getg().m.p.ptr()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
if sched.safePointWait != 0 {
|
|
|
|
|
throw("forEachP: sched.safePointWait != 0")
|
|
|
|
|
}
|
|
|
|
|
sched.safePointWait = gomaxprocs - 1
|
|
|
|
|
sched.safePointFn = fn
|
|
|
|
|
|
|
|
|
|
// Ask all Ps to run the safe point function.
|
2021-02-09 15:48:41 -05:00
|
|
|
for _, p2 := range allp {
|
|
|
|
|
if p2 != pp {
|
|
|
|
|
atomic.Store(&p2.runSafePointFn, 1)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
preemptall()
|
|
|
|
|
|
|
|
|
|
// Any P entering _Pidle or _Psyscall from now on will observe
|
|
|
|
|
// p.runSafePointFn == 1 and will call runSafePointFn when
|
|
|
|
|
// changing its status to _Pidle/_Psyscall.
|
|
|
|
|
|
|
|
|
|
// Run safe point function for all idle Ps. sched.pidle will
|
|
|
|
|
// not change because we hold sched.lock.
|
|
|
|
|
for p := sched.pidle.ptr(); p != nil; p = p.link.ptr() {
|
2015-11-02 14:09:24 -05:00
|
|
|
if atomic.Cas(&p.runSafePointFn, 1, 0) {
|
2015-10-18 17:04:05 -07:00
|
|
|
fn(p)
|
|
|
|
|
sched.safePointWait--
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wait := sched.safePointWait > 0
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
|
|
|
|
// Run fn for the current P.
|
2021-02-09 15:48:41 -05:00
|
|
|
fn(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// Force Ps currently in _Psyscall into _Pidle and hand them
|
|
|
|
|
// off to induce safe point function execution.
|
2021-02-09 15:48:41 -05:00
|
|
|
for _, p2 := range allp {
|
|
|
|
|
s := p2.status
|
|
|
|
|
if s == _Psyscall && p2.runSafePointFn == 1 && atomic.Cas(&p2.status, s, _Pidle) {
|
2015-10-18 17:04:05 -07:00
|
|
|
if trace.enabled {
|
2021-02-09 15:48:41 -05:00
|
|
|
traceGoSysBlock(p2)
|
|
|
|
|
traceProcStop(p2)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
p2.syscalltick++
|
|
|
|
|
handoffp(p2)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wait for remaining Ps to run fn.
|
|
|
|
|
if wait {
|
|
|
|
|
for {
|
|
|
|
|
// Wait for 100us, then try to re-preempt in
|
|
|
|
|
// case of any races.
|
2015-10-26 11:27:37 -04:00
|
|
|
//
|
|
|
|
|
// Requires system stack.
|
2015-10-18 17:04:05 -07:00
|
|
|
if notetsleep(&sched.safePointNote, 100*1000) {
|
|
|
|
|
noteclear(&sched.safePointNote)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
preemptall()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if sched.safePointWait != 0 {
|
|
|
|
|
throw("forEachP: not done")
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
for _, p2 := range allp {
|
|
|
|
|
if p2.runSafePointFn != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("forEachP: P did not run fn")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
sched.safePointFn = nil
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
releasem(mp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// runSafePointFn runs the safe point function, if any, for this P.
|
|
|
|
|
// This should be called like
|
|
|
|
|
//
|
2022-02-03 14:12:08 -05:00
|
|
|
// if getg().m.p.runSafePointFn != 0 {
|
|
|
|
|
// runSafePointFn()
|
|
|
|
|
// }
|
2015-10-18 17:04:05 -07:00
|
|
|
//
|
|
|
|
|
// runSafePointFn must be checked on any transition in to _Pidle or
|
|
|
|
|
// _Psyscall to avoid a race where forEachP sees that the P is running
|
|
|
|
|
// just before the P goes into _Pidle/_Psyscall and neither forEachP
|
|
|
|
|
// nor the P run the safe-point function.
|
|
|
|
|
func runSafePointFn() {
|
|
|
|
|
p := getg().m.p.ptr()
|
|
|
|
|
// Resolve the race between forEachP running the safe-point
|
|
|
|
|
// function on this P's behalf and this P running the
|
|
|
|
|
// safe-point function directly.
|
2015-11-02 14:09:24 -05:00
|
|
|
if !atomic.Cas(&p.runSafePointFn, 1, 0) {
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
sched.safePointFn(p)
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
sched.safePointWait--
|
|
|
|
|
if sched.safePointWait == 0 {
|
|
|
|
|
notewakeup(&sched.safePointNote)
|
|
|
|
|
}
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// When running with cgo, we call _cgo_thread_start
|
|
|
|
|
// to start threads for us so that we can play nicely with
|
|
|
|
|
// foreign code.
|
|
|
|
|
var cgoThreadStart unsafe.Pointer
|
|
|
|
|
|
|
|
|
|
type cgothreadstart struct {
|
|
|
|
|
g guintptr
|
|
|
|
|
tls *uint64
|
|
|
|
|
fn unsafe.Pointer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Allocate a new m unassociated with any thread.
|
|
|
|
|
// Can use p for allocation context if needed.
|
|
|
|
|
// fn is recorded as the new m's m.mstartfn.
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
// id is optional pre-allocated m ID. Omit by passing -1.
|
2015-11-17 17:28:35 -05:00
|
|
|
//
|
2016-10-10 16:46:28 -04:00
|
|
|
// This function is allowed to have write barriers even if the caller
|
2021-02-09 15:48:41 -05:00
|
|
|
// isn't because it borrows pp.
|
2016-10-10 16:46:28 -04:00
|
|
|
//
|
|
|
|
|
//go:yeswritebarrierrec
|
2021-02-09 15:48:41 -05:00
|
|
|
func allocm(pp *p, fn func(), id int64) *m {
|
runtime, syscall: reimplement AllThreadsSyscall using only signals.
In issue 50113, we see that a thread blocked in a system call can result
in a hang of AllThreadsSyscall. To resolve this, we must send a signal
to these threads to knock them out of the system call long enough to run
the per-thread syscall.
Stepping back, if we need to send signals anyway, it should be possible
to implement this entire mechanism on top of signals. This CL does so,
vastly simplifying the mechanism, both as a direct result of
newly-unnecessary code as well as some ancillary simplifications to make
things simpler to follow.
Major changes:
* The rest of the mechanism is moved to os_linux.go, with fields in mOS
instead of m itself.
* 'Fixup' fields and functions are renamed to 'perThreadSyscall' so they
are more precise about their purpose.
* Rather than getting passed a closure, doAllThreadsSyscall takes the
syscall number and arguments. This avoids a lot of hairy behavior:
* The closure may potentially only be live in fields in the M,
hidden from the GC. Not necessary with no closure.
* The need to loan out the race context. A direct RawSyscall6 call
does not require any race context.
* The closure previously conditionally panicked in strange
locations, like a signal handler. Now we simply throw.
* All manual fixup synchronization with mPark, sysmon, templateThread,
sigqueue, etc is gone. The core approach is much simpler:
doAllThreadsSyscall sends a signal to every thread in allm, which
executes the system call from the signal handler. We use (SIGRTMIN +
1), aka SIGSETXID, the same signal used by glibc for this purpose. As
such, we are careful to only handle this signal on non-cgo binaries.
Synchronization with thread creation is a key part of this CL. The
comment near the top of doAllThreadsSyscall describes the required
synchronization semantics and how they are achieved.
Note that current use of allocmLock protects the state mutations of allm
that are also protected by sched.lock. allocmLock is used instead of
sched.lock simply to avoid holding sched.lock for so long.
Fixes #50113
Change-Id: Ic7ea856dc66cf711731540a54996e08fc986ce84
Reviewed-on: https://go-review.googlesource.com/c/go/+/383434
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-02-04 17:15:28 -05:00
|
|
|
allocmLock.rlock()
|
|
|
|
|
|
2021-02-09 15:48:41 -05:00
|
|
|
// The caller owns pp, but we may borrow (i.e., acquirep) it. We must
|
runtime, syscall: reimplement AllThreadsSyscall using only signals.
In issue 50113, we see that a thread blocked in a system call can result
in a hang of AllThreadsSyscall. To resolve this, we must send a signal
to these threads to knock them out of the system call long enough to run
the per-thread syscall.
Stepping back, if we need to send signals anyway, it should be possible
to implement this entire mechanism on top of signals. This CL does so,
vastly simplifying the mechanism, both as a direct result of
newly-unnecessary code as well as some ancillary simplifications to make
things simpler to follow.
Major changes:
* The rest of the mechanism is moved to os_linux.go, with fields in mOS
instead of m itself.
* 'Fixup' fields and functions are renamed to 'perThreadSyscall' so they
are more precise about their purpose.
* Rather than getting passed a closure, doAllThreadsSyscall takes the
syscall number and arguments. This avoids a lot of hairy behavior:
* The closure may potentially only be live in fields in the M,
hidden from the GC. Not necessary with no closure.
* The need to loan out the race context. A direct RawSyscall6 call
does not require any race context.
* The closure previously conditionally panicked in strange
locations, like a signal handler. Now we simply throw.
* All manual fixup synchronization with mPark, sysmon, templateThread,
sigqueue, etc is gone. The core approach is much simpler:
doAllThreadsSyscall sends a signal to every thread in allm, which
executes the system call from the signal handler. We use (SIGRTMIN +
1), aka SIGSETXID, the same signal used by glibc for this purpose. As
such, we are careful to only handle this signal on non-cgo binaries.
Synchronization with thread creation is a key part of this CL. The
comment near the top of doAllThreadsSyscall describes the required
synchronization semantics and how they are achieved.
Note that current use of allocmLock protects the state mutations of allm
that are also protected by sched.lock. allocmLock is used instead of
sched.lock simply to avoid holding sched.lock for so long.
Fixes #50113
Change-Id: Ic7ea856dc66cf711731540a54996e08fc986ce84
Reviewed-on: https://go-review.googlesource.com/c/go/+/383434
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-02-04 17:15:28 -05:00
|
|
|
// disable preemption to ensure it is not stolen, which would make the
|
|
|
|
|
// caller lose ownership.
|
|
|
|
|
acquirem()
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
if gp.m.p == 0 {
|
2021-02-09 15:48:41 -05:00
|
|
|
acquirep(pp) // temporarily borrow p for mallocs in this function
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
|
|
|
|
|
// Release the free M list. We need to do this somewhere and
|
|
|
|
|
// this may free up a stack we can use.
|
|
|
|
|
if sched.freem != nil {
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
var newList *m
|
|
|
|
|
for freem := sched.freem; freem != nil; {
|
2022-10-18 12:01:18 -04:00
|
|
|
wait := freem.freeWait.Load()
|
|
|
|
|
if wait == freeMWait {
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
next := freem.freelink
|
|
|
|
|
freem.freelink = newList
|
|
|
|
|
newList = freem
|
|
|
|
|
freem = next
|
|
|
|
|
continue
|
|
|
|
|
}
|
2022-10-18 12:01:18 -04:00
|
|
|
// Free the stack if needed. For freeMRef, there is
|
|
|
|
|
// nothing to do except drop freem from the sched.freem
|
|
|
|
|
// list.
|
|
|
|
|
if wait == freeMStack {
|
|
|
|
|
// stackfree must be on the system stack, but allocm is
|
|
|
|
|
// reachable off the system stack transitively from
|
|
|
|
|
// startm.
|
|
|
|
|
systemstack(func() {
|
|
|
|
|
stackfree(freem.g0.stack)
|
|
|
|
|
})
|
|
|
|
|
}
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
freem = freem.freelink
|
|
|
|
|
}
|
|
|
|
|
sched.freem = newList
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
mp := new(m)
|
|
|
|
|
mp.mstartfn = fn
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
mcommoninit(mp, id)
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2019-04-29 13:50:49 +00:00
|
|
|
// In case of cgo or Solaris or illumos or Darwin, pthread_create will make us a stack.
|
2015-10-18 17:04:05 -07:00
|
|
|
// Windows and Plan 9 will layout sched stack on OS stack.
|
2020-08-24 18:04:13 +10:00
|
|
|
if iscgo || mStackIsSystemAllocated() {
|
2015-10-18 17:04:05 -07:00
|
|
|
mp.g0 = malg(-1)
|
|
|
|
|
} else {
|
2015-11-11 12:39:30 -05:00
|
|
|
mp.g0 = malg(8192 * sys.StackGuardMultiplier)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
mp.g0.m = mp
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
if pp == gp.m.p.ptr() {
|
2015-10-18 17:04:05 -07:00
|
|
|
releasep()
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
releasem(gp.m)
|
runtime, syscall: reimplement AllThreadsSyscall using only signals.
In issue 50113, we see that a thread blocked in a system call can result
in a hang of AllThreadsSyscall. To resolve this, we must send a signal
to these threads to knock them out of the system call long enough to run
the per-thread syscall.
Stepping back, if we need to send signals anyway, it should be possible
to implement this entire mechanism on top of signals. This CL does so,
vastly simplifying the mechanism, both as a direct result of
newly-unnecessary code as well as some ancillary simplifications to make
things simpler to follow.
Major changes:
* The rest of the mechanism is moved to os_linux.go, with fields in mOS
instead of m itself.
* 'Fixup' fields and functions are renamed to 'perThreadSyscall' so they
are more precise about their purpose.
* Rather than getting passed a closure, doAllThreadsSyscall takes the
syscall number and arguments. This avoids a lot of hairy behavior:
* The closure may potentially only be live in fields in the M,
hidden from the GC. Not necessary with no closure.
* The need to loan out the race context. A direct RawSyscall6 call
does not require any race context.
* The closure previously conditionally panicked in strange
locations, like a signal handler. Now we simply throw.
* All manual fixup synchronization with mPark, sysmon, templateThread,
sigqueue, etc is gone. The core approach is much simpler:
doAllThreadsSyscall sends a signal to every thread in allm, which
executes the system call from the signal handler. We use (SIGRTMIN +
1), aka SIGSETXID, the same signal used by glibc for this purpose. As
such, we are careful to only handle this signal on non-cgo binaries.
Synchronization with thread creation is a key part of this CL. The
comment near the top of doAllThreadsSyscall describes the required
synchronization semantics and how they are achieved.
Note that current use of allocmLock protects the state mutations of allm
that are also protected by sched.lock. allocmLock is used instead of
sched.lock simply to avoid holding sched.lock for so long.
Fixes #50113
Change-Id: Ic7ea856dc66cf711731540a54996e08fc986ce84
Reviewed-on: https://go-review.googlesource.com/c/go/+/383434
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-02-04 17:15:28 -05:00
|
|
|
allocmLock.runlock()
|
2015-10-18 17:04:05 -07:00
|
|
|
return mp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// needm is called when a cgo callback happens on a
|
|
|
|
|
// thread without an m (a thread not created by Go).
|
|
|
|
|
// In this case, needm is expected to find an m to use
|
|
|
|
|
// and return with m, g initialized correctly.
|
|
|
|
|
// Since m and g are not set now (likely nil, but see below)
|
|
|
|
|
// needm is limited in what routines it can call. In particular
|
|
|
|
|
// it can only call nosplit functions (textflag 7) and cannot
|
|
|
|
|
// do any scheduling that requires an m.
|
|
|
|
|
//
|
|
|
|
|
// In order to avoid needing heavy lifting here, we adopt
|
|
|
|
|
// the following strategy: there is a stack of available m's
|
|
|
|
|
// that can be stolen. Using compare-and-swap
|
|
|
|
|
// to pop from the stack has ABA races, so we simulate
|
2018-02-12 11:22:00 -08:00
|
|
|
// a lock by doing an exchange (via Casuintptr) to steal the stack
|
2015-10-18 17:04:05 -07:00
|
|
|
// head and replace the top pointer with MLOCKED (1).
|
|
|
|
|
// This serves as a simple spin lock that we can use even
|
|
|
|
|
// without an m. The thread that locks the stack in this way
|
|
|
|
|
// unlocks the stack by storing a valid stack head pointer.
|
|
|
|
|
//
|
|
|
|
|
// In order to make sure that there is always an m structure
|
|
|
|
|
// available to be stolen, we maintain the invariant that there
|
|
|
|
|
// is always one more than needed. At the beginning of the
|
|
|
|
|
// program (if cgo is in use) the list is seeded with a single m.
|
|
|
|
|
// If needm finds that it has taken the last m off the list, its job
|
|
|
|
|
// is - once it has installed its own m so that it can do things like
|
|
|
|
|
// allocate memory - to create a spare m and put it on the list.
|
|
|
|
|
//
|
|
|
|
|
// Each of these extra m's also has a g0 and a curg that are
|
|
|
|
|
// pressed into service as the scheduling stack and current
|
|
|
|
|
// goroutine for the duration of the cgo callback.
|
|
|
|
|
//
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
// 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.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
func needm(signal bool) {
|
2018-06-03 17:25:29 +00:00
|
|
|
if (iscgo || GOOS == "windows") && !cgoHasExtraM {
|
2015-10-18 17:04:05 -07:00
|
|
|
// Can happen if C/C++ code calls Go from a global ctor.
|
2018-06-03 17:25:29 +00:00
|
|
|
// Can also happen on Windows if a global ctor uses a
|
|
|
|
|
// callback created by syscall.NewCallback. See issue #6751
|
|
|
|
|
// for details.
|
|
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
// Can not throw, because scheduler is not initialized yet.
|
2022-11-01 12:33:59 -07:00
|
|
|
writeErrStr("fatal error: cgo callback before cgo call\n")
|
2015-10-18 17:04:05 -07:00
|
|
|
exit(1)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-27 16:09:40 -07:00
|
|
|
// Save and block signals before getting an M.
|
|
|
|
|
// The signal handler may call needm itself,
|
|
|
|
|
// and we must avoid a deadlock. Also, once g is installed,
|
|
|
|
|
// any incoming signals will try to execute,
|
|
|
|
|
// but we won't have the sigaltstack settings and other data
|
|
|
|
|
// set up appropriately until the end of minit, which will
|
|
|
|
|
// unblock the signals. This is the same dance as when
|
|
|
|
|
// starting a new m to run Go code via newosproc.
|
|
|
|
|
var sigmask sigset
|
|
|
|
|
sigsave(&sigmask)
|
2020-11-12 21:19:52 -08:00
|
|
|
sigblock(false)
|
2020-10-27 16:09:40 -07:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Lock extra list, take head, unlock popped list.
|
|
|
|
|
// nilokay=false is safe here because of the invariant above,
|
|
|
|
|
// that the extra list always contains or will soon contain
|
|
|
|
|
// at least one m.
|
|
|
|
|
mp := lockextra(false)
|
|
|
|
|
|
|
|
|
|
// Set needextram when we've just emptied the list,
|
|
|
|
|
// so that the eventual call into cgocallbackg will
|
|
|
|
|
// allocate a new m for the extra list. We delay the
|
|
|
|
|
// allocation until then so that it can be done
|
|
|
|
|
// after exitsyscall makes sure it is okay to be
|
|
|
|
|
// running at all (that is, there's no garbage collection
|
|
|
|
|
// running right now).
|
|
|
|
|
mp.needextram = mp.schedlink == 0
|
2017-03-15 14:48:23 -04:00
|
|
|
extraMCount--
|
2015-10-18 17:04:05 -07:00
|
|
|
unlockextra(mp.schedlink.ptr())
|
|
|
|
|
|
2020-10-27 16:09:40 -07:00
|
|
|
// Store the original signal mask for use by minit.
|
|
|
|
|
mp.sigmask = sigmask
|
2015-11-13 16:21:01 -05:00
|
|
|
|
runtime: bypass ABI wrapper when calling needm on Windows
On Windows, when calling into needm in cgocallback on a new thread that
is unknown to the Go runtime, we currently call through an ABI wrapper.
The ABI wrapper tries to restore the G register from TLS.
On other platforms, TLS is set up just enough that the wrapper will
simply load a nil g from TLS, but on Windows TLS isn't set up at all, so
there's nowhere for the wrapper to load from.
So, bypass the wrapper in the call to needm. needm takes no arguments
and returns no results so there are no special ABI considerations,
except that we must clear X15 which is used as a zero register in Go
code (a function normally performed by the ABI wrapper). needm is also
otherwise already special and carefully crafted to avoid doing anything
that would require a valid G or M, at least until it is able to create
one.
While we're here, this change simplifies setg so that it doesn't set up
TLS on Windows and instead provides an OS-specific osSetupTLS to do
that.
The result of this is that setg(nil) no longer clears the TLS space
pointer on Windows. There's exactly one place this is used (dropm) where
it doesn't matter anymore, and an empty TLS means that setg's wrapper
will crash on the return path. Another result is that the G slot in the
TLS will be properly cleared, however, which isn't true today.
For #40724.
Change-Id: I65c3d924a3b16abe667b06fd91d467d6d5da31d7
Reviewed-on: https://go-review.googlesource.com/c/go/+/303070
Trust: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
2021-03-18 17:03:50 +00:00
|
|
|
// Install TLS on some platforms (previously setg
|
|
|
|
|
// would do this if necessary).
|
|
|
|
|
osSetupTLS(mp)
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Install g (= m->g0) and set the stack bounds
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
// to match the current stack. If we don't actually know
|
2015-10-18 17:04:05 -07:00
|
|
|
// how big the stack is, like we don't know how big any
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
// scheduling stack is, but we assume there's at least 32 kB.
|
|
|
|
|
// If we can get a more accurate stack bound from pthread,
|
|
|
|
|
// use that.
|
2015-10-18 17:04:05 -07:00
|
|
|
setg(mp.g0)
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
gp.stack.hi = getcallersp() + 1024
|
|
|
|
|
gp.stack.lo = getcallersp() - 32*1024
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
if !signal && _cgo_getstackbound != nil {
|
|
|
|
|
// Don't adjust if called from the signal handler.
|
|
|
|
|
// We are on the signal stack, not the pthread stack.
|
|
|
|
|
// (We could get the stack bounds from sigaltstack, but
|
|
|
|
|
// we're getting out of the signal handler very soon
|
|
|
|
|
// anyway. Not worth it.)
|
|
|
|
|
asmcgocall(_cgo_getstackbound, unsafe.Pointer(gp))
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.stackguard0 = gp.stack.lo + _StackGuard
|
2015-10-18 17:04:05 -07:00
|
|
|
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
// 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
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Initialize this thread to use the m.
|
|
|
|
|
asminit()
|
|
|
|
|
minit()
|
2017-06-06 18:37:59 -04:00
|
|
|
|
|
|
|
|
// mp.curg is now a real goroutine.
|
|
|
|
|
casgstatus(mp.curg, _Gdead, _Gsyscall)
|
2022-07-20 17:48:19 -04:00
|
|
|
sched.ngsys.Add(-1)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
// 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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-18 23:00:43 -07:00
|
|
|
// newextram allocates m's and puts them on the extra list.
|
2015-10-18 17:04:05 -07:00
|
|
|
// It is called with a working local m, so that it can do things
|
|
|
|
|
// like call schedlock and allocate.
|
|
|
|
|
func newextram() {
|
2022-08-25 21:08:02 +08:00
|
|
|
c := extraMWaiters.Swap(0)
|
2016-07-18 23:00:43 -07:00
|
|
|
if c > 0 {
|
|
|
|
|
for i := uint32(0); i < c; i++ {
|
|
|
|
|
oneNewExtraM()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Make sure there is at least one extra M.
|
|
|
|
|
mp := lockextra(true)
|
|
|
|
|
unlockextra(mp)
|
|
|
|
|
if mp == nil {
|
|
|
|
|
oneNewExtraM()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// oneNewExtraM allocates an m and puts it on the extra list.
|
|
|
|
|
func oneNewExtraM() {
|
2015-10-18 17:04:05 -07:00
|
|
|
// Create extra goroutine locked to extra m.
|
|
|
|
|
// The goroutine is the context in which the cgo callback will run.
|
|
|
|
|
// The sched.pc will never be returned to, but setting it to
|
|
|
|
|
// goexit makes clear to the traceback routines where
|
|
|
|
|
// the goroutine stack ends.
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
mp := allocm(nil, nil, -1)
|
2015-10-18 17:04:05 -07:00
|
|
|
gp := malg(4096)
|
2021-04-02 13:24:35 -04:00
|
|
|
gp.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum
|
2015-10-18 17:04:05 -07:00
|
|
|
gp.sched.sp = gp.stack.hi
|
2021-06-16 23:05:44 +00:00
|
|
|
gp.sched.sp -= 4 * goarch.PtrSize // extra space in case of reads slightly beyond frame
|
2015-10-18 17:04:05 -07:00
|
|
|
gp.sched.lr = 0
|
|
|
|
|
gp.sched.g = guintptr(unsafe.Pointer(gp))
|
|
|
|
|
gp.syscallpc = gp.sched.pc
|
|
|
|
|
gp.syscallsp = gp.sched.sp
|
|
|
|
|
gp.stktopsp = gp.sched.sp
|
2017-06-06 18:37:59 -04:00
|
|
|
// 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)
|
2015-10-18 17:04:05 -07:00
|
|
|
gp.m = mp
|
|
|
|
|
mp.curg = gp
|
2022-09-24 11:15:43 +00:00
|
|
|
mp.isextra = true
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
// mark we are in C by default.
|
|
|
|
|
mp.isExtraInC = true
|
2017-06-14 11:46:35 -04:00
|
|
|
mp.lockedInt++
|
2017-09-13 10:14:02 -07:00
|
|
|
mp.lockedg.set(gp)
|
|
|
|
|
gp.lockedm.set(mp)
|
2022-07-19 13:49:33 -04:00
|
|
|
gp.goid = sched.goidgen.Add(1)
|
2022-09-24 11:15:43 +00:00
|
|
|
gp.sysblocktraced = true
|
2015-10-18 17:04:05 -07:00
|
|
|
if raceenabled {
|
2021-05-21 13:37:19 -04:00
|
|
|
gp.racectx = racegostart(abi.FuncPCABIInternal(newextram) + sys.PCQuantum)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2022-09-24 11:15:43 +00:00
|
|
|
if trace.enabled {
|
|
|
|
|
// Trigger two trace events for the locked g in the extra m,
|
|
|
|
|
// since the next event of the g will be traceEvGoSysExit in exitsyscall,
|
|
|
|
|
// while calling from C thread to Go.
|
|
|
|
|
traceGoCreate(gp, 0) // no start pc
|
|
|
|
|
gp.traceseq++
|
|
|
|
|
traceEvent(traceEvGoInSyscall, -1, gp.goid)
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
// put on allg for garbage collector
|
|
|
|
|
allgadd(gp)
|
|
|
|
|
|
2017-06-06 18:37:59 -04:00
|
|
|
// 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.
|
2022-07-20 17:48:19 -04:00
|
|
|
sched.ngsys.Add(1)
|
2017-06-06 18:37:59 -04:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Add m to the extra list.
|
|
|
|
|
mnext := lockextra(true)
|
|
|
|
|
mp.schedlink.set(mnext)
|
2017-03-15 14:48:23 -04:00
|
|
|
extraMCount++
|
2015-10-18 17:04:05 -07:00
|
|
|
unlockextra(mp)
|
|
|
|
|
}
|
|
|
|
|
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
// dropm puts the current m back onto the extra list.
|
|
|
|
|
//
|
|
|
|
|
// 1. On systems without pthreads, like Windows
|
2015-10-18 17:04:05 -07:00
|
|
|
// 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.
|
|
|
|
|
//
|
|
|
|
|
// 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
|
|
|
|
|
// from this thread. It is tempting to try to save the m for next time,
|
|
|
|
|
// which would eliminate both these costs, but there might not be
|
|
|
|
|
// a next time: the current thread (which Go does not control) might exit.
|
|
|
|
|
// If we saved the m for that thread, there would be an m leak each time
|
|
|
|
|
// such a thread exited. Instead, we acquire and release an m on each
|
|
|
|
|
// call. These should typically not be scheduling operations, just a few
|
|
|
|
|
// atomics, so the cost should be small.
|
|
|
|
|
//
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
// 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.
|
|
|
|
|
//
|
|
|
|
|
// NOTE: this always runs without a P, so, nowritebarrierrec required.
|
|
|
|
|
//
|
|
|
|
|
//go:nowritebarrierrec
|
2015-10-18 17:04:05 -07:00
|
|
|
func dropm() {
|
|
|
|
|
// Clear m and g, and return m to the extra list.
|
|
|
|
|
// After the call to setg we can only call nosplit functions
|
|
|
|
|
// with no pointer manipulation.
|
|
|
|
|
mp := getg().m
|
|
|
|
|
|
2017-06-06 18:37:59 -04:00
|
|
|
// Return mp.curg to dead state.
|
|
|
|
|
casgstatus(mp.curg, _Gsyscall, _Gdead)
|
2019-11-01 17:53:53 -07:00
|
|
|
mp.curg.preemptStop = false
|
2022-07-20 17:48:19 -04:00
|
|
|
sched.ngsys.Add(1)
|
2017-06-06 18:37:59 -04:00
|
|
|
|
2015-11-13 16:21:01 -05:00
|
|
|
// 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.
|
|
|
|
|
// It's important not to try to handle a signal between those two steps.
|
2016-01-12 15:34:03 -08:00
|
|
|
sigmask := mp.sigmask
|
2020-11-12 21:19:52 -08:00
|
|
|
sigblock(false)
|
2015-11-13 16:21:01 -05:00
|
|
|
unminit()
|
2016-01-12 15:34:03 -08:00
|
|
|
|
|
|
|
|
mnext := lockextra(true)
|
2017-03-15 14:48:23 -04:00
|
|
|
extraMCount++
|
2016-01-12 15:34:03 -08:00
|
|
|
mp.schedlink.set(mnext)
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
setg(nil)
|
2015-11-13 16:21:01 -05:00
|
|
|
|
|
|
|
|
// Commit the release of mp.
|
2015-10-18 17:04:05 -07:00
|
|
|
unlockextra(mp)
|
2016-01-12 15:34:03 -08:00
|
|
|
|
|
|
|
|
msigrestore(sigmask)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
runtime/cgo: store M for C-created thread in pthread key
This reapplies CL 392854, with the followup fixes in CL 479255,
CL 479915, and CL 481057 incorporated.
CL 392854, by doujiang24 <doujiang24@gmail.com>, speed up C to Go
calls by binding the M to the C thread. See below for its
description.
CL 479255 is a followup fix for a small bug in ARM assembly code.
CL 479915 is another followup fix to address C to Go calls after
the C code uses some stack, but that CL is also buggy.
CL 481057, by Michael Knyszek, is a followup fix for a memory leak
bug of CL 479915.
[Original CL 392854 description]
In a C thread, it's necessary to acquire an extra M by using needm while invoking a Go function from C. But, needm and dropm are heavy costs due to the signal-related syscalls.
So, we change to not dropm while returning back to C, which means binding the extra M to the C thread until it exits, to avoid needm and dropm on each C to Go call.
Instead, we only dropm while the C thread exits, so the extra M won't leak.
When invoking a Go function from C:
Allocate a pthread variable using pthread_key_create, only once per shared object, and register a thread-exit-time destructor.
And store the g0 of the current m into the thread-specified value of the pthread key, only once per C thread, so that the destructor will put the extra M back onto the extra M list while the C thread exits.
When returning back to C:
Skip dropm in cgocallback, when the pthread variable has been created, so that the extra M will be reused the next time invoke a Go function from C.
This is purely a performance optimization. The old version, in which needm & dropm happen on each cgo call, is still correct too, and we have to keep the old version on systems with cgo but without pthreads, like Windows.
This optimization is significant, and the specific value depends on the OS system and CPU, but in general, it can be considered as 10x faster, for a simple Go function call from a C thread.
For the newly added BenchmarkCGoInCThread, some benchmark results:
1. it's 28x faster, from 3395 ns/op to 121 ns/op, in darwin OS & Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
2. it's 6.5x faster, from 1495 ns/op to 230 ns/op, in Linux OS & Intel(R) Xeon(R) CPU E5-2630 0 @ 2.30GHz
[CL 479915 description]
Currently, when C calls into Go the first time, we grab an M
using needm, which sets m.g0's stack bounds using the SP. We don't
know how big the stack is, so we simply assume 32K. Previously,
when the Go function returns to C, we drop the M, and the next
time C calls into Go, we put a new stack bound on the g0 based on
the current SP. After CL 392854, we don't drop the M, and the next
time C calls into Go, we reuse the same g0, without recomputing
the stack bounds. If the C code uses quite a bit of stack space
before calling into Go, the SP may be well below the 32K stack
bound we assumed, so the runtime thinks the g0 stack overflows.
This CL makes needm get a more accurate stack bound from
pthread. (In some platforms this may still be a guess as we don't
know exactly where we are in the C stack), but it is probably
better than simply assuming 32K.
Fixes #51676.
Fixes #59294.
Change-Id: I9bf1400106d5c08ce621d2ed1df3a2d9e3f55494
Reviewed-on: https://go-review.googlesource.com/c/go/+/481061
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: DeJiang Zhu (doujiang) <doujiang24@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-03-31 15:45:39 -04:00
|
|
|
// 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))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-08 16:56:02 -08:00
|
|
|
// A helper function for EnsureDropM.
|
|
|
|
|
func getm() uintptr {
|
|
|
|
|
return uintptr(unsafe.Pointer(getg().m))
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-25 21:08:02 +08:00
|
|
|
var extram atomic.Uintptr
|
2017-03-15 14:48:23 -04:00
|
|
|
var extraMCount uint32 // Protected by lockextra
|
2022-08-25 21:08:02 +08:00
|
|
|
var extraMWaiters atomic.Uint32
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// lockextra locks the extra list and returns the list head.
|
|
|
|
|
// The caller must unlock the list by storing a new list head
|
|
|
|
|
// to extram. If nilokay is true, then lockextra will
|
|
|
|
|
// return a nil list head if that's what it finds. If nilokay is false,
|
|
|
|
|
// lockextra will keep waiting until the list head is no longer nil.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
|
|
|
|
func lockextra(nilokay bool) *m {
|
|
|
|
|
const locked = 1
|
|
|
|
|
|
2016-07-18 23:00:43 -07:00
|
|
|
incr := false
|
2015-10-18 17:04:05 -07:00
|
|
|
for {
|
2022-08-25 21:08:02 +08:00
|
|
|
old := extram.Load()
|
2015-10-18 17:04:05 -07:00
|
|
|
if old == locked {
|
runtime: clean up system calls during cgo callback init
During a cgocallback, the runtime calls needm to get an m.
The calls made during needm cannot themselves assume that
there is an m or a g (which is attached to the m).
In the old days of making direct system calls, the only thing
you had to do for such functions was mark them //go:nosplit,
to avoid the use of g in the stack split prologue.
But now, on operating systems that make system calls through
shared libraries and use code that saves state in the g or m
before doing so, it's not safe to assume g exists. In fact, it is
not even safe to call getg(), because it might fault deferencing
the TLS storage to find the g pointer (that storage may not be
initialized yet, at least on Windows, and perhaps on other systems
in the future).
The specific routines that are problematic are usleep and osyield,
which are called during lock contention in lockextra, called
from needm.
All this is rather subtle and hidden, so in addition to fixing the
problem on Windows, this CL makes the fact of not running on
a g much clearer by introducing variants usleep_no_g and
osyield_no_g whose names should make clear that there is no g.
And then we can remove the various sketchy getg() == nil checks
in the existing routines.
As part of this cleanup, this CL also deletes onosstack on Windows.
onosstack is from back when the runtime was implemented in C.
It predates systemstack but does essentially the same thing.
Instead of having two different copies of this code, we can use
systemstack consistently. This way we need not port onosstack
to each architecture.
This CL is part of a stack adding windows/arm64
support (#36439), intended to land in the Go 1.17 cycle.
This CL is, however, not windows/arm64-specific.
It is cleanup meant to make the port (and future ports) easier.
Change-Id: I3352de1fd0a3c26267c6e209063e6e86abd26187
Reviewed-on: https://go-review.googlesource.com/c/go/+/288793
Trust: Russ Cox <rsc@golang.org>
Trust: Jason A. Donenfeld <Jason@zx2c4.com>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-01-30 07:07:42 -05:00
|
|
|
osyield_no_g()
|
2015-10-18 17:04:05 -07:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if old == 0 && !nilokay {
|
2016-07-18 23:00:43 -07:00
|
|
|
if !incr {
|
|
|
|
|
// Add 1 to the number of threads
|
|
|
|
|
// waiting for an M.
|
|
|
|
|
// This is cleared by newextram.
|
2022-08-25 21:08:02 +08:00
|
|
|
extraMWaiters.Add(1)
|
2016-07-18 23:00:43 -07:00
|
|
|
incr = true
|
|
|
|
|
}
|
runtime: clean up system calls during cgo callback init
During a cgocallback, the runtime calls needm to get an m.
The calls made during needm cannot themselves assume that
there is an m or a g (which is attached to the m).
In the old days of making direct system calls, the only thing
you had to do for such functions was mark them //go:nosplit,
to avoid the use of g in the stack split prologue.
But now, on operating systems that make system calls through
shared libraries and use code that saves state in the g or m
before doing so, it's not safe to assume g exists. In fact, it is
not even safe to call getg(), because it might fault deferencing
the TLS storage to find the g pointer (that storage may not be
initialized yet, at least on Windows, and perhaps on other systems
in the future).
The specific routines that are problematic are usleep and osyield,
which are called during lock contention in lockextra, called
from needm.
All this is rather subtle and hidden, so in addition to fixing the
problem on Windows, this CL makes the fact of not running on
a g much clearer by introducing variants usleep_no_g and
osyield_no_g whose names should make clear that there is no g.
And then we can remove the various sketchy getg() == nil checks
in the existing routines.
As part of this cleanup, this CL also deletes onosstack on Windows.
onosstack is from back when the runtime was implemented in C.
It predates systemstack but does essentially the same thing.
Instead of having two different copies of this code, we can use
systemstack consistently. This way we need not port onosstack
to each architecture.
This CL is part of a stack adding windows/arm64
support (#36439), intended to land in the Go 1.17 cycle.
This CL is, however, not windows/arm64-specific.
It is cleanup meant to make the port (and future ports) easier.
Change-Id: I3352de1fd0a3c26267c6e209063e6e86abd26187
Reviewed-on: https://go-review.googlesource.com/c/go/+/288793
Trust: Russ Cox <rsc@golang.org>
Trust: Jason A. Donenfeld <Jason@zx2c4.com>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-01-30 07:07:42 -05:00
|
|
|
usleep_no_g(1)
|
2015-10-18 17:04:05 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2022-08-25 21:08:02 +08:00
|
|
|
if extram.CompareAndSwap(old, locked) {
|
2015-10-18 17:04:05 -07:00
|
|
|
return (*m)(unsafe.Pointer(old))
|
|
|
|
|
}
|
runtime: clean up system calls during cgo callback init
During a cgocallback, the runtime calls needm to get an m.
The calls made during needm cannot themselves assume that
there is an m or a g (which is attached to the m).
In the old days of making direct system calls, the only thing
you had to do for such functions was mark them //go:nosplit,
to avoid the use of g in the stack split prologue.
But now, on operating systems that make system calls through
shared libraries and use code that saves state in the g or m
before doing so, it's not safe to assume g exists. In fact, it is
not even safe to call getg(), because it might fault deferencing
the TLS storage to find the g pointer (that storage may not be
initialized yet, at least on Windows, and perhaps on other systems
in the future).
The specific routines that are problematic are usleep and osyield,
which are called during lock contention in lockextra, called
from needm.
All this is rather subtle and hidden, so in addition to fixing the
problem on Windows, this CL makes the fact of not running on
a g much clearer by introducing variants usleep_no_g and
osyield_no_g whose names should make clear that there is no g.
And then we can remove the various sketchy getg() == nil checks
in the existing routines.
As part of this cleanup, this CL also deletes onosstack on Windows.
onosstack is from back when the runtime was implemented in C.
It predates systemstack but does essentially the same thing.
Instead of having two different copies of this code, we can use
systemstack consistently. This way we need not port onosstack
to each architecture.
This CL is part of a stack adding windows/arm64
support (#36439), intended to land in the Go 1.17 cycle.
This CL is, however, not windows/arm64-specific.
It is cleanup meant to make the port (and future ports) easier.
Change-Id: I3352de1fd0a3c26267c6e209063e6e86abd26187
Reviewed-on: https://go-review.googlesource.com/c/go/+/288793
Trust: Russ Cox <rsc@golang.org>
Trust: Jason A. Donenfeld <Jason@zx2c4.com>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-01-30 07:07:42 -05:00
|
|
|
osyield_no_g()
|
2015-10-18 17:04:05 -07:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func unlockextra(mp *m) {
|
2022-08-25 21:08:02 +08:00
|
|
|
extram.Store(uintptr(unsafe.Pointer(mp)))
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
runtime, syscall: reimplement AllThreadsSyscall using only signals.
In issue 50113, we see that a thread blocked in a system call can result
in a hang of AllThreadsSyscall. To resolve this, we must send a signal
to these threads to knock them out of the system call long enough to run
the per-thread syscall.
Stepping back, if we need to send signals anyway, it should be possible
to implement this entire mechanism on top of signals. This CL does so,
vastly simplifying the mechanism, both as a direct result of
newly-unnecessary code as well as some ancillary simplifications to make
things simpler to follow.
Major changes:
* The rest of the mechanism is moved to os_linux.go, with fields in mOS
instead of m itself.
* 'Fixup' fields and functions are renamed to 'perThreadSyscall' so they
are more precise about their purpose.
* Rather than getting passed a closure, doAllThreadsSyscall takes the
syscall number and arguments. This avoids a lot of hairy behavior:
* The closure may potentially only be live in fields in the M,
hidden from the GC. Not necessary with no closure.
* The need to loan out the race context. A direct RawSyscall6 call
does not require any race context.
* The closure previously conditionally panicked in strange
locations, like a signal handler. Now we simply throw.
* All manual fixup synchronization with mPark, sysmon, templateThread,
sigqueue, etc is gone. The core approach is much simpler:
doAllThreadsSyscall sends a signal to every thread in allm, which
executes the system call from the signal handler. We use (SIGRTMIN +
1), aka SIGSETXID, the same signal used by glibc for this purpose. As
such, we are careful to only handle this signal on non-cgo binaries.
Synchronization with thread creation is a key part of this CL. The
comment near the top of doAllThreadsSyscall describes the required
synchronization semantics and how they are achieved.
Note that current use of allocmLock protects the state mutations of allm
that are also protected by sched.lock. allocmLock is used instead of
sched.lock simply to avoid holding sched.lock for so long.
Fixes #50113
Change-Id: Ic7ea856dc66cf711731540a54996e08fc986ce84
Reviewed-on: https://go-review.googlesource.com/c/go/+/383434
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-02-04 17:15:28 -05:00
|
|
|
var (
|
|
|
|
|
// allocmLock is locked for read when creating new Ms in allocm and their
|
|
|
|
|
// addition to allm. Thus acquiring this lock for write blocks the
|
|
|
|
|
// creation of new Ms.
|
|
|
|
|
allocmLock rwmutex
|
|
|
|
|
|
|
|
|
|
// execLock serializes exec and clone to avoid bugs or unspecified
|
|
|
|
|
// behaviour around exec'ing while creating/destroying threads. See
|
|
|
|
|
// issue #19546.
|
|
|
|
|
execLock rwmutex
|
|
|
|
|
)
|
2017-05-20 17:22:36 +01:00
|
|
|
|
2022-11-01 12:33:59 -07:00
|
|
|
// These errors are reported (via writeErrStr) by some OS-specific
|
|
|
|
|
// versions of newosproc and newosproc0.
|
|
|
|
|
const (
|
|
|
|
|
failthreadcreate = "runtime: failed to create new OS thread\n"
|
|
|
|
|
failallocatestack = "runtime: failed to allocate stack for the new OS thread\n"
|
|
|
|
|
)
|
|
|
|
|
|
2017-06-15 10:51:15 -04:00
|
|
|
// newmHandoff contains a list of m structures that need new OS threads.
|
|
|
|
|
// This is used by newm in situations where newm itself can't safely
|
|
|
|
|
// start an OS thread.
|
|
|
|
|
var newmHandoff struct {
|
|
|
|
|
lock mutex
|
|
|
|
|
|
|
|
|
|
// newm points to a list of M structures that need new OS
|
|
|
|
|
// threads. The list is linked through m.schedlink.
|
|
|
|
|
newm muintptr
|
|
|
|
|
|
|
|
|
|
// waiting indicates that wake needs to be notified when an m
|
|
|
|
|
// is put on the list.
|
|
|
|
|
waiting bool
|
|
|
|
|
wake note
|
|
|
|
|
|
|
|
|
|
// haveTemplateThread indicates that the templateThread has
|
|
|
|
|
// been started. This is not protected by lock. Use cas to set
|
|
|
|
|
// to 1.
|
|
|
|
|
haveTemplateThread uint32
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-01 23:21:55 +00:00
|
|
|
// Create a new m. It will start off with a call to fn, or else the scheduler.
|
2015-10-18 17:04:05 -07:00
|
|
|
// fn needs to be static and not a heap allocated closure.
|
|
|
|
|
// May run with m.p==nil, so write barriers are not allowed.
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
//
|
|
|
|
|
// id is optional pre-allocated m ID. Omit by passing -1.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2021-02-09 15:48:41 -05:00
|
|
|
func newm(fn func(), pp *p, id int64) {
|
runtime, syscall: reimplement AllThreadsSyscall using only signals.
In issue 50113, we see that a thread blocked in a system call can result
in a hang of AllThreadsSyscall. To resolve this, we must send a signal
to these threads to knock them out of the system call long enough to run
the per-thread syscall.
Stepping back, if we need to send signals anyway, it should be possible
to implement this entire mechanism on top of signals. This CL does so,
vastly simplifying the mechanism, both as a direct result of
newly-unnecessary code as well as some ancillary simplifications to make
things simpler to follow.
Major changes:
* The rest of the mechanism is moved to os_linux.go, with fields in mOS
instead of m itself.
* 'Fixup' fields and functions are renamed to 'perThreadSyscall' so they
are more precise about their purpose.
* Rather than getting passed a closure, doAllThreadsSyscall takes the
syscall number and arguments. This avoids a lot of hairy behavior:
* The closure may potentially only be live in fields in the M,
hidden from the GC. Not necessary with no closure.
* The need to loan out the race context. A direct RawSyscall6 call
does not require any race context.
* The closure previously conditionally panicked in strange
locations, like a signal handler. Now we simply throw.
* All manual fixup synchronization with mPark, sysmon, templateThread,
sigqueue, etc is gone. The core approach is much simpler:
doAllThreadsSyscall sends a signal to every thread in allm, which
executes the system call from the signal handler. We use (SIGRTMIN +
1), aka SIGSETXID, the same signal used by glibc for this purpose. As
such, we are careful to only handle this signal on non-cgo binaries.
Synchronization with thread creation is a key part of this CL. The
comment near the top of doAllThreadsSyscall describes the required
synchronization semantics and how they are achieved.
Note that current use of allocmLock protects the state mutations of allm
that are also protected by sched.lock. allocmLock is used instead of
sched.lock simply to avoid holding sched.lock for so long.
Fixes #50113
Change-Id: Ic7ea856dc66cf711731540a54996e08fc986ce84
Reviewed-on: https://go-review.googlesource.com/c/go/+/383434
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-02-04 17:15:28 -05:00
|
|
|
// allocm adds a new M to allm, but they do not start until created by
|
|
|
|
|
// the OS in newm1 or the template thread.
|
|
|
|
|
//
|
|
|
|
|
// doAllThreadsSyscall requires that every M in allm will eventually
|
|
|
|
|
// start and be signal-able, even with a STW.
|
|
|
|
|
//
|
|
|
|
|
// Disable preemption here until we start the thread to ensure that
|
|
|
|
|
// newm is not preempted between allocm and starting the new thread,
|
|
|
|
|
// ensuring that anything added to allm is guaranteed to eventually
|
|
|
|
|
// start.
|
|
|
|
|
acquirem()
|
|
|
|
|
|
2021-02-09 15:48:41 -05:00
|
|
|
mp := allocm(pp, fn, id)
|
|
|
|
|
mp.nextp.set(pp)
|
2015-12-19 10:17:10 -08:00
|
|
|
mp.sigmask = initSigmask
|
2017-10-12 22:59:16 +02:00
|
|
|
if gp := getg(); gp != nil && gp.m != nil && (gp.m.lockedExt != 0 || gp.m.incgo) && GOOS != "plan9" {
|
2017-06-15 10:51:15 -04:00
|
|
|
// We're on a locked M or a thread that may have been
|
|
|
|
|
// started by C. The kernel state of this thread may
|
|
|
|
|
// be strange (the user may have locked it for that
|
|
|
|
|
// purpose). We don't want to clone that into another
|
|
|
|
|
// thread. Instead, ask a known-good thread to create
|
|
|
|
|
// the thread for us.
|
|
|
|
|
//
|
2017-10-12 22:59:16 +02:00
|
|
|
// This is disabled on Plan 9. See golang.org/issue/22227.
|
|
|
|
|
//
|
2017-06-15 10:51:15 -04:00
|
|
|
// TODO: This may be unnecessary on Windows, which
|
|
|
|
|
// doesn't model thread creation off fork.
|
|
|
|
|
lock(&newmHandoff.lock)
|
|
|
|
|
if newmHandoff.haveTemplateThread == 0 {
|
|
|
|
|
throw("on a locked thread with no template thread")
|
|
|
|
|
}
|
|
|
|
|
mp.schedlink = newmHandoff.newm
|
|
|
|
|
newmHandoff.newm.set(mp)
|
|
|
|
|
if newmHandoff.waiting {
|
|
|
|
|
newmHandoff.waiting = false
|
|
|
|
|
notewakeup(&newmHandoff.wake)
|
|
|
|
|
}
|
|
|
|
|
unlock(&newmHandoff.lock)
|
runtime, syscall: reimplement AllThreadsSyscall using only signals.
In issue 50113, we see that a thread blocked in a system call can result
in a hang of AllThreadsSyscall. To resolve this, we must send a signal
to these threads to knock them out of the system call long enough to run
the per-thread syscall.
Stepping back, if we need to send signals anyway, it should be possible
to implement this entire mechanism on top of signals. This CL does so,
vastly simplifying the mechanism, both as a direct result of
newly-unnecessary code as well as some ancillary simplifications to make
things simpler to follow.
Major changes:
* The rest of the mechanism is moved to os_linux.go, with fields in mOS
instead of m itself.
* 'Fixup' fields and functions are renamed to 'perThreadSyscall' so they
are more precise about their purpose.
* Rather than getting passed a closure, doAllThreadsSyscall takes the
syscall number and arguments. This avoids a lot of hairy behavior:
* The closure may potentially only be live in fields in the M,
hidden from the GC. Not necessary with no closure.
* The need to loan out the race context. A direct RawSyscall6 call
does not require any race context.
* The closure previously conditionally panicked in strange
locations, like a signal handler. Now we simply throw.
* All manual fixup synchronization with mPark, sysmon, templateThread,
sigqueue, etc is gone. The core approach is much simpler:
doAllThreadsSyscall sends a signal to every thread in allm, which
executes the system call from the signal handler. We use (SIGRTMIN +
1), aka SIGSETXID, the same signal used by glibc for this purpose. As
such, we are careful to only handle this signal on non-cgo binaries.
Synchronization with thread creation is a key part of this CL. The
comment near the top of doAllThreadsSyscall describes the required
synchronization semantics and how they are achieved.
Note that current use of allocmLock protects the state mutations of allm
that are also protected by sched.lock. allocmLock is used instead of
sched.lock simply to avoid holding sched.lock for so long.
Fixes #50113
Change-Id: Ic7ea856dc66cf711731540a54996e08fc986ce84
Reviewed-on: https://go-review.googlesource.com/c/go/+/383434
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-02-04 17:15:28 -05:00
|
|
|
// The M has not started yet, but the template thread does not
|
|
|
|
|
// participate in STW, so it will always process queued Ms and
|
|
|
|
|
// it is safe to releasem.
|
|
|
|
|
releasem(getg().m)
|
2017-06-15 10:51:15 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
newm1(mp)
|
runtime, syscall: reimplement AllThreadsSyscall using only signals.
In issue 50113, we see that a thread blocked in a system call can result
in a hang of AllThreadsSyscall. To resolve this, we must send a signal
to these threads to knock them out of the system call long enough to run
the per-thread syscall.
Stepping back, if we need to send signals anyway, it should be possible
to implement this entire mechanism on top of signals. This CL does so,
vastly simplifying the mechanism, both as a direct result of
newly-unnecessary code as well as some ancillary simplifications to make
things simpler to follow.
Major changes:
* The rest of the mechanism is moved to os_linux.go, with fields in mOS
instead of m itself.
* 'Fixup' fields and functions are renamed to 'perThreadSyscall' so they
are more precise about their purpose.
* Rather than getting passed a closure, doAllThreadsSyscall takes the
syscall number and arguments. This avoids a lot of hairy behavior:
* The closure may potentially only be live in fields in the M,
hidden from the GC. Not necessary with no closure.
* The need to loan out the race context. A direct RawSyscall6 call
does not require any race context.
* The closure previously conditionally panicked in strange
locations, like a signal handler. Now we simply throw.
* All manual fixup synchronization with mPark, sysmon, templateThread,
sigqueue, etc is gone. The core approach is much simpler:
doAllThreadsSyscall sends a signal to every thread in allm, which
executes the system call from the signal handler. We use (SIGRTMIN +
1), aka SIGSETXID, the same signal used by glibc for this purpose. As
such, we are careful to only handle this signal on non-cgo binaries.
Synchronization with thread creation is a key part of this CL. The
comment near the top of doAllThreadsSyscall describes the required
synchronization semantics and how they are achieved.
Note that current use of allocmLock protects the state mutations of allm
that are also protected by sched.lock. allocmLock is used instead of
sched.lock simply to avoid holding sched.lock for so long.
Fixes #50113
Change-Id: Ic7ea856dc66cf711731540a54996e08fc986ce84
Reviewed-on: https://go-review.googlesource.com/c/go/+/383434
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-02-04 17:15:28 -05:00
|
|
|
releasem(getg().m)
|
2017-06-15 10:51:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newm1(mp *m) {
|
2015-10-18 17:04:05 -07:00
|
|
|
if iscgo {
|
|
|
|
|
var ts cgothreadstart
|
|
|
|
|
if _cgo_thread_start == nil {
|
|
|
|
|
throw("_cgo_thread_start missing")
|
|
|
|
|
}
|
|
|
|
|
ts.g.set(mp.g0)
|
|
|
|
|
ts.tls = (*uint64)(unsafe.Pointer(&mp.tls[0]))
|
2021-05-20 21:33:01 -04:00
|
|
|
ts.fn = unsafe.Pointer(abi.FuncPCABI0(mstart))
|
2016-01-05 14:06:58 -08:00
|
|
|
if msanenabled {
|
|
|
|
|
msanwrite(unsafe.Pointer(&ts), unsafe.Sizeof(ts))
|
|
|
|
|
}
|
2021-01-05 17:52:43 +08:00
|
|
|
if asanenabled {
|
|
|
|
|
asanwrite(unsafe.Pointer(&ts), unsafe.Sizeof(ts))
|
|
|
|
|
}
|
2017-06-28 15:58:59 -04:00
|
|
|
execLock.rlock() // Prevent process clone.
|
2015-10-18 17:04:05 -07:00
|
|
|
asmcgocall(_cgo_thread_start, unsafe.Pointer(&ts))
|
2017-06-28 15:58:59 -04:00
|
|
|
execLock.runlock()
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
2017-06-28 15:58:59 -04:00
|
|
|
execLock.rlock() // Prevent process clone.
|
2018-04-23 07:30:32 -07:00
|
|
|
newosproc(mp)
|
2017-06-28 15:58:59 -04:00
|
|
|
execLock.runlock()
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2017-06-15 10:51:15 -04:00
|
|
|
// startTemplateThread starts the template thread if it is not already
|
|
|
|
|
// running.
|
|
|
|
|
//
|
|
|
|
|
// The calling thread must itself be in a known-good state.
|
|
|
|
|
func startTemplateThread() {
|
2018-03-31 23:14:17 +02:00
|
|
|
if GOARCH == "wasm" { // no threads on wasm yet
|
|
|
|
|
return
|
|
|
|
|
}
|
runtime: disable preemption in startTemplateThread
When a locked M wants to start a new M, it hands off to the template
thread to actually call clone and start the thread. The template thread
is lazily created the first time a thread is locked (or if cgo is in
use).
stoplockedm will release the P (_Pidle), then call handoffp to give the
P to another M. In the case of a pending STW, one of two things can
happen:
1. handoffp starts an M, which does acquirep followed by schedule, which
will finally enter _Pgcstop.
2. handoffp immediately enters _Pgcstop. This only occurs if the P has
no local work, GC work, and no spinning M is required.
If handoffp starts an M, and must create a new M to do so, then newm
will simply queue the M on newmHandoff for the template thread to do the
clone.
When a stop-the-world is required, stopTheWorldWithSema will start the
stop and then wait for all Ps to enter _Pgcstop. If the template thread
is not fully created because startTemplateThread gets stopped, then
another stoplockedm may queue an M that will never get created, and the
handoff P will never leave _Pidle. Thus stopTheWorldWithSema will wait
forever.
A sequence to trigger this hang when STW occurs can be visualized with
two threads:
T1 T2
------------------------------- -----------------------------
LockOSThread LockOSThread
haveTemplateThread == 0
startTemplateThread
haveTemplateThread = 1
newm haveTemplateThread == 1
preempt -> schedule g.m.lockedExt++
gcstopm -> _Pgcstop g.m.lockedg = ...
park g.lockedm = ...
return
... (any code)
preempt -> schedule
stoplockedm
releasep -> _Pidle
handoffp
startm (first 3 handoffp cases)
newm
g.m.lockedExt != 0
Add to newmHandoff, return
park
Note that the P in T2 is stuck sitting in _Pidle. Since the template
thread isn't running, the new M will not be started complete the
transition to _Pgcstop.
To resolve this, we disable preemption around the assignment of
haveTemplateThread and the creation of the template thread in order to
guarantee that if handTemplateThread is set then the template thread
will eventually exist, in the presence of stops.
Fixes #38931
Change-Id: I50535fbbe2f328f47b18e24d9030136719274191
Reviewed-on: https://go-review.googlesource.com/c/go/+/232978
Run-TryBot: Michael Pratt <mpratt@google.com>
Reviewed-by: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2020-05-07 18:13:21 -04:00
|
|
|
|
|
|
|
|
// Disable preemption to guarantee that the template thread will be
|
|
|
|
|
// created before a park once haveTemplateThread is set.
|
|
|
|
|
mp := acquirem()
|
2017-06-15 10:51:15 -04:00
|
|
|
if !atomic.Cas(&newmHandoff.haveTemplateThread, 0, 1) {
|
runtime: disable preemption in startTemplateThread
When a locked M wants to start a new M, it hands off to the template
thread to actually call clone and start the thread. The template thread
is lazily created the first time a thread is locked (or if cgo is in
use).
stoplockedm will release the P (_Pidle), then call handoffp to give the
P to another M. In the case of a pending STW, one of two things can
happen:
1. handoffp starts an M, which does acquirep followed by schedule, which
will finally enter _Pgcstop.
2. handoffp immediately enters _Pgcstop. This only occurs if the P has
no local work, GC work, and no spinning M is required.
If handoffp starts an M, and must create a new M to do so, then newm
will simply queue the M on newmHandoff for the template thread to do the
clone.
When a stop-the-world is required, stopTheWorldWithSema will start the
stop and then wait for all Ps to enter _Pgcstop. If the template thread
is not fully created because startTemplateThread gets stopped, then
another stoplockedm may queue an M that will never get created, and the
handoff P will never leave _Pidle. Thus stopTheWorldWithSema will wait
forever.
A sequence to trigger this hang when STW occurs can be visualized with
two threads:
T1 T2
------------------------------- -----------------------------
LockOSThread LockOSThread
haveTemplateThread == 0
startTemplateThread
haveTemplateThread = 1
newm haveTemplateThread == 1
preempt -> schedule g.m.lockedExt++
gcstopm -> _Pgcstop g.m.lockedg = ...
park g.lockedm = ...
return
... (any code)
preempt -> schedule
stoplockedm
releasep -> _Pidle
handoffp
startm (first 3 handoffp cases)
newm
g.m.lockedExt != 0
Add to newmHandoff, return
park
Note that the P in T2 is stuck sitting in _Pidle. Since the template
thread isn't running, the new M will not be started complete the
transition to _Pgcstop.
To resolve this, we disable preemption around the assignment of
haveTemplateThread and the creation of the template thread in order to
guarantee that if handTemplateThread is set then the template thread
will eventually exist, in the presence of stops.
Fixes #38931
Change-Id: I50535fbbe2f328f47b18e24d9030136719274191
Reviewed-on: https://go-review.googlesource.com/c/go/+/232978
Run-TryBot: Michael Pratt <mpratt@google.com>
Reviewed-by: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2020-05-07 18:13:21 -04:00
|
|
|
releasem(mp)
|
2017-06-15 10:51:15 -04:00
|
|
|
return
|
|
|
|
|
}
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
newm(templateThread, nil, -1)
|
runtime: disable preemption in startTemplateThread
When a locked M wants to start a new M, it hands off to the template
thread to actually call clone and start the thread. The template thread
is lazily created the first time a thread is locked (or if cgo is in
use).
stoplockedm will release the P (_Pidle), then call handoffp to give the
P to another M. In the case of a pending STW, one of two things can
happen:
1. handoffp starts an M, which does acquirep followed by schedule, which
will finally enter _Pgcstop.
2. handoffp immediately enters _Pgcstop. This only occurs if the P has
no local work, GC work, and no spinning M is required.
If handoffp starts an M, and must create a new M to do so, then newm
will simply queue the M on newmHandoff for the template thread to do the
clone.
When a stop-the-world is required, stopTheWorldWithSema will start the
stop and then wait for all Ps to enter _Pgcstop. If the template thread
is not fully created because startTemplateThread gets stopped, then
another stoplockedm may queue an M that will never get created, and the
handoff P will never leave _Pidle. Thus stopTheWorldWithSema will wait
forever.
A sequence to trigger this hang when STW occurs can be visualized with
two threads:
T1 T2
------------------------------- -----------------------------
LockOSThread LockOSThread
haveTemplateThread == 0
startTemplateThread
haveTemplateThread = 1
newm haveTemplateThread == 1
preempt -> schedule g.m.lockedExt++
gcstopm -> _Pgcstop g.m.lockedg = ...
park g.lockedm = ...
return
... (any code)
preempt -> schedule
stoplockedm
releasep -> _Pidle
handoffp
startm (first 3 handoffp cases)
newm
g.m.lockedExt != 0
Add to newmHandoff, return
park
Note that the P in T2 is stuck sitting in _Pidle. Since the template
thread isn't running, the new M will not be started complete the
transition to _Pgcstop.
To resolve this, we disable preemption around the assignment of
haveTemplateThread and the creation of the template thread in order to
guarantee that if handTemplateThread is set then the template thread
will eventually exist, in the presence of stops.
Fixes #38931
Change-Id: I50535fbbe2f328f47b18e24d9030136719274191
Reviewed-on: https://go-review.googlesource.com/c/go/+/232978
Run-TryBot: Michael Pratt <mpratt@google.com>
Reviewed-by: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2020-05-07 18:13:21 -04:00
|
|
|
releasem(mp)
|
2017-06-15 10:51:15 -04:00
|
|
|
}
|
|
|
|
|
|
2018-06-04 15:20:07 +00:00
|
|
|
// templateThread is a thread in a known-good state that exists solely
|
2017-06-15 10:51:15 -04:00
|
|
|
// to start new threads in known-good states when the calling thread
|
2018-10-06 06:10:25 +00:00
|
|
|
// may not be in a good state.
|
2017-06-15 10:51:15 -04:00
|
|
|
//
|
|
|
|
|
// Many programs never need this, so templateThread is started lazily
|
|
|
|
|
// when we first enter a state that might lead to running on a thread
|
|
|
|
|
// in an unknown state.
|
|
|
|
|
//
|
|
|
|
|
// templateThread runs on an M without a P, so it must not have write
|
|
|
|
|
// barriers.
|
|
|
|
|
//
|
|
|
|
|
//go:nowritebarrierrec
|
|
|
|
|
func templateThread() {
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
sched.nmsys++
|
|
|
|
|
checkdead()
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
lock(&newmHandoff.lock)
|
|
|
|
|
for newmHandoff.newm != 0 {
|
|
|
|
|
newm := newmHandoff.newm.ptr()
|
|
|
|
|
newmHandoff.newm = 0
|
|
|
|
|
unlock(&newmHandoff.lock)
|
|
|
|
|
for newm != nil {
|
|
|
|
|
next := newm.schedlink.ptr()
|
|
|
|
|
newm.schedlink = 0
|
|
|
|
|
newm1(newm)
|
|
|
|
|
newm = next
|
|
|
|
|
}
|
|
|
|
|
lock(&newmHandoff.lock)
|
|
|
|
|
}
|
|
|
|
|
newmHandoff.waiting = true
|
|
|
|
|
noteclear(&newmHandoff.wake)
|
|
|
|
|
unlock(&newmHandoff.lock)
|
|
|
|
|
notesleep(&newmHandoff.wake)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Stops execution of the current m until new work is available.
|
|
|
|
|
// Returns with acquired P.
|
|
|
|
|
func stopm() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.locks != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("stopm holding locks")
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.p != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("stopm holding p")
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.spinning {
|
2015-12-08 15:11:27 +01:00
|
|
|
throw("stopm spinning")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lock(&sched.lock)
|
2021-02-11 11:15:53 -05:00
|
|
|
mput(gp.m)
|
2015-10-18 17:04:05 -07:00
|
|
|
unlock(&sched.lock)
|
syscall: support POSIX semantics for Linux syscalls
This change adds two new methods for invoking system calls
under Linux: syscall.AllThreadsSyscall() and
syscall.AllThreadsSyscall6().
These system call wrappers ensure that all OSThreads mirror
a common system call. The wrappers serialize execution of the
runtime to ensure no race conditions where any Go code observes
a non-atomic OS state change. As such, the syscalls have
higher runtime overhead than regular system calls, and only
need to be used where such thread (or 'm' in the parlance
of the runtime sources) consistency is required.
The new support is used to enable these functions under Linux:
syscall.Setegid(), syscall.Seteuid(), syscall.Setgroups(),
syscall.Setgid(), syscall.Setregid(), syscall.Setreuid(),
syscall.Setresgid(), syscall.Setresuid() and syscall.Setuid().
They work identically to their glibc counterparts.
Extensive discussion of the background issue addressed in this
patch can be found here:
https://github.com/golang/go/issues/1435
In the case where cgo is used, the C runtime can launch pthreads that
are not managed by the Go runtime. As such, the added
syscall.AllThreadsSyscall*() return ENOTSUP when cgo is enabled.
However, for the 9 syscall.Set*() functions listed above, when cgo is
active, these functions redirect to invoke their C.set*() equivalents
in glibc, which wraps the raw system calls with a nptl:setxid fixup
mechanism. This achieves POSIX semantics for these functions in the
combined Go and C runtime.
As a side note, the glibc/nptl:setxid support (2019-11-30) does not
extend to all security related system calls under Linux so using
native Go (CGO_ENABLED=0) and these AllThreadsSyscall*()s, where
needed, will yield more well defined/consistent behavior over all
threads of a Go program. That is, using the
syscall.AllThreadsSyscall*() wrappers for things like setting state
through SYS_PRCTL and SYS_CAPSET etc.
Fixes #1435
Change-Id: Ib1a3e16b9180f64223196a32fc0f9dce14d9105c
Reviewed-on: https://go-review.googlesource.com/c/go/+/210639
Trust: Emmanuel Odeke <emm.odeke@gmail.com>
Trust: Ian Lance Taylor <iant@golang.org>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Austin Clements <austin@google.com>
2019-12-09 21:50:16 -08:00
|
|
|
mPark()
|
2021-02-11 11:15:53 -05:00
|
|
|
acquirep(gp.m.nextp.ptr())
|
|
|
|
|
gp.m.nextp = 0
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func mspinning() {
|
2015-12-08 15:11:27 +01:00
|
|
|
// startm's caller incremented nmspinning. Set the new M's spinning.
|
|
|
|
|
getg().m.spinning = true
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Schedules some M to run the p (creates an M if necessary).
|
|
|
|
|
// If p==nil, tries to get an idle P, if no idle P's does nothing.
|
|
|
|
|
// May run with m.p==nil, so write barriers are not allowed.
|
2022-03-01 15:06:37 -05:00
|
|
|
// If spinning is set, the caller has incremented nmspinning and must provide a
|
|
|
|
|
// P. startm will set m.spinning in the newly started M.
|
2020-11-02 16:34:51 -05:00
|
|
|
//
|
|
|
|
|
// Callers passing a non-nil P must call from a non-preemptible context. See
|
|
|
|
|
// comment on acquirem below.
|
|
|
|
|
//
|
|
|
|
|
// Must not have write barriers because this may be called without a P.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2021-02-09 15:48:41 -05:00
|
|
|
func startm(pp *p, spinning bool) {
|
2020-11-02 16:34:51 -05:00
|
|
|
// Disable preemption.
|
|
|
|
|
//
|
|
|
|
|
// Every owned P must have an owner that will eventually stop it in the
|
|
|
|
|
// event of a GC stop request. startm takes transient ownership of a P
|
|
|
|
|
// (either from argument or pidleget below) and transfers ownership to
|
|
|
|
|
// a started M, which will be responsible for performing the stop.
|
|
|
|
|
//
|
|
|
|
|
// Preemption must be disabled during this transient ownership,
|
|
|
|
|
// otherwise the P this is running on may enter GC stop while still
|
|
|
|
|
// holding the transient P, leaving that P in limbo and deadlocking the
|
|
|
|
|
// STW.
|
|
|
|
|
//
|
|
|
|
|
// Callers passing a non-nil P must already be in non-preemptible
|
|
|
|
|
// context, otherwise such preemption could occur on function entry to
|
|
|
|
|
// startm. Callers passing a nil P may be preemptible, so we must
|
|
|
|
|
// disable preemption before acquiring a P from pidleget below.
|
|
|
|
|
mp := acquirem()
|
2015-10-18 17:04:05 -07:00
|
|
|
lock(&sched.lock)
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp == nil {
|
2022-03-01 15:06:37 -05:00
|
|
|
if spinning {
|
|
|
|
|
// TODO(prattmic): All remaining calls to this function
|
|
|
|
|
// with _p_ == nil could be cleaned up to find a P
|
|
|
|
|
// before calling startm.
|
|
|
|
|
throw("startm: P required for spinning=true")
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
pp, _ = pidleget(0)
|
|
|
|
|
if pp == nil {
|
2015-10-18 17:04:05 -07:00
|
|
|
unlock(&sched.lock)
|
2020-11-02 16:34:51 -05:00
|
|
|
releasem(mp)
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-02 16:34:51 -05:00
|
|
|
nmp := mget()
|
|
|
|
|
if nmp == nil {
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
// No M is available, we must drop sched.lock and call newm.
|
|
|
|
|
// However, we already own a P to assign to the M.
|
|
|
|
|
//
|
|
|
|
|
// Once sched.lock is released, another G (e.g., in a syscall),
|
|
|
|
|
// could find no idle P while checkdead finds a runnable G but
|
|
|
|
|
// no running M's because this new M hasn't started yet, thus
|
|
|
|
|
// throwing in an apparent deadlock.
|
|
|
|
|
//
|
|
|
|
|
// Avoid this situation by pre-allocating the ID for the new M,
|
|
|
|
|
// thus marking it as 'running' before we drop sched.lock. This
|
|
|
|
|
// new M will eventually run the scheduler to execute any
|
|
|
|
|
// queued G's.
|
|
|
|
|
id := mReserveID()
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
var fn func()
|
|
|
|
|
if spinning {
|
2015-12-08 15:11:27 +01:00
|
|
|
// The caller incremented nmspinning, so set m.spinning in the new M.
|
2015-10-18 17:04:05 -07:00
|
|
|
fn = mspinning
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
newm(fn, pp, id)
|
|
|
|
|
// Ownership transfer of pp committed by start in newm.
|
2020-11-02 16:34:51 -05:00
|
|
|
// Preemption is now safe.
|
|
|
|
|
releasem(mp)
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
runtime: ensure startm new M is consistently visible to checkdead
If no M is available, startm first grabs an idle P, then drops
sched.lock and calls newm to start a new M to run than P.
Unfortunately, that leaves a window in which a G (e.g., returning from a
syscall) may find no idle P, add to the global runq, and then in stopm
discover that there are no running M's, a condition that should be
impossible with runnable G's.
To avoid this condition, we pre-allocate the new M ID in startm before
dropping sched.lock. This ensures that checkdead will see the M as
running, and since that new M must eventually run the scheduler, it will
handle any pending work as necessary.
Outside of startm, most other calls to newm/allocm don't have a P at
all. The only exception is startTheWorldWithSema, which always has an M
if there is 1 P (i.e., the currently running M), and if there is >1 P
the findrunnable spinning dance ensures the problem never occurs.
This has been tested with strategically placed sleeps in the runtime to
help induce the correct race ordering, but the timing on this is too
narrow for a test that can be checked in.
Fixes #40368
Change-Id: If5e0293a430cc85154b7ed55bc6dadf9b340abe2
Reviewed-on: https://go-review.googlesource.com/c/go/+/245018
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2020-07-27 15:04:17 -04:00
|
|
|
unlock(&sched.lock)
|
2020-11-02 16:34:51 -05:00
|
|
|
if nmp.spinning {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("startm: m is spinning")
|
|
|
|
|
}
|
2020-11-02 16:34:51 -05:00
|
|
|
if nmp.nextp != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("startm: m has p")
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if spinning && !runqempty(pp) {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("startm: p has runnable gs")
|
|
|
|
|
}
|
2015-12-08 15:11:27 +01:00
|
|
|
// The caller incremented nmspinning, so set m.spinning in the new M.
|
2020-11-02 16:34:51 -05:00
|
|
|
nmp.spinning = spinning
|
2021-02-09 15:48:41 -05:00
|
|
|
nmp.nextp.set(pp)
|
2020-11-02 16:34:51 -05:00
|
|
|
notewakeup(&nmp.park)
|
2021-02-09 15:48:41 -05:00
|
|
|
// Ownership transfer of pp committed by wakeup. Preemption is now
|
2020-11-02 16:34:51 -05:00
|
|
|
// safe.
|
|
|
|
|
releasem(mp)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hands off P from syscall or locked M.
|
|
|
|
|
// Always runs without a P, so write barriers are not allowed.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2021-02-09 15:48:41 -05:00
|
|
|
func handoffp(pp *p) {
|
runtime: start an M when handing off a P when there's GC work
Currently it's possible for the scheduler to deadlock with the right
confluence of locked Gs, assists, and scheduling of background mark
workers. Broadly, this happens because handoffp is stricter than
findrunnable, and if the only work for a P is GC work, handoffp will
put the P into idle, rather than starting an M to execute that P. One
way this can happen is as follows:
0. There is only one user G, which we'll call G 1. There is more than
one P, but they're all idle except the one running G 1.
1. G 1 locks itself to an M using runtime.LockOSThread.
2. GC starts up and enters mark 1.
3. G 1 performs a GC assist, which completes mark 1 without being
fully satisfied. Completing mark 1 causes all background mark
workers to park. And since the assist isn't fully satisfied, it
parks as well, waiting for a background mark worker to satisfy its
remaining assist debt.
4. The assist park enters the scheduler. Since G 1 is locked to the M,
the scheduler releases the P and calls handoffp to hand the P to
another M.
5. handoffp checks the local and global run queues, which are empty,
and sees that there are idle Ps, so rather than start an M, it puts
the P into idle.
At this point, all of the Gs are waiting and all of the Ps are idle.
In particular, none of the GC workers are running, so no mark work
gets done and the assist on the main G is never satisfied, so the
whole process soft locks up.
Fix this by making handoffp start an M if there is GC work. This
reintroduces a key invariant: that in any situation where findrunnable
would return a G to run on a P, handoffp for that P will start an M to
run work on that P.
Fixes #13645.
Tested by running 2,689 iterations of `go tool dist test -no-rebuild
runtime:cpu124` across 10 linux-amd64-noopt VMs with no failures.
Without this change, the failure rate was somewhere around 1%.
Performance change is negligible.
name old time/op new time/op delta
XBenchGarbage-12 2.48ms ± 2% 2.48ms ± 1% -0.24% (p=0.000 n=92+93)
name old time/op new time/op delta
BinaryTree17-12 2.86s ± 2% 2.87s ± 2% ~ (p=0.667 n=19+20)
Fannkuch11-12 2.52s ± 1% 2.47s ± 1% -2.05% (p=0.000 n=18+20)
FmtFprintfEmpty-12 51.7ns ± 1% 51.5ns ± 3% ~ (p=0.931 n=16+20)
FmtFprintfString-12 170ns ± 1% 168ns ± 1% -0.65% (p=0.000 n=19+19)
FmtFprintfInt-12 160ns ± 0% 160ns ± 0% +0.18% (p=0.033 n=17+19)
FmtFprintfIntInt-12 265ns ± 1% 273ns ± 1% +2.98% (p=0.000 n=17+19)
FmtFprintfPrefixedInt-12 235ns ± 1% 239ns ± 1% +1.99% (p=0.000 n=16+19)
FmtFprintfFloat-12 315ns ± 0% 315ns ± 1% ~ (p=0.250 n=17+19)
FmtManyArgs-12 1.04µs ± 1% 1.05µs ± 0% +0.87% (p=0.000 n=17+19)
GobDecode-12 7.93ms ± 0% 7.85ms ± 1% -1.03% (p=0.000 n=16+18)
GobEncode-12 6.62ms ± 1% 6.58ms ± 1% -0.60% (p=0.000 n=18+19)
Gzip-12 322ms ± 1% 320ms ± 1% -0.46% (p=0.009 n=20+20)
Gunzip-12 42.5ms ± 1% 42.5ms ± 0% ~ (p=0.751 n=19+19)
HTTPClientServer-12 69.7µs ± 1% 70.0µs ± 2% ~ (p=0.056 n=19+19)
JSONEncode-12 16.9ms ± 1% 16.7ms ± 1% -1.13% (p=0.000 n=19+19)
JSONDecode-12 61.5ms ± 1% 61.3ms ± 1% -0.35% (p=0.001 n=20+17)
Mandelbrot200-12 3.94ms ± 0% 3.91ms ± 0% -0.67% (p=0.000 n=20+18)
GoParse-12 3.71ms ± 1% 3.70ms ± 1% ~ (p=0.244 n=17+19)
RegexpMatchEasy0_32-12 101ns ± 1% 102ns ± 2% +0.54% (p=0.037 n=19+20)
RegexpMatchEasy0_1K-12 349ns ± 0% 350ns ± 0% +0.33% (p=0.000 n=17+18)
RegexpMatchEasy1_32-12 84.5ns ± 2% 84.2ns ± 1% -0.43% (p=0.048 n=19+20)
RegexpMatchEasy1_1K-12 510ns ± 1% 513ns ± 2% +0.58% (p=0.002 n=18+20)
RegexpMatchMedium_32-12 132ns ± 1% 134ns ± 1% +0.95% (p=0.000 n=20+20)
RegexpMatchMedium_1K-12 40.1µs ± 1% 39.6µs ± 1% -1.39% (p=0.000 n=20+20)
RegexpMatchHard_32-12 2.08µs ± 0% 2.06µs ± 1% -0.95% (p=0.000 n=18+18)
RegexpMatchHard_1K-12 62.2µs ± 1% 61.9µs ± 1% -0.42% (p=0.001 n=19+20)
Revcomp-12 537ms ± 0% 536ms ± 0% ~ (p=0.076 n=20+20)
Template-12 71.3ms ± 1% 69.3ms ± 1% -2.75% (p=0.000 n=20+20)
TimeParse-12 361ns ± 0% 360ns ± 1% ~ (p=0.056 n=19+19)
TimeFormat-12 353ns ± 0% 352ns ± 0% -0.23% (p=0.000 n=17+18)
[Geo mean] 62.6µs 62.5µs -0.17%
Change-Id: I0fbbbe4d7d99653ba5600ffb4394fa03558bc4e9
Reviewed-on: https://go-review.googlesource.com/19107
Reviewed-by: Rick Hudson <rlh@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-02-01 14:06:51 -05:00
|
|
|
// handoffp must start an M in any situation where
|
2021-02-09 15:48:41 -05:00
|
|
|
// findrunnable would return a G to run on pp.
|
runtime: start an M when handing off a P when there's GC work
Currently it's possible for the scheduler to deadlock with the right
confluence of locked Gs, assists, and scheduling of background mark
workers. Broadly, this happens because handoffp is stricter than
findrunnable, and if the only work for a P is GC work, handoffp will
put the P into idle, rather than starting an M to execute that P. One
way this can happen is as follows:
0. There is only one user G, which we'll call G 1. There is more than
one P, but they're all idle except the one running G 1.
1. G 1 locks itself to an M using runtime.LockOSThread.
2. GC starts up and enters mark 1.
3. G 1 performs a GC assist, which completes mark 1 without being
fully satisfied. Completing mark 1 causes all background mark
workers to park. And since the assist isn't fully satisfied, it
parks as well, waiting for a background mark worker to satisfy its
remaining assist debt.
4. The assist park enters the scheduler. Since G 1 is locked to the M,
the scheduler releases the P and calls handoffp to hand the P to
another M.
5. handoffp checks the local and global run queues, which are empty,
and sees that there are idle Ps, so rather than start an M, it puts
the P into idle.
At this point, all of the Gs are waiting and all of the Ps are idle.
In particular, none of the GC workers are running, so no mark work
gets done and the assist on the main G is never satisfied, so the
whole process soft locks up.
Fix this by making handoffp start an M if there is GC work. This
reintroduces a key invariant: that in any situation where findrunnable
would return a G to run on a P, handoffp for that P will start an M to
run work on that P.
Fixes #13645.
Tested by running 2,689 iterations of `go tool dist test -no-rebuild
runtime:cpu124` across 10 linux-amd64-noopt VMs with no failures.
Without this change, the failure rate was somewhere around 1%.
Performance change is negligible.
name old time/op new time/op delta
XBenchGarbage-12 2.48ms ± 2% 2.48ms ± 1% -0.24% (p=0.000 n=92+93)
name old time/op new time/op delta
BinaryTree17-12 2.86s ± 2% 2.87s ± 2% ~ (p=0.667 n=19+20)
Fannkuch11-12 2.52s ± 1% 2.47s ± 1% -2.05% (p=0.000 n=18+20)
FmtFprintfEmpty-12 51.7ns ± 1% 51.5ns ± 3% ~ (p=0.931 n=16+20)
FmtFprintfString-12 170ns ± 1% 168ns ± 1% -0.65% (p=0.000 n=19+19)
FmtFprintfInt-12 160ns ± 0% 160ns ± 0% +0.18% (p=0.033 n=17+19)
FmtFprintfIntInt-12 265ns ± 1% 273ns ± 1% +2.98% (p=0.000 n=17+19)
FmtFprintfPrefixedInt-12 235ns ± 1% 239ns ± 1% +1.99% (p=0.000 n=16+19)
FmtFprintfFloat-12 315ns ± 0% 315ns ± 1% ~ (p=0.250 n=17+19)
FmtManyArgs-12 1.04µs ± 1% 1.05µs ± 0% +0.87% (p=0.000 n=17+19)
GobDecode-12 7.93ms ± 0% 7.85ms ± 1% -1.03% (p=0.000 n=16+18)
GobEncode-12 6.62ms ± 1% 6.58ms ± 1% -0.60% (p=0.000 n=18+19)
Gzip-12 322ms ± 1% 320ms ± 1% -0.46% (p=0.009 n=20+20)
Gunzip-12 42.5ms ± 1% 42.5ms ± 0% ~ (p=0.751 n=19+19)
HTTPClientServer-12 69.7µs ± 1% 70.0µs ± 2% ~ (p=0.056 n=19+19)
JSONEncode-12 16.9ms ± 1% 16.7ms ± 1% -1.13% (p=0.000 n=19+19)
JSONDecode-12 61.5ms ± 1% 61.3ms ± 1% -0.35% (p=0.001 n=20+17)
Mandelbrot200-12 3.94ms ± 0% 3.91ms ± 0% -0.67% (p=0.000 n=20+18)
GoParse-12 3.71ms ± 1% 3.70ms ± 1% ~ (p=0.244 n=17+19)
RegexpMatchEasy0_32-12 101ns ± 1% 102ns ± 2% +0.54% (p=0.037 n=19+20)
RegexpMatchEasy0_1K-12 349ns ± 0% 350ns ± 0% +0.33% (p=0.000 n=17+18)
RegexpMatchEasy1_32-12 84.5ns ± 2% 84.2ns ± 1% -0.43% (p=0.048 n=19+20)
RegexpMatchEasy1_1K-12 510ns ± 1% 513ns ± 2% +0.58% (p=0.002 n=18+20)
RegexpMatchMedium_32-12 132ns ± 1% 134ns ± 1% +0.95% (p=0.000 n=20+20)
RegexpMatchMedium_1K-12 40.1µs ± 1% 39.6µs ± 1% -1.39% (p=0.000 n=20+20)
RegexpMatchHard_32-12 2.08µs ± 0% 2.06µs ± 1% -0.95% (p=0.000 n=18+18)
RegexpMatchHard_1K-12 62.2µs ± 1% 61.9µs ± 1% -0.42% (p=0.001 n=19+20)
Revcomp-12 537ms ± 0% 536ms ± 0% ~ (p=0.076 n=20+20)
Template-12 71.3ms ± 1% 69.3ms ± 1% -2.75% (p=0.000 n=20+20)
TimeParse-12 361ns ± 0% 360ns ± 1% ~ (p=0.056 n=19+19)
TimeFormat-12 353ns ± 0% 352ns ± 0% -0.23% (p=0.000 n=17+18)
[Geo mean] 62.6µs 62.5µs -0.17%
Change-Id: I0fbbbe4d7d99653ba5600ffb4394fa03558bc4e9
Reviewed-on: https://go-review.googlesource.com/19107
Reviewed-by: Rick Hudson <rlh@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-02-01 14:06:51 -05:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// if it has local work, start it straight away
|
2021-02-09 15:48:41 -05:00
|
|
|
if !runqempty(pp) || sched.runqsize != 0 {
|
|
|
|
|
startm(pp, false)
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
// if there's trace work to do, start it straight away
|
2022-07-21 14:54:34 -04:00
|
|
|
if (trace.enabled || trace.shutdown) && traceReaderAvailable() != nil {
|
2021-02-09 15:48:41 -05:00
|
|
|
startm(pp, false)
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
return
|
|
|
|
|
}
|
runtime: start an M when handing off a P when there's GC work
Currently it's possible for the scheduler to deadlock with the right
confluence of locked Gs, assists, and scheduling of background mark
workers. Broadly, this happens because handoffp is stricter than
findrunnable, and if the only work for a P is GC work, handoffp will
put the P into idle, rather than starting an M to execute that P. One
way this can happen is as follows:
0. There is only one user G, which we'll call G 1. There is more than
one P, but they're all idle except the one running G 1.
1. G 1 locks itself to an M using runtime.LockOSThread.
2. GC starts up and enters mark 1.
3. G 1 performs a GC assist, which completes mark 1 without being
fully satisfied. Completing mark 1 causes all background mark
workers to park. And since the assist isn't fully satisfied, it
parks as well, waiting for a background mark worker to satisfy its
remaining assist debt.
4. The assist park enters the scheduler. Since G 1 is locked to the M,
the scheduler releases the P and calls handoffp to hand the P to
another M.
5. handoffp checks the local and global run queues, which are empty,
and sees that there are idle Ps, so rather than start an M, it puts
the P into idle.
At this point, all of the Gs are waiting and all of the Ps are idle.
In particular, none of the GC workers are running, so no mark work
gets done and the assist on the main G is never satisfied, so the
whole process soft locks up.
Fix this by making handoffp start an M if there is GC work. This
reintroduces a key invariant: that in any situation where findrunnable
would return a G to run on a P, handoffp for that P will start an M to
run work on that P.
Fixes #13645.
Tested by running 2,689 iterations of `go tool dist test -no-rebuild
runtime:cpu124` across 10 linux-amd64-noopt VMs with no failures.
Without this change, the failure rate was somewhere around 1%.
Performance change is negligible.
name old time/op new time/op delta
XBenchGarbage-12 2.48ms ± 2% 2.48ms ± 1% -0.24% (p=0.000 n=92+93)
name old time/op new time/op delta
BinaryTree17-12 2.86s ± 2% 2.87s ± 2% ~ (p=0.667 n=19+20)
Fannkuch11-12 2.52s ± 1% 2.47s ± 1% -2.05% (p=0.000 n=18+20)
FmtFprintfEmpty-12 51.7ns ± 1% 51.5ns ± 3% ~ (p=0.931 n=16+20)
FmtFprintfString-12 170ns ± 1% 168ns ± 1% -0.65% (p=0.000 n=19+19)
FmtFprintfInt-12 160ns ± 0% 160ns ± 0% +0.18% (p=0.033 n=17+19)
FmtFprintfIntInt-12 265ns ± 1% 273ns ± 1% +2.98% (p=0.000 n=17+19)
FmtFprintfPrefixedInt-12 235ns ± 1% 239ns ± 1% +1.99% (p=0.000 n=16+19)
FmtFprintfFloat-12 315ns ± 0% 315ns ± 1% ~ (p=0.250 n=17+19)
FmtManyArgs-12 1.04µs ± 1% 1.05µs ± 0% +0.87% (p=0.000 n=17+19)
GobDecode-12 7.93ms ± 0% 7.85ms ± 1% -1.03% (p=0.000 n=16+18)
GobEncode-12 6.62ms ± 1% 6.58ms ± 1% -0.60% (p=0.000 n=18+19)
Gzip-12 322ms ± 1% 320ms ± 1% -0.46% (p=0.009 n=20+20)
Gunzip-12 42.5ms ± 1% 42.5ms ± 0% ~ (p=0.751 n=19+19)
HTTPClientServer-12 69.7µs ± 1% 70.0µs ± 2% ~ (p=0.056 n=19+19)
JSONEncode-12 16.9ms ± 1% 16.7ms ± 1% -1.13% (p=0.000 n=19+19)
JSONDecode-12 61.5ms ± 1% 61.3ms ± 1% -0.35% (p=0.001 n=20+17)
Mandelbrot200-12 3.94ms ± 0% 3.91ms ± 0% -0.67% (p=0.000 n=20+18)
GoParse-12 3.71ms ± 1% 3.70ms ± 1% ~ (p=0.244 n=17+19)
RegexpMatchEasy0_32-12 101ns ± 1% 102ns ± 2% +0.54% (p=0.037 n=19+20)
RegexpMatchEasy0_1K-12 349ns ± 0% 350ns ± 0% +0.33% (p=0.000 n=17+18)
RegexpMatchEasy1_32-12 84.5ns ± 2% 84.2ns ± 1% -0.43% (p=0.048 n=19+20)
RegexpMatchEasy1_1K-12 510ns ± 1% 513ns ± 2% +0.58% (p=0.002 n=18+20)
RegexpMatchMedium_32-12 132ns ± 1% 134ns ± 1% +0.95% (p=0.000 n=20+20)
RegexpMatchMedium_1K-12 40.1µs ± 1% 39.6µs ± 1% -1.39% (p=0.000 n=20+20)
RegexpMatchHard_32-12 2.08µs ± 0% 2.06µs ± 1% -0.95% (p=0.000 n=18+18)
RegexpMatchHard_1K-12 62.2µs ± 1% 61.9µs ± 1% -0.42% (p=0.001 n=19+20)
Revcomp-12 537ms ± 0% 536ms ± 0% ~ (p=0.076 n=20+20)
Template-12 71.3ms ± 1% 69.3ms ± 1% -2.75% (p=0.000 n=20+20)
TimeParse-12 361ns ± 0% 360ns ± 1% ~ (p=0.056 n=19+19)
TimeFormat-12 353ns ± 0% 352ns ± 0% -0.23% (p=0.000 n=17+18)
[Geo mean] 62.6µs 62.5µs -0.17%
Change-Id: I0fbbbe4d7d99653ba5600ffb4394fa03558bc4e9
Reviewed-on: https://go-review.googlesource.com/19107
Reviewed-by: Rick Hudson <rlh@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-02-01 14:06:51 -05:00
|
|
|
// if it has GC work, start it straight away
|
2021-02-09 15:48:41 -05:00
|
|
|
if gcBlackenEnabled != 0 && gcMarkWorkAvailable(pp) {
|
|
|
|
|
startm(pp, false)
|
runtime: start an M when handing off a P when there's GC work
Currently it's possible for the scheduler to deadlock with the right
confluence of locked Gs, assists, and scheduling of background mark
workers. Broadly, this happens because handoffp is stricter than
findrunnable, and if the only work for a P is GC work, handoffp will
put the P into idle, rather than starting an M to execute that P. One
way this can happen is as follows:
0. There is only one user G, which we'll call G 1. There is more than
one P, but they're all idle except the one running G 1.
1. G 1 locks itself to an M using runtime.LockOSThread.
2. GC starts up and enters mark 1.
3. G 1 performs a GC assist, which completes mark 1 without being
fully satisfied. Completing mark 1 causes all background mark
workers to park. And since the assist isn't fully satisfied, it
parks as well, waiting for a background mark worker to satisfy its
remaining assist debt.
4. The assist park enters the scheduler. Since G 1 is locked to the M,
the scheduler releases the P and calls handoffp to hand the P to
another M.
5. handoffp checks the local and global run queues, which are empty,
and sees that there are idle Ps, so rather than start an M, it puts
the P into idle.
At this point, all of the Gs are waiting and all of the Ps are idle.
In particular, none of the GC workers are running, so no mark work
gets done and the assist on the main G is never satisfied, so the
whole process soft locks up.
Fix this by making handoffp start an M if there is GC work. This
reintroduces a key invariant: that in any situation where findrunnable
would return a G to run on a P, handoffp for that P will start an M to
run work on that P.
Fixes #13645.
Tested by running 2,689 iterations of `go tool dist test -no-rebuild
runtime:cpu124` across 10 linux-amd64-noopt VMs with no failures.
Without this change, the failure rate was somewhere around 1%.
Performance change is negligible.
name old time/op new time/op delta
XBenchGarbage-12 2.48ms ± 2% 2.48ms ± 1% -0.24% (p=0.000 n=92+93)
name old time/op new time/op delta
BinaryTree17-12 2.86s ± 2% 2.87s ± 2% ~ (p=0.667 n=19+20)
Fannkuch11-12 2.52s ± 1% 2.47s ± 1% -2.05% (p=0.000 n=18+20)
FmtFprintfEmpty-12 51.7ns ± 1% 51.5ns ± 3% ~ (p=0.931 n=16+20)
FmtFprintfString-12 170ns ± 1% 168ns ± 1% -0.65% (p=0.000 n=19+19)
FmtFprintfInt-12 160ns ± 0% 160ns ± 0% +0.18% (p=0.033 n=17+19)
FmtFprintfIntInt-12 265ns ± 1% 273ns ± 1% +2.98% (p=0.000 n=17+19)
FmtFprintfPrefixedInt-12 235ns ± 1% 239ns ± 1% +1.99% (p=0.000 n=16+19)
FmtFprintfFloat-12 315ns ± 0% 315ns ± 1% ~ (p=0.250 n=17+19)
FmtManyArgs-12 1.04µs ± 1% 1.05µs ± 0% +0.87% (p=0.000 n=17+19)
GobDecode-12 7.93ms ± 0% 7.85ms ± 1% -1.03% (p=0.000 n=16+18)
GobEncode-12 6.62ms ± 1% 6.58ms ± 1% -0.60% (p=0.000 n=18+19)
Gzip-12 322ms ± 1% 320ms ± 1% -0.46% (p=0.009 n=20+20)
Gunzip-12 42.5ms ± 1% 42.5ms ± 0% ~ (p=0.751 n=19+19)
HTTPClientServer-12 69.7µs ± 1% 70.0µs ± 2% ~ (p=0.056 n=19+19)
JSONEncode-12 16.9ms ± 1% 16.7ms ± 1% -1.13% (p=0.000 n=19+19)
JSONDecode-12 61.5ms ± 1% 61.3ms ± 1% -0.35% (p=0.001 n=20+17)
Mandelbrot200-12 3.94ms ± 0% 3.91ms ± 0% -0.67% (p=0.000 n=20+18)
GoParse-12 3.71ms ± 1% 3.70ms ± 1% ~ (p=0.244 n=17+19)
RegexpMatchEasy0_32-12 101ns ± 1% 102ns ± 2% +0.54% (p=0.037 n=19+20)
RegexpMatchEasy0_1K-12 349ns ± 0% 350ns ± 0% +0.33% (p=0.000 n=17+18)
RegexpMatchEasy1_32-12 84.5ns ± 2% 84.2ns ± 1% -0.43% (p=0.048 n=19+20)
RegexpMatchEasy1_1K-12 510ns ± 1% 513ns ± 2% +0.58% (p=0.002 n=18+20)
RegexpMatchMedium_32-12 132ns ± 1% 134ns ± 1% +0.95% (p=0.000 n=20+20)
RegexpMatchMedium_1K-12 40.1µs ± 1% 39.6µs ± 1% -1.39% (p=0.000 n=20+20)
RegexpMatchHard_32-12 2.08µs ± 0% 2.06µs ± 1% -0.95% (p=0.000 n=18+18)
RegexpMatchHard_1K-12 62.2µs ± 1% 61.9µs ± 1% -0.42% (p=0.001 n=19+20)
Revcomp-12 537ms ± 0% 536ms ± 0% ~ (p=0.076 n=20+20)
Template-12 71.3ms ± 1% 69.3ms ± 1% -2.75% (p=0.000 n=20+20)
TimeParse-12 361ns ± 0% 360ns ± 1% ~ (p=0.056 n=19+19)
TimeFormat-12 353ns ± 0% 352ns ± 0% -0.23% (p=0.000 n=17+18)
[Geo mean] 62.6µs 62.5µs -0.17%
Change-Id: I0fbbbe4d7d99653ba5600ffb4394fa03558bc4e9
Reviewed-on: https://go-review.googlesource.com/19107
Reviewed-by: Rick Hudson <rlh@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-02-01 14:06:51 -05:00
|
|
|
return
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
// no local work, check that there are no spinning/idle M's,
|
|
|
|
|
// otherwise our help is not required
|
2022-07-25 15:20:22 -04:00
|
|
|
if sched.nmspinning.Load()+sched.npidle.Load() == 0 && sched.nmspinning.CompareAndSwap(0, 1) { // TODO: fast atomic
|
2022-03-01 15:06:37 -05:00
|
|
|
sched.needspinning.Store(0)
|
2021-02-09 15:48:41 -05:00
|
|
|
startm(pp, true)
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
lock(&sched.lock)
|
2022-07-25 15:31:03 -04:00
|
|
|
if sched.gcwaiting.Load() {
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.status = _Pgcstop
|
2015-10-18 17:04:05 -07:00
|
|
|
sched.stopwait--
|
|
|
|
|
if sched.stopwait == 0 {
|
|
|
|
|
notewakeup(&sched.stopnote)
|
|
|
|
|
}
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
return
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp.runSafePointFn != 0 && atomic.Cas(&pp.runSafePointFn, 1, 0) {
|
|
|
|
|
sched.safePointFn(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
sched.safePointWait--
|
|
|
|
|
if sched.safePointWait == 0 {
|
|
|
|
|
notewakeup(&sched.safePointNote)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if sched.runqsize != 0 {
|
|
|
|
|
unlock(&sched.lock)
|
2021-02-09 15:48:41 -05:00
|
|
|
startm(pp, false)
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// If this is the last running P and nobody is polling network,
|
|
|
|
|
// need to wakeup another M to poll network.
|
2022-07-20 18:01:31 -04:00
|
|
|
if sched.npidle.Load() == gomaxprocs-1 && sched.lastpoll.Load() != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
unlock(&sched.lock)
|
2021-02-09 15:48:41 -05:00
|
|
|
startm(pp, false)
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
2020-05-01 17:04:36 -04:00
|
|
|
|
|
|
|
|
// The scheduler lock cannot be held when calling wakeNetPoller below
|
|
|
|
|
// because wakeNetPoller may call wakep which may call startm.
|
2021-02-09 15:48:41 -05:00
|
|
|
when := nobarrierWakeTime(pp)
|
|
|
|
|
pidleput(pp, 0)
|
2015-10-18 17:04:05 -07:00
|
|
|
unlock(&sched.lock)
|
2020-05-01 17:04:36 -04:00
|
|
|
|
|
|
|
|
if when != 0 {
|
|
|
|
|
wakeNetPoller(when)
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tries to add one more P to execute G's.
|
|
|
|
|
// Called when a G is made runnable (newproc, ready).
|
2022-03-01 15:06:37 -05:00
|
|
|
// Must be called with a P.
|
2015-10-18 17:04:05 -07:00
|
|
|
func wakep() {
|
2022-03-01 15:06:37 -05:00
|
|
|
// Be conservative about spinning threads, only start one if none exist
|
|
|
|
|
// already.
|
|
|
|
|
if sched.nmspinning.Load() != 0 || !sched.nmspinning.CompareAndSwap(0, 1) {
|
2020-04-28 20:54:31 -04:00
|
|
|
return
|
|
|
|
|
}
|
2022-03-01 15:06:37 -05:00
|
|
|
|
|
|
|
|
// Disable preemption until ownership of pp transfers to the next M in
|
|
|
|
|
// startm. Otherwise preemption here would leave pp stuck waiting to
|
|
|
|
|
// enter _Pgcstop.
|
|
|
|
|
//
|
|
|
|
|
// See preemption comment on acquirem in startm for more details.
|
|
|
|
|
mp := acquirem()
|
|
|
|
|
|
|
|
|
|
var pp *p
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
pp, _ = pidlegetSpinning(0)
|
|
|
|
|
if pp == nil {
|
|
|
|
|
if sched.nmspinning.Add(-1) < 0 {
|
|
|
|
|
throw("wakep: negative nmspinning")
|
|
|
|
|
}
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
releasem(mp)
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
2022-03-01 15:06:37 -05:00
|
|
|
// Since we always have a P, the race in the "No M is available"
|
|
|
|
|
// comment in startm doesn't apply during the small window between the
|
|
|
|
|
// unlock here and lock in startm. A checkdead in between will always
|
|
|
|
|
// see at least one running M (ours).
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
|
|
|
|
startm(pp, true)
|
|
|
|
|
|
|
|
|
|
releasem(mp)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stops execution of the current m that is locked to a g until the g is runnable again.
|
|
|
|
|
// Returns with acquired P.
|
|
|
|
|
func stoplockedm() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.lockedg == 0 || gp.m.lockedg.ptr().lockedm.ptr() != gp.m {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("stoplockedm: inconsistent locking")
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.p != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
// Schedule another M to run this p.
|
2021-02-09 15:48:41 -05:00
|
|
|
pp := releasep()
|
|
|
|
|
handoffp(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
incidlelocked(1)
|
|
|
|
|
// Wait until another thread schedules lockedg again.
|
syscall: support POSIX semantics for Linux syscalls
This change adds two new methods for invoking system calls
under Linux: syscall.AllThreadsSyscall() and
syscall.AllThreadsSyscall6().
These system call wrappers ensure that all OSThreads mirror
a common system call. The wrappers serialize execution of the
runtime to ensure no race conditions where any Go code observes
a non-atomic OS state change. As such, the syscalls have
higher runtime overhead than regular system calls, and only
need to be used where such thread (or 'm' in the parlance
of the runtime sources) consistency is required.
The new support is used to enable these functions under Linux:
syscall.Setegid(), syscall.Seteuid(), syscall.Setgroups(),
syscall.Setgid(), syscall.Setregid(), syscall.Setreuid(),
syscall.Setresgid(), syscall.Setresuid() and syscall.Setuid().
They work identically to their glibc counterparts.
Extensive discussion of the background issue addressed in this
patch can be found here:
https://github.com/golang/go/issues/1435
In the case where cgo is used, the C runtime can launch pthreads that
are not managed by the Go runtime. As such, the added
syscall.AllThreadsSyscall*() return ENOTSUP when cgo is enabled.
However, for the 9 syscall.Set*() functions listed above, when cgo is
active, these functions redirect to invoke their C.set*() equivalents
in glibc, which wraps the raw system calls with a nptl:setxid fixup
mechanism. This achieves POSIX semantics for these functions in the
combined Go and C runtime.
As a side note, the glibc/nptl:setxid support (2019-11-30) does not
extend to all security related system calls under Linux so using
native Go (CGO_ENABLED=0) and these AllThreadsSyscall*()s, where
needed, will yield more well defined/consistent behavior over all
threads of a Go program. That is, using the
syscall.AllThreadsSyscall*() wrappers for things like setting state
through SYS_PRCTL and SYS_CAPSET etc.
Fixes #1435
Change-Id: Ib1a3e16b9180f64223196a32fc0f9dce14d9105c
Reviewed-on: https://go-review.googlesource.com/c/go/+/210639
Trust: Emmanuel Odeke <emm.odeke@gmail.com>
Trust: Ian Lance Taylor <iant@golang.org>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Austin Clements <austin@google.com>
2019-12-09 21:50:16 -08:00
|
|
|
mPark()
|
2021-02-11 11:15:53 -05:00
|
|
|
status := readgstatus(gp.m.lockedg.ptr())
|
2015-10-18 17:04:05 -07:00
|
|
|
if status&^_Gscan != _Grunnable {
|
2020-10-15 01:43:51 +00:00
|
|
|
print("runtime:stoplockedm: lockedg (atomicstatus=", status, ") is not Grunnable or Gscanrunnable\n")
|
2021-02-11 11:15:53 -05:00
|
|
|
dumpgstatus(gp.m.lockedg.ptr())
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("stoplockedm: not runnable")
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
acquirep(gp.m.nextp.ptr())
|
|
|
|
|
gp.m.nextp = 0
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Schedules the locked m to run the locked gp.
|
|
|
|
|
// May run during STW, so write barriers are not allowed.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2015-10-18 17:04:05 -07:00
|
|
|
func startlockedm(gp *g) {
|
2017-09-13 10:14:02 -07:00
|
|
|
mp := gp.lockedm.ptr()
|
2021-02-10 12:46:09 -05:00
|
|
|
if mp == getg().m {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("startlockedm: locked to me")
|
|
|
|
|
}
|
|
|
|
|
if mp.nextp != 0 {
|
|
|
|
|
throw("startlockedm: m has p")
|
|
|
|
|
}
|
|
|
|
|
// directly handoff current P to the locked m
|
|
|
|
|
incidlelocked(-1)
|
2021-02-09 15:48:41 -05:00
|
|
|
pp := releasep()
|
|
|
|
|
mp.nextp.set(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
notewakeup(&mp.park)
|
|
|
|
|
stopm()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stops the current m for stopTheWorld.
|
|
|
|
|
// Returns when the world is restarted.
|
|
|
|
|
func gcstopm() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2022-07-25 15:31:03 -04:00
|
|
|
if !sched.gcwaiting.Load() {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("gcstopm: not waiting for gc")
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.spinning {
|
|
|
|
|
gp.m.spinning = false
|
2015-12-08 15:11:27 +01:00
|
|
|
// OK to just drop nmspinning here,
|
|
|
|
|
// startTheWorld will unpark threads as necessary.
|
2022-07-25 15:20:22 -04:00
|
|
|
if sched.nmspinning.Add(-1) < 0 {
|
2015-12-08 15:11:27 +01:00
|
|
|
throw("gcstopm: negative nmspinning")
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
pp := releasep()
|
2015-10-18 17:04:05 -07:00
|
|
|
lock(&sched.lock)
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.status = _Pgcstop
|
2015-10-18 17:04:05 -07:00
|
|
|
sched.stopwait--
|
|
|
|
|
if sched.stopwait == 0 {
|
|
|
|
|
notewakeup(&sched.stopnote)
|
|
|
|
|
}
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
stopm()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Schedules gp to run on the current M.
|
|
|
|
|
// If inheritTime is true, gp inherits the remaining time in the
|
|
|
|
|
// current time slice. Otherwise, it starts a new time slice.
|
|
|
|
|
// Never returns.
|
2016-10-10 17:14:14 -04:00
|
|
|
//
|
|
|
|
|
// Write barriers are allowed because this is called immediately after
|
|
|
|
|
// acquiring a P in several places.
|
|
|
|
|
//
|
|
|
|
|
//go:yeswritebarrierrec
|
2015-10-18 17:04:05 -07:00
|
|
|
func execute(gp *g, inheritTime bool) {
|
2021-02-10 12:46:09 -05:00
|
|
|
mp := getg().m
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2022-02-18 10:56:16 -08:00
|
|
|
if goroutineProfile.active {
|
|
|
|
|
// Make sure that gp has had its stack written out to the goroutine
|
|
|
|
|
// profile, exactly as it was when the goroutine profiler first stopped
|
|
|
|
|
// the world.
|
|
|
|
|
tryRecordGoroutineProfile(gp, osyield)
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-15 21:46:43 -04:00
|
|
|
// Assign gp.m before entering _Grunning so running Gs have an
|
|
|
|
|
// M.
|
2021-02-10 12:46:09 -05:00
|
|
|
mp.curg = gp
|
|
|
|
|
gp.m = mp
|
2015-10-18 17:04:05 -07:00
|
|
|
casgstatus(gp, _Grunnable, _Grunning)
|
|
|
|
|
gp.waitsince = 0
|
|
|
|
|
gp.preempt = false
|
|
|
|
|
gp.stackguard0 = gp.stack.lo + _StackGuard
|
|
|
|
|
if !inheritTime {
|
2021-02-10 12:46:09 -05:00
|
|
|
mp.p.ptr().schedtick++
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check whether the profiler needs to be turned on or off.
|
|
|
|
|
hz := sched.profilehz
|
2021-02-10 12:46:09 -05:00
|
|
|
if mp.profilehz != hz {
|
2016-12-06 20:54:41 -08:00
|
|
|
setThreadCPUProfiler(hz)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
// GoSysExit has to happen when we have a P, but before GoStart.
|
|
|
|
|
// So we emit it here.
|
|
|
|
|
if gp.syscallsp != 0 && gp.sysblocktraced {
|
2016-04-05 15:29:14 +02:00
|
|
|
traceGoSysExit(gp.sysexitticks)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
traceGoStart()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gogo(&gp.sched)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Finds a runnable goroutine to execute.
|
2018-08-31 07:16:37 +00:00
|
|
|
// Tries to steal from other P's, get g from local or global queue, poll network.
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
// tryWakeP indicates that the returned goroutine is not normal (GC worker, trace
|
|
|
|
|
// reader) so the caller should try to wake a P.
|
|
|
|
|
func findRunnable() (gp *g, inheritTime, tryWakeP bool) {
|
2021-02-10 12:46:09 -05:00
|
|
|
mp := getg().m
|
2015-10-18 17:04:05 -07:00
|
|
|
|
runtime: start an M when handing off a P when there's GC work
Currently it's possible for the scheduler to deadlock with the right
confluence of locked Gs, assists, and scheduling of background mark
workers. Broadly, this happens because handoffp is stricter than
findrunnable, and if the only work for a P is GC work, handoffp will
put the P into idle, rather than starting an M to execute that P. One
way this can happen is as follows:
0. There is only one user G, which we'll call G 1. There is more than
one P, but they're all idle except the one running G 1.
1. G 1 locks itself to an M using runtime.LockOSThread.
2. GC starts up and enters mark 1.
3. G 1 performs a GC assist, which completes mark 1 without being
fully satisfied. Completing mark 1 causes all background mark
workers to park. And since the assist isn't fully satisfied, it
parks as well, waiting for a background mark worker to satisfy its
remaining assist debt.
4. The assist park enters the scheduler. Since G 1 is locked to the M,
the scheduler releases the P and calls handoffp to hand the P to
another M.
5. handoffp checks the local and global run queues, which are empty,
and sees that there are idle Ps, so rather than start an M, it puts
the P into idle.
At this point, all of the Gs are waiting and all of the Ps are idle.
In particular, none of the GC workers are running, so no mark work
gets done and the assist on the main G is never satisfied, so the
whole process soft locks up.
Fix this by making handoffp start an M if there is GC work. This
reintroduces a key invariant: that in any situation where findrunnable
would return a G to run on a P, handoffp for that P will start an M to
run work on that P.
Fixes #13645.
Tested by running 2,689 iterations of `go tool dist test -no-rebuild
runtime:cpu124` across 10 linux-amd64-noopt VMs with no failures.
Without this change, the failure rate was somewhere around 1%.
Performance change is negligible.
name old time/op new time/op delta
XBenchGarbage-12 2.48ms ± 2% 2.48ms ± 1% -0.24% (p=0.000 n=92+93)
name old time/op new time/op delta
BinaryTree17-12 2.86s ± 2% 2.87s ± 2% ~ (p=0.667 n=19+20)
Fannkuch11-12 2.52s ± 1% 2.47s ± 1% -2.05% (p=0.000 n=18+20)
FmtFprintfEmpty-12 51.7ns ± 1% 51.5ns ± 3% ~ (p=0.931 n=16+20)
FmtFprintfString-12 170ns ± 1% 168ns ± 1% -0.65% (p=0.000 n=19+19)
FmtFprintfInt-12 160ns ± 0% 160ns ± 0% +0.18% (p=0.033 n=17+19)
FmtFprintfIntInt-12 265ns ± 1% 273ns ± 1% +2.98% (p=0.000 n=17+19)
FmtFprintfPrefixedInt-12 235ns ± 1% 239ns ± 1% +1.99% (p=0.000 n=16+19)
FmtFprintfFloat-12 315ns ± 0% 315ns ± 1% ~ (p=0.250 n=17+19)
FmtManyArgs-12 1.04µs ± 1% 1.05µs ± 0% +0.87% (p=0.000 n=17+19)
GobDecode-12 7.93ms ± 0% 7.85ms ± 1% -1.03% (p=0.000 n=16+18)
GobEncode-12 6.62ms ± 1% 6.58ms ± 1% -0.60% (p=0.000 n=18+19)
Gzip-12 322ms ± 1% 320ms ± 1% -0.46% (p=0.009 n=20+20)
Gunzip-12 42.5ms ± 1% 42.5ms ± 0% ~ (p=0.751 n=19+19)
HTTPClientServer-12 69.7µs ± 1% 70.0µs ± 2% ~ (p=0.056 n=19+19)
JSONEncode-12 16.9ms ± 1% 16.7ms ± 1% -1.13% (p=0.000 n=19+19)
JSONDecode-12 61.5ms ± 1% 61.3ms ± 1% -0.35% (p=0.001 n=20+17)
Mandelbrot200-12 3.94ms ± 0% 3.91ms ± 0% -0.67% (p=0.000 n=20+18)
GoParse-12 3.71ms ± 1% 3.70ms ± 1% ~ (p=0.244 n=17+19)
RegexpMatchEasy0_32-12 101ns ± 1% 102ns ± 2% +0.54% (p=0.037 n=19+20)
RegexpMatchEasy0_1K-12 349ns ± 0% 350ns ± 0% +0.33% (p=0.000 n=17+18)
RegexpMatchEasy1_32-12 84.5ns ± 2% 84.2ns ± 1% -0.43% (p=0.048 n=19+20)
RegexpMatchEasy1_1K-12 510ns ± 1% 513ns ± 2% +0.58% (p=0.002 n=18+20)
RegexpMatchMedium_32-12 132ns ± 1% 134ns ± 1% +0.95% (p=0.000 n=20+20)
RegexpMatchMedium_1K-12 40.1µs ± 1% 39.6µs ± 1% -1.39% (p=0.000 n=20+20)
RegexpMatchHard_32-12 2.08µs ± 0% 2.06µs ± 1% -0.95% (p=0.000 n=18+18)
RegexpMatchHard_1K-12 62.2µs ± 1% 61.9µs ± 1% -0.42% (p=0.001 n=19+20)
Revcomp-12 537ms ± 0% 536ms ± 0% ~ (p=0.076 n=20+20)
Template-12 71.3ms ± 1% 69.3ms ± 1% -2.75% (p=0.000 n=20+20)
TimeParse-12 361ns ± 0% 360ns ± 1% ~ (p=0.056 n=19+19)
TimeFormat-12 353ns ± 0% 352ns ± 0% -0.23% (p=0.000 n=17+18)
[Geo mean] 62.6µs 62.5µs -0.17%
Change-Id: I0fbbbe4d7d99653ba5600ffb4394fa03558bc4e9
Reviewed-on: https://go-review.googlesource.com/19107
Reviewed-by: Rick Hudson <rlh@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-02-01 14:06:51 -05:00
|
|
|
// The conditions here and in handoffp must agree: if
|
|
|
|
|
// findrunnable would return a G to run, handoffp must start
|
|
|
|
|
// an M.
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
top:
|
2021-02-10 12:46:09 -05:00
|
|
|
pp := mp.p.ptr()
|
2022-07-25 15:31:03 -04:00
|
|
|
if sched.gcwaiting.Load() {
|
2015-10-18 17:04:05 -07:00
|
|
|
gcstopm()
|
|
|
|
|
goto top
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp.runSafePointFn != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
runSafePointFn()
|
|
|
|
|
}
|
2019-04-05 16:24:14 -07:00
|
|
|
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
// now and pollUntil are saved for work stealing later,
|
|
|
|
|
// which may steal timers. It's important that between now
|
|
|
|
|
// and then, nothing blocks, so these numbers remain mostly
|
|
|
|
|
// relevant.
|
2021-02-09 15:48:41 -05:00
|
|
|
now, pollUntil, _ := checkTimers(pp, 0)
|
2019-04-05 16:24:14 -07:00
|
|
|
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
// Try to schedule the trace reader.
|
|
|
|
|
if trace.enabled || trace.shutdown {
|
2022-05-21 00:20:47 +08:00
|
|
|
gp := traceReader()
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
if gp != nil {
|
|
|
|
|
casgstatus(gp, _Gwaiting, _Grunnable)
|
|
|
|
|
traceGoUnpark(gp, 0)
|
|
|
|
|
return gp, false, true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to schedule a GC worker.
|
|
|
|
|
if gcBlackenEnabled != 0 {
|
2022-05-21 00:20:47 +08:00
|
|
|
gp, tnow := gcController.findRunnableGCWorker(pp, now)
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
if gp != nil {
|
|
|
|
|
return gp, false, true
|
|
|
|
|
}
|
2022-05-21 00:20:47 +08:00
|
|
|
now = tnow
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check the global runnable queue once in a while to ensure fairness.
|
|
|
|
|
// Otherwise two goroutines can completely occupy the local runqueue
|
|
|
|
|
// by constantly respawning each other.
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp.schedtick%61 == 0 && sched.runqsize > 0 {
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
lock(&sched.lock)
|
2022-05-21 00:20:47 +08:00
|
|
|
gp := globrunqget(pp, 1)
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
unlock(&sched.lock)
|
|
|
|
|
if gp != nil {
|
|
|
|
|
return gp, false, false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wake up the finalizer G.
|
2022-04-13 21:14:22 +08:00
|
|
|
if fingStatus.Load()&(fingWait|fingWake) == fingWait|fingWake {
|
2015-10-18 17:04:05 -07:00
|
|
|
if gp := wakefing(); gp != nil {
|
2016-05-17 18:21:54 -04:00
|
|
|
ready(gp, 0, true)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
2017-03-23 22:47:56 -04:00
|
|
|
if *cgo_yield != nil {
|
|
|
|
|
asmcgocall(*cgo_yield, nil)
|
2017-01-19 16:09:10 -05:00
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// local runq
|
2021-02-09 15:48:41 -05:00
|
|
|
if gp, inheritTime := runqget(pp); gp != nil {
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
return gp, inheritTime, false
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// global runq
|
|
|
|
|
if sched.runqsize != 0 {
|
|
|
|
|
lock(&sched.lock)
|
2021-02-09 15:48:41 -05:00
|
|
|
gp := globrunqget(pp, 0)
|
2015-10-18 17:04:05 -07:00
|
|
|
unlock(&sched.lock)
|
|
|
|
|
if gp != nil {
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
return gp, false, false
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Poll network.
|
|
|
|
|
// This netpoll is only an optimization before we resort to stealing.
|
2017-11-20 11:01:00 -08:00
|
|
|
// We can safely skip it if there are no waiters or a thread is blocked
|
|
|
|
|
// in netpoll already. If there is any kind of logical race with that
|
|
|
|
|
// blocked thread (e.g. it has already returned from netpoll, but does
|
|
|
|
|
// not set lastpoll yet), this thread will do blocking netpoll below
|
|
|
|
|
// anyway.
|
2022-08-25 02:26:08 +08:00
|
|
|
if netpollinited() && netpollWaiters.Load() > 0 && sched.lastpoll.Load() != 0 {
|
2019-04-02 20:27:35 -07:00
|
|
|
if list := netpoll(0); !list.empty() { // non-blocking
|
2018-08-10 00:09:00 -04:00
|
|
|
gp := list.pop()
|
2018-08-10 10:33:05 -04:00
|
|
|
injectglist(&list)
|
2015-10-18 17:04:05 -07:00
|
|
|
casgstatus(gp, _Gwaiting, _Grunnable)
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoUnpark(gp, 0)
|
|
|
|
|
}
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
return gp, false, false
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-16 15:50:49 -05:00
|
|
|
// Spinning Ms: steal work from other Ps.
|
|
|
|
|
//
|
|
|
|
|
// Limit the number of spinning Ms to half the number of busy Ps.
|
|
|
|
|
// This is necessary to prevent excessive CPU consumption when
|
|
|
|
|
// GOMAXPROCS>>1 but the program parallelism is low.
|
2022-07-25 15:20:22 -04:00
|
|
|
if mp.spinning || 2*sched.nmspinning.Load() < gomaxprocs-sched.npidle.Load() {
|
2021-02-10 12:46:09 -05:00
|
|
|
if !mp.spinning {
|
2022-03-01 15:06:37 -05:00
|
|
|
mp.becomeSpinning()
|
2021-02-16 15:50:49 -05:00
|
|
|
}
|
2020-05-01 17:04:36 -04:00
|
|
|
|
2021-02-16 15:50:49 -05:00
|
|
|
gp, inheritTime, tnow, w, newWork := stealWork(now)
|
|
|
|
|
if gp != nil {
|
|
|
|
|
// Successfully stole.
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
return gp, inheritTime, false
|
2021-02-16 15:50:49 -05:00
|
|
|
}
|
|
|
|
|
if newWork {
|
|
|
|
|
// There may be new timer or GC work; restart to
|
|
|
|
|
// discover.
|
|
|
|
|
goto top
|
|
|
|
|
}
|
2022-05-21 00:20:47 +08:00
|
|
|
|
|
|
|
|
now = tnow
|
2021-02-16 15:50:49 -05:00
|
|
|
if w != 0 && (pollUntil == 0 || w < pollUntil) {
|
|
|
|
|
// Earlier timer to wait for.
|
|
|
|
|
pollUntil = w
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-16 15:50:49 -05:00
|
|
|
// We have nothing to do.
|
|
|
|
|
//
|
|
|
|
|
// If we're in the GC mark phase, can safely scan and blacken objects,
|
runtime: reduce max idle mark workers during periodic GC cycles
This change reduces the maximum number of idle mark workers during
periodic (currently every 2 minutes) GC cycles to 1.
Idle mark workers soak up all available and unused Ps, up to GOMAXPROCS.
While this provides some throughput and latency benefit in general, it
can cause what appear to be massive CPU utilization spikes in otherwise
idle applications. This is mostly an issue for *very* idle applications,
ones idle enough to trigger periodic GC cycles. This spike also tends to
interact poorly with auto-scaling systems, as the system might assume
the load average is very low and suddenly see a massive burst in
activity.
The result of this change is not to bring down this 100% (of GOMAXPROCS)
CPU utilization spike to 0%, but rather
min(25% + 1/GOMAXPROCS*100%, 100%)
Idle mark workers also do incur a small latency penalty as they must be
descheduled for other work that might pop up. Luckily the runtime is
pretty good about getting idle mark workers off of Ps, so in general
the latency benefit from shorter GC cycles outweighs this cost. But, the
cost is still non-zero and may be more significant in idle applications
that aren't invoking assists and write barriers quite as often.
We can't completely eliminate idle mark workers because they're
currently necessary for GC progress in some circumstances. Namely,
they're critical for progress when all we have is fractional workers. If
a fractional worker meets its quota, and all user goroutines are blocked
directly or indirectly on a GC cycle (via runtime.GOMAXPROCS, or
runtime.GC), the program may deadlock without GC workers, since the
fractional worker will go to sleep with nothing to wake it.
Fixes #37116.
For #44163.
Change-Id: Ib74793bb6b88d1765c52d445831310b0d11ef423
Reviewed-on: https://go-review.googlesource.com/c/go/+/393394
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-16 15:47:57 +00:00
|
|
|
// and have work to do, run idle-time marking rather than give up the P.
|
2021-02-09 15:48:41 -05:00
|
|
|
if gcBlackenEnabled != 0 && gcMarkWorkAvailable(pp) && gcController.addIdleMarkWorker() {
|
runtime: manage gcBgMarkWorkers with a global pool
Background mark workers perform per-P marking work. Currently each
worker is assigned a P at creation time. The worker "attaches" to the P
via p.gcBgMarkWorker, making itself (usually) available to
findRunnableGCWorker for scheduling GC work.
While running gcMarkDone, the worker "detaches" from the P (by clearing
p.gcBgMarkWorker), since it may park for other reasons and should not be
scheduled by findRunnableGCWorker.
Unfortunately, this design is complex and difficult to reason about. We
simplify things by changing the design to eliminate the hard P
attachment. Rather than workers always performing work from the same P,
workers perform work for whichever P they find themselves on. On park,
the workers are placed in a pool of free workers, which each P's
findRunnableGCWorker can use to run a worker for its P.
Now if a worker parks in gcMarkDone, a P may simply use another worker
from the pool to complete its own work.
The P's GC worker mode is used to communicate the mode to run to the
selected worker. It is also used to emit the appropriate worker
EvGoStart tracepoint. This is a slight change, as this G may be
preempted (e.g., in gcMarkDone). When it is rescheduled, the trace
viewer will show it as a normal goroutine again. It is currently a bit
difficult to connect to the original worker tracepoint, as the viewer
does not display the goid for the original worker (though the data is in
the trace file).
Change-Id: Id7bd3a364dc18a4d2b1c99c4dc4810fae1293c1b
Reviewed-on: https://go-review.googlesource.com/c/go/+/262348
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-13 12:39:13 -04:00
|
|
|
node := (*gcBgMarkWorkerNode)(gcBgMarkWorkerPool.pop())
|
|
|
|
|
if node != nil {
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.gcMarkWorkerMode = gcMarkWorkerIdleMode
|
runtime: manage gcBgMarkWorkers with a global pool
Background mark workers perform per-P marking work. Currently each
worker is assigned a P at creation time. The worker "attaches" to the P
via p.gcBgMarkWorker, making itself (usually) available to
findRunnableGCWorker for scheduling GC work.
While running gcMarkDone, the worker "detaches" from the P (by clearing
p.gcBgMarkWorker), since it may park for other reasons and should not be
scheduled by findRunnableGCWorker.
Unfortunately, this design is complex and difficult to reason about. We
simplify things by changing the design to eliminate the hard P
attachment. Rather than workers always performing work from the same P,
workers perform work for whichever P they find themselves on. On park,
the workers are placed in a pool of free workers, which each P's
findRunnableGCWorker can use to run a worker for its P.
Now if a worker parks in gcMarkDone, a P may simply use another worker
from the pool to complete its own work.
The P's GC worker mode is used to communicate the mode to run to the
selected worker. It is also used to emit the appropriate worker
EvGoStart tracepoint. This is a slight change, as this G may be
preempted (e.g., in gcMarkDone). When it is rescheduled, the trace
viewer will show it as a normal goroutine again. It is currently a bit
difficult to connect to the original worker tracepoint, as the viewer
does not display the goid for the original worker (though the data is in
the trace file).
Change-Id: Id7bd3a364dc18a4d2b1c99c4dc4810fae1293c1b
Reviewed-on: https://go-review.googlesource.com/c/go/+/262348
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-13 12:39:13 -04:00
|
|
|
gp := node.gp.ptr()
|
|
|
|
|
casgstatus(gp, _Gwaiting, _Grunnable)
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoUnpark(gp, 0)
|
|
|
|
|
}
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
return gp, false, false
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
runtime: reduce max idle mark workers during periodic GC cycles
This change reduces the maximum number of idle mark workers during
periodic (currently every 2 minutes) GC cycles to 1.
Idle mark workers soak up all available and unused Ps, up to GOMAXPROCS.
While this provides some throughput and latency benefit in general, it
can cause what appear to be massive CPU utilization spikes in otherwise
idle applications. This is mostly an issue for *very* idle applications,
ones idle enough to trigger periodic GC cycles. This spike also tends to
interact poorly with auto-scaling systems, as the system might assume
the load average is very low and suddenly see a massive burst in
activity.
The result of this change is not to bring down this 100% (of GOMAXPROCS)
CPU utilization spike to 0%, but rather
min(25% + 1/GOMAXPROCS*100%, 100%)
Idle mark workers also do incur a small latency penalty as they must be
descheduled for other work that might pop up. Luckily the runtime is
pretty good about getting idle mark workers off of Ps, so in general
the latency benefit from shorter GC cycles outweighs this cost. But, the
cost is still non-zero and may be more significant in idle applications
that aren't invoking assists and write barriers quite as often.
We can't completely eliminate idle mark workers because they're
currently necessary for GC progress in some circumstances. Namely,
they're critical for progress when all we have is fractional workers. If
a fractional worker meets its quota, and all user goroutines are blocked
directly or indirectly on a GC cycle (via runtime.GOMAXPROCS, or
runtime.GC), the program may deadlock without GC workers, since the
fractional worker will go to sleep with nothing to wake it.
Fixes #37116.
For #44163.
Change-Id: Ib74793bb6b88d1765c52d445831310b0d11ef423
Reviewed-on: https://go-review.googlesource.com/c/go/+/393394
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-16 15:47:57 +00:00
|
|
|
gcController.removeIdleMarkWorker()
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2018-05-20 00:56:36 +02:00
|
|
|
// wasm only:
|
2018-10-11 12:46:14 +02:00
|
|
|
// If a callback returned and no other goroutine is awake,
|
2020-04-25 18:53:53 +02:00
|
|
|
// then wake event handler goroutine which pauses execution
|
|
|
|
|
// until a callback was triggered.
|
2021-04-07 10:15:33 -04:00
|
|
|
gp, otherReady := beforeIdle(now, pollUntil)
|
2020-04-25 18:53:53 +02:00
|
|
|
if gp != nil {
|
|
|
|
|
casgstatus(gp, _Gwaiting, _Grunnable)
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoUnpark(gp, 0)
|
|
|
|
|
}
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
return gp, false, false
|
2020-04-25 18:53:53 +02:00
|
|
|
}
|
|
|
|
|
if otherReady {
|
2018-05-20 00:56:36 +02:00
|
|
|
goto top
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-04 10:43:29 -05:00
|
|
|
// Before we drop our P, make a snapshot of the allp slice,
|
|
|
|
|
// which can change underfoot once we no longer block
|
|
|
|
|
// safe-points. We don't need to snapshot the contents because
|
|
|
|
|
// everything up to cap(allp) is immutable.
|
|
|
|
|
allpSnapshot := allp
|
2020-10-29 16:03:57 -04:00
|
|
|
// Also snapshot masks. Value changes are OK, but we can't allow
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
// len to change out from under us.
|
|
|
|
|
idlepMaskSnapshot := idlepMask
|
2020-10-29 16:03:57 -04:00
|
|
|
timerpMaskSnapshot := timerpMask
|
2018-01-04 10:43:29 -05:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// return P and block
|
|
|
|
|
lock(&sched.lock)
|
2022-07-25 15:31:03 -04:00
|
|
|
if sched.gcwaiting.Load() || pp.runSafePointFn != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
unlock(&sched.lock)
|
|
|
|
|
goto top
|
|
|
|
|
}
|
|
|
|
|
if sched.runqsize != 0 {
|
2021-02-09 15:48:41 -05:00
|
|
|
gp := globrunqget(pp, 0)
|
2015-10-18 17:04:05 -07:00
|
|
|
unlock(&sched.lock)
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
return gp, false, false
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2022-03-01 15:06:37 -05:00
|
|
|
if !mp.spinning && sched.needspinning.Load() == 1 {
|
|
|
|
|
// See "Delicate dance" comment below.
|
|
|
|
|
mp.becomeSpinning()
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
goto top
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if releasep() != pp {
|
2016-03-18 12:52:52 +01:00
|
|
|
throw("findrunnable: wrong p")
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
now = pidleput(pp, now)
|
2015-10-18 17:04:05 -07:00
|
|
|
unlock(&sched.lock)
|
2015-12-08 15:11:27 +01:00
|
|
|
|
2021-02-16 10:52:38 -05:00
|
|
|
// Delicate dance: thread transitions from spinning to non-spinning
|
|
|
|
|
// state, potentially concurrently with submission of new work. We must
|
|
|
|
|
// drop nmspinning first and then check all sources again (with
|
|
|
|
|
// #StoreLoad memory barrier in between). If we do it the other way
|
|
|
|
|
// around, another thread can submit work after we've checked all
|
|
|
|
|
// sources but before we drop nmspinning; as a result nobody will
|
|
|
|
|
// unpark a thread to run the work.
|
|
|
|
|
//
|
|
|
|
|
// This applies to the following sources of work:
|
|
|
|
|
//
|
|
|
|
|
// * Goroutines added to a per-P run queue.
|
|
|
|
|
// * New/modified-earlier timers on a per-P timer heap.
|
|
|
|
|
// * Idle-priority GC work (barring golang.org/issue/19112).
|
|
|
|
|
//
|
2022-03-01 15:06:37 -05:00
|
|
|
// If we discover new work below, we need to restore m.spinning as a
|
|
|
|
|
// signal for resetspinning to unpark a new worker thread (because
|
|
|
|
|
// there can be more than one starving goroutine).
|
|
|
|
|
//
|
|
|
|
|
// However, if after discovering new work we also observe no idle Ps
|
|
|
|
|
// (either here or in resetspinning), we have a problem. We may be
|
|
|
|
|
// racing with a non-spinning M in the block above, having found no
|
|
|
|
|
// work and preparing to release its P and park. Allowing that P to go
|
|
|
|
|
// idle will result in loss of work conservation (idle P while there is
|
|
|
|
|
// runnable work). This could result in complete deadlock in the
|
|
|
|
|
// unlikely event that we discover new work (from netpoll) right as we
|
|
|
|
|
// are racing with _all_ other Ps going idle.
|
|
|
|
|
//
|
|
|
|
|
// We use sched.needspinning to synchronize with non-spinning Ms going
|
|
|
|
|
// idle. If needspinning is set when they are about to drop their P,
|
|
|
|
|
// they abort the drop and instead become a new spinning M on our
|
|
|
|
|
// behalf. If we are not racing and the system is truly fully loaded
|
|
|
|
|
// then no spinning threads are required, and the next thread to
|
|
|
|
|
// naturally become spinning will clear the flag.
|
|
|
|
|
//
|
|
|
|
|
// Also see "Worker thread parking/unparking" comment at the top of the
|
|
|
|
|
// file.
|
2021-02-10 12:46:09 -05:00
|
|
|
wasSpinning := mp.spinning
|
|
|
|
|
if mp.spinning {
|
|
|
|
|
mp.spinning = false
|
2022-07-25 15:20:22 -04:00
|
|
|
if sched.nmspinning.Add(-1) < 0 {
|
2015-12-08 15:11:27 +01:00
|
|
|
throw("findrunnable: negative nmspinning")
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
|
runtime: skip work recheck for non-spinning Ms
When an M transitions from spinning to non-spinning state, it must
recheck most sources of work to avoid missing work submitted between its
initial check and decrementing sched.nmspinning (see "delicate dance"
comment).
Ever since the scheduler rewrite in Go 1.1 (golang.org/cl/7314062), we
have performed this recheck on all Ms before stopping, regardless of
whether or not they were spinning.
Unfortunately, there is a problem with this approach: non-spinning Ms
are not eligible to steal work (note the skip over the stealWork block),
but can detect work during the recheck. If there is work available, this
non-spinning M will jump to top, skip stealing, land in recheck again,
and repeat. i.e., it will spin uselessly.
The spin is bounded. This can only occur if there is another spinning M,
which will either take the work, allowing this M to stop, or take some
other work, allowing this M to upgrade to spinning. But the spinning is
ultimately just a fancy spin-wait.
golang.org/issue/43997 discusses several ways to address this. This CL
takes the simplest approach: skipping the recheck on non-spinning Ms and
allowing them to go to stop.
Results for scheduler-relevant runtime and time benchmarks can be found
at https://perf.golang.org/search?q=upload:20210420.5.
The new BenchmarkCreateGoroutinesSingle is a characteristic example
workload that hits this issue hard. A single M readies lots of work
without itself parking. Other Ms must spin to steal work, which is very
short-lived, forcing those Ms to spin again. Some of the Ms will be
non-spinning and hit the above bug.
With this fixed, that benchmark drops in CPU usage by a massive 68%, and
wall time 24%. BenchmarkNetpollBreak shows similar drops because it is
unintentionally almost the same benchmark (create short-living Gs in a
loop). Typical well-behaved programs show little change.
We also measure scheduling latency (time from goready to execute). Note
that many of these benchmarks are very noisy because they don't involve
much scheduling. Those that do, like CreateGoroutinesSingle, are
expected to increase as we are replacing unintentional spin waiting with
a real park.
Fixes #43997
Change-Id: Ie1d1e1800f393cee1792455412caaa5865d13562
Reviewed-on: https://go-review.googlesource.com/c/go/+/310850
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>
2021-04-07 12:01:44 -04:00
|
|
|
// Note the for correctness, only the last M transitioning from
|
|
|
|
|
// spinning to non-spinning must perform these rechecks to
|
2022-03-01 15:06:37 -05:00
|
|
|
// ensure no missed work. However, the runtime has some cases
|
|
|
|
|
// of transient increments of nmspinning that are decremented
|
|
|
|
|
// without going through this path, so we must be conservative
|
|
|
|
|
// and perform the check on all spinning Ms.
|
|
|
|
|
//
|
|
|
|
|
// See https://go.dev/issue/43997.
|
runtime: skip work recheck for non-spinning Ms
When an M transitions from spinning to non-spinning state, it must
recheck most sources of work to avoid missing work submitted between its
initial check and decrementing sched.nmspinning (see "delicate dance"
comment).
Ever since the scheduler rewrite in Go 1.1 (golang.org/cl/7314062), we
have performed this recheck on all Ms before stopping, regardless of
whether or not they were spinning.
Unfortunately, there is a problem with this approach: non-spinning Ms
are not eligible to steal work (note the skip over the stealWork block),
but can detect work during the recheck. If there is work available, this
non-spinning M will jump to top, skip stealing, land in recheck again,
and repeat. i.e., it will spin uselessly.
The spin is bounded. This can only occur if there is another spinning M,
which will either take the work, allowing this M to stop, or take some
other work, allowing this M to upgrade to spinning. But the spinning is
ultimately just a fancy spin-wait.
golang.org/issue/43997 discusses several ways to address this. This CL
takes the simplest approach: skipping the recheck on non-spinning Ms and
allowing them to go to stop.
Results for scheduler-relevant runtime and time benchmarks can be found
at https://perf.golang.org/search?q=upload:20210420.5.
The new BenchmarkCreateGoroutinesSingle is a characteristic example
workload that hits this issue hard. A single M readies lots of work
without itself parking. Other Ms must spin to steal work, which is very
short-lived, forcing those Ms to spin again. Some of the Ms will be
non-spinning and hit the above bug.
With this fixed, that benchmark drops in CPU usage by a massive 68%, and
wall time 24%. BenchmarkNetpollBreak shows similar drops because it is
unintentionally almost the same benchmark (create short-living Gs in a
loop). Typical well-behaved programs show little change.
We also measure scheduling latency (time from goready to execute). Note
that many of these benchmarks are very noisy because they don't involve
much scheduling. Those that do, like CreateGoroutinesSingle, are
expected to increase as we are replacing unintentional spin waiting with
a real park.
Fixes #43997
Change-Id: Ie1d1e1800f393cee1792455412caaa5865d13562
Reviewed-on: https://go-review.googlesource.com/c/go/+/310850
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>
2021-04-07 12:01:44 -04:00
|
|
|
|
|
|
|
|
// Check all runqueues once again.
|
2022-05-21 00:20:47 +08:00
|
|
|
pp := checkRunqsNoP(allpSnapshot, idlepMaskSnapshot)
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp != nil {
|
|
|
|
|
acquirep(pp)
|
2022-03-01 15:06:37 -05:00
|
|
|
mp.becomeSpinning()
|
runtime: skip work recheck for non-spinning Ms
When an M transitions from spinning to non-spinning state, it must
recheck most sources of work to avoid missing work submitted between its
initial check and decrementing sched.nmspinning (see "delicate dance"
comment).
Ever since the scheduler rewrite in Go 1.1 (golang.org/cl/7314062), we
have performed this recheck on all Ms before stopping, regardless of
whether or not they were spinning.
Unfortunately, there is a problem with this approach: non-spinning Ms
are not eligible to steal work (note the skip over the stealWork block),
but can detect work during the recheck. If there is work available, this
non-spinning M will jump to top, skip stealing, land in recheck again,
and repeat. i.e., it will spin uselessly.
The spin is bounded. This can only occur if there is another spinning M,
which will either take the work, allowing this M to stop, or take some
other work, allowing this M to upgrade to spinning. But the spinning is
ultimately just a fancy spin-wait.
golang.org/issue/43997 discusses several ways to address this. This CL
takes the simplest approach: skipping the recheck on non-spinning Ms and
allowing them to go to stop.
Results for scheduler-relevant runtime and time benchmarks can be found
at https://perf.golang.org/search?q=upload:20210420.5.
The new BenchmarkCreateGoroutinesSingle is a characteristic example
workload that hits this issue hard. A single M readies lots of work
without itself parking. Other Ms must spin to steal work, which is very
short-lived, forcing those Ms to spin again. Some of the Ms will be
non-spinning and hit the above bug.
With this fixed, that benchmark drops in CPU usage by a massive 68%, and
wall time 24%. BenchmarkNetpollBreak shows similar drops because it is
unintentionally almost the same benchmark (create short-living Gs in a
loop). Typical well-behaved programs show little change.
We also measure scheduling latency (time from goready to execute). Note
that many of these benchmarks are very noisy because they don't involve
much scheduling. Those that do, like CreateGoroutinesSingle, are
expected to increase as we are replacing unintentional spin waiting with
a real park.
Fixes #43997
Change-Id: Ie1d1e1800f393cee1792455412caaa5865d13562
Reviewed-on: https://go-review.googlesource.com/c/go/+/310850
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>
2021-04-07 12:01:44 -04:00
|
|
|
goto top
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
runtime: skip work recheck for non-spinning Ms
When an M transitions from spinning to non-spinning state, it must
recheck most sources of work to avoid missing work submitted between its
initial check and decrementing sched.nmspinning (see "delicate dance"
comment).
Ever since the scheduler rewrite in Go 1.1 (golang.org/cl/7314062), we
have performed this recheck on all Ms before stopping, regardless of
whether or not they were spinning.
Unfortunately, there is a problem with this approach: non-spinning Ms
are not eligible to steal work (note the skip over the stealWork block),
but can detect work during the recheck. If there is work available, this
non-spinning M will jump to top, skip stealing, land in recheck again,
and repeat. i.e., it will spin uselessly.
The spin is bounded. This can only occur if there is another spinning M,
which will either take the work, allowing this M to stop, or take some
other work, allowing this M to upgrade to spinning. But the spinning is
ultimately just a fancy spin-wait.
golang.org/issue/43997 discusses several ways to address this. This CL
takes the simplest approach: skipping the recheck on non-spinning Ms and
allowing them to go to stop.
Results for scheduler-relevant runtime and time benchmarks can be found
at https://perf.golang.org/search?q=upload:20210420.5.
The new BenchmarkCreateGoroutinesSingle is a characteristic example
workload that hits this issue hard. A single M readies lots of work
without itself parking. Other Ms must spin to steal work, which is very
short-lived, forcing those Ms to spin again. Some of the Ms will be
non-spinning and hit the above bug.
With this fixed, that benchmark drops in CPU usage by a massive 68%, and
wall time 24%. BenchmarkNetpollBreak shows similar drops because it is
unintentionally almost the same benchmark (create short-living Gs in a
loop). Typical well-behaved programs show little change.
We also measure scheduling latency (time from goready to execute). Note
that many of these benchmarks are very noisy because they don't involve
much scheduling. Those that do, like CreateGoroutinesSingle, are
expected to increase as we are replacing unintentional spin waiting with
a real park.
Fixes #43997
Change-Id: Ie1d1e1800f393cee1792455412caaa5865d13562
Reviewed-on: https://go-review.googlesource.com/c/go/+/310850
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>
2021-04-07 12:01:44 -04:00
|
|
|
// Check for idle-priority GC work again.
|
2022-05-21 00:20:47 +08:00
|
|
|
pp, gp := checkIdleGCNoP()
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp != nil {
|
|
|
|
|
acquirep(pp)
|
2022-03-01 15:06:37 -05:00
|
|
|
mp.becomeSpinning()
|
runtime: manage gcBgMarkWorkers with a global pool
Background mark workers perform per-P marking work. Currently each
worker is assigned a P at creation time. The worker "attaches" to the P
via p.gcBgMarkWorker, making itself (usually) available to
findRunnableGCWorker for scheduling GC work.
While running gcMarkDone, the worker "detaches" from the P (by clearing
p.gcBgMarkWorker), since it may park for other reasons and should not be
scheduled by findRunnableGCWorker.
Unfortunately, this design is complex and difficult to reason about. We
simplify things by changing the design to eliminate the hard P
attachment. Rather than workers always performing work from the same P,
workers perform work for whichever P they find themselves on. On park,
the workers are placed in a pool of free workers, which each P's
findRunnableGCWorker can use to run a worker for its P.
Now if a worker parks in gcMarkDone, a P may simply use another worker
from the pool to complete its own work.
The P's GC worker mode is used to communicate the mode to run to the
selected worker. It is also used to emit the appropriate worker
EvGoStart tracepoint. This is a slight change, as this G may be
preempted (e.g., in gcMarkDone). When it is rescheduled, the trace
viewer will show it as a normal goroutine again. It is currently a bit
difficult to connect to the original worker tracepoint, as the viewer
does not display the goid for the original worker (though the data is in
the trace file).
Change-Id: Id7bd3a364dc18a4d2b1c99c4dc4810fae1293c1b
Reviewed-on: https://go-review.googlesource.com/c/go/+/262348
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-13 12:39:13 -04:00
|
|
|
|
runtime: skip work recheck for non-spinning Ms
When an M transitions from spinning to non-spinning state, it must
recheck most sources of work to avoid missing work submitted between its
initial check and decrementing sched.nmspinning (see "delicate dance"
comment).
Ever since the scheduler rewrite in Go 1.1 (golang.org/cl/7314062), we
have performed this recheck on all Ms before stopping, regardless of
whether or not they were spinning.
Unfortunately, there is a problem with this approach: non-spinning Ms
are not eligible to steal work (note the skip over the stealWork block),
but can detect work during the recheck. If there is work available, this
non-spinning M will jump to top, skip stealing, land in recheck again,
and repeat. i.e., it will spin uselessly.
The spin is bounded. This can only occur if there is another spinning M,
which will either take the work, allowing this M to stop, or take some
other work, allowing this M to upgrade to spinning. But the spinning is
ultimately just a fancy spin-wait.
golang.org/issue/43997 discusses several ways to address this. This CL
takes the simplest approach: skipping the recheck on non-spinning Ms and
allowing them to go to stop.
Results for scheduler-relevant runtime and time benchmarks can be found
at https://perf.golang.org/search?q=upload:20210420.5.
The new BenchmarkCreateGoroutinesSingle is a characteristic example
workload that hits this issue hard. A single M readies lots of work
without itself parking. Other Ms must spin to steal work, which is very
short-lived, forcing those Ms to spin again. Some of the Ms will be
non-spinning and hit the above bug.
With this fixed, that benchmark drops in CPU usage by a massive 68%, and
wall time 24%. BenchmarkNetpollBreak shows similar drops because it is
unintentionally almost the same benchmark (create short-living Gs in a
loop). Typical well-behaved programs show little change.
We also measure scheduling latency (time from goready to execute). Note
that many of these benchmarks are very noisy because they don't involve
much scheduling. Those that do, like CreateGoroutinesSingle, are
expected to increase as we are replacing unintentional spin waiting with
a real park.
Fixes #43997
Change-Id: Ie1d1e1800f393cee1792455412caaa5865d13562
Reviewed-on: https://go-review.googlesource.com/c/go/+/310850
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>
2021-04-07 12:01:44 -04:00
|
|
|
// Run the idle worker.
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.gcMarkWorkerMode = gcMarkWorkerIdleMode
|
runtime: skip work recheck for non-spinning Ms
When an M transitions from spinning to non-spinning state, it must
recheck most sources of work to avoid missing work submitted between its
initial check and decrementing sched.nmspinning (see "delicate dance"
comment).
Ever since the scheduler rewrite in Go 1.1 (golang.org/cl/7314062), we
have performed this recheck on all Ms before stopping, regardless of
whether or not they were spinning.
Unfortunately, there is a problem with this approach: non-spinning Ms
are not eligible to steal work (note the skip over the stealWork block),
but can detect work during the recheck. If there is work available, this
non-spinning M will jump to top, skip stealing, land in recheck again,
and repeat. i.e., it will spin uselessly.
The spin is bounded. This can only occur if there is another spinning M,
which will either take the work, allowing this M to stop, or take some
other work, allowing this M to upgrade to spinning. But the spinning is
ultimately just a fancy spin-wait.
golang.org/issue/43997 discusses several ways to address this. This CL
takes the simplest approach: skipping the recheck on non-spinning Ms and
allowing them to go to stop.
Results for scheduler-relevant runtime and time benchmarks can be found
at https://perf.golang.org/search?q=upload:20210420.5.
The new BenchmarkCreateGoroutinesSingle is a characteristic example
workload that hits this issue hard. A single M readies lots of work
without itself parking. Other Ms must spin to steal work, which is very
short-lived, forcing those Ms to spin again. Some of the Ms will be
non-spinning and hit the above bug.
With this fixed, that benchmark drops in CPU usage by a massive 68%, and
wall time 24%. BenchmarkNetpollBreak shows similar drops because it is
unintentionally almost the same benchmark (create short-living Gs in a
loop). Typical well-behaved programs show little change.
We also measure scheduling latency (time from goready to execute). Note
that many of these benchmarks are very noisy because they don't involve
much scheduling. Those that do, like CreateGoroutinesSingle, are
expected to increase as we are replacing unintentional spin waiting with
a real park.
Fixes #43997
Change-Id: Ie1d1e1800f393cee1792455412caaa5865d13562
Reviewed-on: https://go-review.googlesource.com/c/go/+/310850
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>
2021-04-07 12:01:44 -04:00
|
|
|
casgstatus(gp, _Gwaiting, _Grunnable)
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoUnpark(gp, 0)
|
|
|
|
|
}
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
return gp, false, false
|
runtime: wake idle Ps when enqueuing GC work
If the scheduler has no user work and there's no GC work visible, it
puts the P to sleep (or blocks on the network). However, if we later
enqueue more GC work, there's currently nothing that specifically
wakes up the scheduler to let it start an idle GC worker. As a result,
we can underutilize the CPU during GC if Ps have been put to sleep.
Fix this by making GC wake idle Ps when work buffers are put on the
full list. We already have a hook to do this, since we use this to
preempt a random P if we need more dedicated workers. We expand this
hook to instead wake an idle P if there is one. The logic we use for
this is identical to the logic used to wake an idle P when we ready a
goroutine.
To make this really sound, we also fix the scheduler to re-check the
idle GC worker condition after releasing its P. This closes a race
where 1) the scheduler checks for idle work and finds none, 2) new
work is enqueued but there are no idle Ps so none are woken, and 3)
the scheduler releases its P.
There is one subtlety here. Currently we call enlistWorker directly
from putfull, but the gcWork is in an inconsistent state in the places
that call putfull. This isn't a problem right now because nothing that
enlistWorker does touches the gcWork, but with the added call to
wakep, it's possible to get a recursive call into the gcWork
(specifically, while write barriers are disallowed, this can do an
allocation, which can dispose a gcWork, which can put a workbuf). To
handle this, we lift the enlistWorker calls up a layer and delay them
until the gcWork is in a consistent state.
Fixes #14179.
Change-Id: Ia2467a52e54c9688c3c1752e1fc00f5b37bbfeeb
Reviewed-on: https://go-review.googlesource.com/32434
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
2016-10-30 20:43:53 -04:00
|
|
|
}
|
|
|
|
|
|
runtime: skip work recheck for non-spinning Ms
When an M transitions from spinning to non-spinning state, it must
recheck most sources of work to avoid missing work submitted between its
initial check and decrementing sched.nmspinning (see "delicate dance"
comment).
Ever since the scheduler rewrite in Go 1.1 (golang.org/cl/7314062), we
have performed this recheck on all Ms before stopping, regardless of
whether or not they were spinning.
Unfortunately, there is a problem with this approach: non-spinning Ms
are not eligible to steal work (note the skip over the stealWork block),
but can detect work during the recheck. If there is work available, this
non-spinning M will jump to top, skip stealing, land in recheck again,
and repeat. i.e., it will spin uselessly.
The spin is bounded. This can only occur if there is another spinning M,
which will either take the work, allowing this M to stop, or take some
other work, allowing this M to upgrade to spinning. But the spinning is
ultimately just a fancy spin-wait.
golang.org/issue/43997 discusses several ways to address this. This CL
takes the simplest approach: skipping the recheck on non-spinning Ms and
allowing them to go to stop.
Results for scheduler-relevant runtime and time benchmarks can be found
at https://perf.golang.org/search?q=upload:20210420.5.
The new BenchmarkCreateGoroutinesSingle is a characteristic example
workload that hits this issue hard. A single M readies lots of work
without itself parking. Other Ms must spin to steal work, which is very
short-lived, forcing those Ms to spin again. Some of the Ms will be
non-spinning and hit the above bug.
With this fixed, that benchmark drops in CPU usage by a massive 68%, and
wall time 24%. BenchmarkNetpollBreak shows similar drops because it is
unintentionally almost the same benchmark (create short-living Gs in a
loop). Typical well-behaved programs show little change.
We also measure scheduling latency (time from goready to execute). Note
that many of these benchmarks are very noisy because they don't involve
much scheduling. Those that do, like CreateGoroutinesSingle, are
expected to increase as we are replacing unintentional spin waiting with
a real park.
Fixes #43997
Change-Id: Ie1d1e1800f393cee1792455412caaa5865d13562
Reviewed-on: https://go-review.googlesource.com/c/go/+/310850
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>
2021-04-07 12:01:44 -04:00
|
|
|
// Finally, check for timer creation or expiry concurrently with
|
|
|
|
|
// transitioning from spinning to non-spinning.
|
|
|
|
|
//
|
|
|
|
|
// Note that we cannot use checkTimers here because it calls
|
|
|
|
|
// adjusttimers which may need to allocate memory, and that isn't
|
|
|
|
|
// allowed when we don't have an active P.
|
|
|
|
|
pollUntil = checkTimersNoP(allpSnapshot, timerpMaskSnapshot, pollUntil)
|
|
|
|
|
}
|
2021-04-08 16:57:57 -04:00
|
|
|
|
2021-04-07 09:58:18 -04:00
|
|
|
// Poll network until next timer.
|
2022-08-25 02:26:08 +08:00
|
|
|
if netpollinited() && (netpollWaiters.Load() > 0 || pollUntil != 0) && sched.lastpoll.Swap(0) != 0 {
|
2022-07-20 17:42:53 -04:00
|
|
|
sched.pollUntil.Store(pollUntil)
|
2021-02-10 12:46:09 -05:00
|
|
|
if mp.p != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("findrunnable: netpoll with p")
|
|
|
|
|
}
|
2021-02-10 12:46:09 -05:00
|
|
|
if mp.spinning {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("findrunnable: netpoll with spinning")
|
|
|
|
|
}
|
2022-06-02 21:26:49 +00:00
|
|
|
// Refresh now.
|
|
|
|
|
now = nanotime()
|
2021-04-07 10:15:33 -04:00
|
|
|
delay := int64(-1)
|
|
|
|
|
if pollUntil != 0 {
|
|
|
|
|
delay = pollUntil - now
|
|
|
|
|
if delay < 0 {
|
|
|
|
|
delay = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-11 08:53:42 -07:00
|
|
|
if faketime != 0 {
|
|
|
|
|
// When using fake time, just poll.
|
2021-04-07 10:15:33 -04:00
|
|
|
delay = 0
|
2019-04-11 08:53:42 -07:00
|
|
|
}
|
2021-04-07 10:15:33 -04:00
|
|
|
list := netpoll(delay) // block until new work is available
|
2022-07-20 17:42:53 -04:00
|
|
|
sched.pollUntil.Store(0)
|
2022-07-20 17:39:12 -04:00
|
|
|
sched.lastpoll.Store(now)
|
2019-04-11 08:53:42 -07:00
|
|
|
if faketime != 0 && list.empty() {
|
|
|
|
|
// Using fake time and nothing is ready; stop M.
|
|
|
|
|
// When all M's stop, checkdead will call timejump.
|
|
|
|
|
stopm()
|
|
|
|
|
goto top
|
|
|
|
|
}
|
2019-04-02 20:27:35 -07:00
|
|
|
lock(&sched.lock)
|
2022-05-21 00:20:47 +08:00
|
|
|
pp, _ := pidleget(now)
|
2019-04-02 20:27:35 -07:00
|
|
|
unlock(&sched.lock)
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp == nil {
|
2019-04-02 20:27:35 -07:00
|
|
|
injectglist(&list)
|
|
|
|
|
} else {
|
2021-02-09 15:48:41 -05:00
|
|
|
acquirep(pp)
|
2019-04-02 20:27:35 -07:00
|
|
|
if !list.empty() {
|
2018-08-10 00:09:00 -04:00
|
|
|
gp := list.pop()
|
2018-08-10 10:33:05 -04:00
|
|
|
injectglist(&list)
|
2015-10-18 17:04:05 -07:00
|
|
|
casgstatus(gp, _Gwaiting, _Grunnable)
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoUnpark(gp, 0)
|
|
|
|
|
}
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
return gp, false, false
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2019-04-02 20:27:35 -07:00
|
|
|
if wasSpinning {
|
2022-03-01 15:06:37 -05:00
|
|
|
mp.becomeSpinning()
|
2019-04-02 20:27:35 -07:00
|
|
|
}
|
|
|
|
|
goto top
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2019-04-05 16:24:14 -07:00
|
|
|
} else if pollUntil != 0 && netpollinited() {
|
2022-07-20 17:42:53 -04:00
|
|
|
pollerPollUntil := sched.pollUntil.Load()
|
2019-04-05 16:24:14 -07:00
|
|
|
if pollerPollUntil == 0 || pollerPollUntil > pollUntil {
|
|
|
|
|
netpollBreak()
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
stopm()
|
|
|
|
|
goto top
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-02 15:18:43 +00:00
|
|
|
// pollWork reports whether there is non-background work this P could
|
2016-10-30 20:20:17 -04:00
|
|
|
// be doing. This is a fairly lightweight check to be used for
|
|
|
|
|
// background work loops, like idle GC. It checks a subset of the
|
|
|
|
|
// conditions checked by the actual scheduler.
|
|
|
|
|
func pollWork() bool {
|
|
|
|
|
if sched.runqsize != 0 {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
p := getg().m.p.ptr()
|
|
|
|
|
if !runqempty(p) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2022-08-25 02:26:08 +08:00
|
|
|
if netpollinited() && netpollWaiters.Load() > 0 && sched.lastpoll.Load() != 0 {
|
2019-04-02 20:27:35 -07:00
|
|
|
if list := netpoll(0); !list.empty() {
|
2018-08-10 10:33:05 -04:00
|
|
|
injectglist(&list)
|
2016-10-30 20:20:17 -04:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-16 15:50:49 -05:00
|
|
|
// stealWork attempts to steal a runnable goroutine or timer from any P.
|
|
|
|
|
//
|
|
|
|
|
// If newWork is true, new work may have been readied.
|
|
|
|
|
//
|
|
|
|
|
// If now is not 0 it is the current time. stealWork returns the passed time or
|
|
|
|
|
// the current time if now was passed as 0.
|
|
|
|
|
func stealWork(now int64) (gp *g, inheritTime bool, rnow, pollUntil int64, newWork bool) {
|
|
|
|
|
pp := getg().m.p.ptr()
|
|
|
|
|
|
|
|
|
|
ranTimer := false
|
|
|
|
|
|
|
|
|
|
const stealTries = 4
|
|
|
|
|
for i := 0; i < stealTries; i++ {
|
|
|
|
|
stealTimersOrRunNextG := i == stealTries-1
|
|
|
|
|
|
|
|
|
|
for enum := stealOrder.start(fastrand()); !enum.done(); enum.next() {
|
2022-07-25 15:31:03 -04:00
|
|
|
if sched.gcwaiting.Load() {
|
2021-02-16 15:50:49 -05:00
|
|
|
// GC work may be available.
|
|
|
|
|
return nil, false, now, pollUntil, true
|
|
|
|
|
}
|
|
|
|
|
p2 := allp[enum.position()]
|
|
|
|
|
if pp == p2 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Steal timers from p2. This call to checkTimers is the only place
|
|
|
|
|
// where we might hold a lock on a different P's timers. We do this
|
|
|
|
|
// once on the last pass before checking runnext because stealing
|
|
|
|
|
// from the other P's runnext should be the last resort, so if there
|
|
|
|
|
// are timers to steal do that first.
|
|
|
|
|
//
|
|
|
|
|
// We only check timers on one of the stealing iterations because
|
|
|
|
|
// the time stored in now doesn't change in this loop and checking
|
|
|
|
|
// the timers for each P more than once with the same value of now
|
|
|
|
|
// is probably a waste of time.
|
|
|
|
|
//
|
|
|
|
|
// timerpMask tells us whether the P may have timers at all. If it
|
|
|
|
|
// can't, no need to check at all.
|
|
|
|
|
if stealTimersOrRunNextG && timerpMask.read(enum.position()) {
|
|
|
|
|
tnow, w, ran := checkTimers(p2, now)
|
|
|
|
|
now = tnow
|
|
|
|
|
if w != 0 && (pollUntil == 0 || w < pollUntil) {
|
|
|
|
|
pollUntil = w
|
|
|
|
|
}
|
|
|
|
|
if ran {
|
|
|
|
|
// Running the timers may have
|
|
|
|
|
// made an arbitrary number of G's
|
|
|
|
|
// ready and added them to this P's
|
|
|
|
|
// local run queue. That invalidates
|
|
|
|
|
// the assumption of runqsteal
|
|
|
|
|
// that it always has room to add
|
|
|
|
|
// stolen G's. So check now if there
|
|
|
|
|
// is a local G to run.
|
|
|
|
|
if gp, inheritTime := runqget(pp); gp != nil {
|
|
|
|
|
return gp, inheritTime, now, pollUntil, ranTimer
|
|
|
|
|
}
|
|
|
|
|
ranTimer = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't bother to attempt to steal if p2 is idle.
|
|
|
|
|
if !idlepMask.read(enum.position()) {
|
|
|
|
|
if gp := runqsteal(pp, p2, stealTimersOrRunNextG); gp != nil {
|
|
|
|
|
return gp, false, now, pollUntil, ranTimer
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No goroutines found to steal. Regardless, running a timer may have
|
|
|
|
|
// made some goroutine ready that we missed. Indicate the next timer to
|
|
|
|
|
// wait for.
|
|
|
|
|
return nil, false, now, pollUntil, ranTimer
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-07 09:58:18 -04:00
|
|
|
// Check all Ps for a runnable G to steal.
|
|
|
|
|
//
|
|
|
|
|
// On entry we have no P. If a G is available to steal and a P is available,
|
|
|
|
|
// the P is returned which the caller should acquire and attempt to steal the
|
|
|
|
|
// work to.
|
|
|
|
|
func checkRunqsNoP(allpSnapshot []*p, idlepMaskSnapshot pMask) *p {
|
|
|
|
|
for id, p2 := range allpSnapshot {
|
|
|
|
|
if !idlepMaskSnapshot.read(uint32(id)) && !runqempty(p2) {
|
|
|
|
|
lock(&sched.lock)
|
2022-03-01 15:06:37 -05:00
|
|
|
pp, _ := pidlegetSpinning(0)
|
|
|
|
|
if pp == nil {
|
|
|
|
|
// Can't get a P, don't bother checking remaining Ps.
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
return nil
|
2021-04-07 09:58:18 -04:00
|
|
|
}
|
2022-03-01 15:06:37 -05:00
|
|
|
unlock(&sched.lock)
|
|
|
|
|
return pp
|
2021-04-07 09:58:18 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-01 15:06:37 -05:00
|
|
|
// No work available.
|
2021-04-07 09:58:18 -04:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check all Ps for a timer expiring sooner than pollUntil.
|
|
|
|
|
//
|
|
|
|
|
// Returns updated pollUntil value.
|
|
|
|
|
func checkTimersNoP(allpSnapshot []*p, timerpMaskSnapshot pMask, pollUntil int64) int64 {
|
|
|
|
|
for id, p2 := range allpSnapshot {
|
|
|
|
|
if timerpMaskSnapshot.read(uint32(id)) {
|
|
|
|
|
w := nobarrierWakeTime(p2)
|
|
|
|
|
if w != 0 && (pollUntil == 0 || w < pollUntil) {
|
|
|
|
|
pollUntil = w
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pollUntil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for idle-priority GC, without a P on entry.
|
|
|
|
|
//
|
|
|
|
|
// If some GC work, a P, and a worker G are all available, the P and G will be
|
|
|
|
|
// returned. The returned P has not been wired yet.
|
|
|
|
|
func checkIdleGCNoP() (*p, *g) {
|
|
|
|
|
// N.B. Since we have no P, gcBlackenEnabled may change at any time; we
|
runtime: reduce max idle mark workers during periodic GC cycles
This change reduces the maximum number of idle mark workers during
periodic (currently every 2 minutes) GC cycles to 1.
Idle mark workers soak up all available and unused Ps, up to GOMAXPROCS.
While this provides some throughput and latency benefit in general, it
can cause what appear to be massive CPU utilization spikes in otherwise
idle applications. This is mostly an issue for *very* idle applications,
ones idle enough to trigger periodic GC cycles. This spike also tends to
interact poorly with auto-scaling systems, as the system might assume
the load average is very low and suddenly see a massive burst in
activity.
The result of this change is not to bring down this 100% (of GOMAXPROCS)
CPU utilization spike to 0%, but rather
min(25% + 1/GOMAXPROCS*100%, 100%)
Idle mark workers also do incur a small latency penalty as they must be
descheduled for other work that might pop up. Luckily the runtime is
pretty good about getting idle mark workers off of Ps, so in general
the latency benefit from shorter GC cycles outweighs this cost. But, the
cost is still non-zero and may be more significant in idle applications
that aren't invoking assists and write barriers quite as often.
We can't completely eliminate idle mark workers because they're
currently necessary for GC progress in some circumstances. Namely,
they're critical for progress when all we have is fractional workers. If
a fractional worker meets its quota, and all user goroutines are blocked
directly or indirectly on a GC cycle (via runtime.GOMAXPROCS, or
runtime.GC), the program may deadlock without GC workers, since the
fractional worker will go to sleep with nothing to wake it.
Fixes #37116.
For #44163.
Change-Id: Ib74793bb6b88d1765c52d445831310b0d11ef423
Reviewed-on: https://go-review.googlesource.com/c/go/+/393394
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-16 15:47:57 +00:00
|
|
|
// must check again after acquiring a P. As an optimization, we also check
|
|
|
|
|
// if an idle mark worker is needed at all. This is OK here, because if we
|
|
|
|
|
// observe that one isn't needed, at least one is currently running. Even if
|
|
|
|
|
// it stops running, its own journey into the scheduler should schedule it
|
|
|
|
|
// again, if need be (at which point, this check will pass, if relevant).
|
|
|
|
|
if atomic.Load(&gcBlackenEnabled) == 0 || !gcController.needIdleMarkWorker() {
|
2021-04-07 09:58:18 -04:00
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
if !gcMarkWorkAvailable(nil) {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-10 16:50:32 -04:00
|
|
|
// Work is available; we can start an idle GC worker only if there is
|
|
|
|
|
// an available P and available worker G.
|
2021-04-07 09:58:18 -04:00
|
|
|
//
|
2021-05-10 16:50:32 -04:00
|
|
|
// We can attempt to acquire these in either order, though both have
|
2021-05-11 21:37:56 +00:00
|
|
|
// synchronization concerns (see below). Workers are almost always
|
2021-05-10 16:50:32 -04:00
|
|
|
// available (see comment in findRunnableGCWorker for the one case
|
|
|
|
|
// there may be none). Since we're slightly less likely to find a P,
|
|
|
|
|
// check for that first.
|
|
|
|
|
//
|
|
|
|
|
// Synchronization: note that we must hold sched.lock until we are
|
|
|
|
|
// committed to keeping it. Otherwise we cannot put the unnecessary P
|
|
|
|
|
// back in sched.pidle without performing the full set of idle
|
|
|
|
|
// transition checks.
|
|
|
|
|
//
|
|
|
|
|
// If we were to check gcBgMarkWorkerPool first, we must somehow handle
|
|
|
|
|
// the assumption in gcControllerState.findRunnableGCWorker that an
|
|
|
|
|
// empty gcBgMarkWorkerPool is only possible if gcMarkDone is running.
|
2021-04-07 09:58:18 -04:00
|
|
|
lock(&sched.lock)
|
2022-03-01 15:06:37 -05:00
|
|
|
pp, now := pidlegetSpinning(0)
|
2021-04-07 09:58:18 -04:00
|
|
|
if pp == nil {
|
2021-05-10 16:50:32 -04:00
|
|
|
unlock(&sched.lock)
|
2021-04-07 09:58:18 -04:00
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
runtime: reduce max idle mark workers during periodic GC cycles
This change reduces the maximum number of idle mark workers during
periodic (currently every 2 minutes) GC cycles to 1.
Idle mark workers soak up all available and unused Ps, up to GOMAXPROCS.
While this provides some throughput and latency benefit in general, it
can cause what appear to be massive CPU utilization spikes in otherwise
idle applications. This is mostly an issue for *very* idle applications,
ones idle enough to trigger periodic GC cycles. This spike also tends to
interact poorly with auto-scaling systems, as the system might assume
the load average is very low and suddenly see a massive burst in
activity.
The result of this change is not to bring down this 100% (of GOMAXPROCS)
CPU utilization spike to 0%, but rather
min(25% + 1/GOMAXPROCS*100%, 100%)
Idle mark workers also do incur a small latency penalty as they must be
descheduled for other work that might pop up. Luckily the runtime is
pretty good about getting idle mark workers off of Ps, so in general
the latency benefit from shorter GC cycles outweighs this cost. But, the
cost is still non-zero and may be more significant in idle applications
that aren't invoking assists and write barriers quite as often.
We can't completely eliminate idle mark workers because they're
currently necessary for GC progress in some circumstances. Namely,
they're critical for progress when all we have is fractional workers. If
a fractional worker meets its quota, and all user goroutines are blocked
directly or indirectly on a GC cycle (via runtime.GOMAXPROCS, or
runtime.GC), the program may deadlock without GC workers, since the
fractional worker will go to sleep with nothing to wake it.
Fixes #37116.
For #44163.
Change-Id: Ib74793bb6b88d1765c52d445831310b0d11ef423
Reviewed-on: https://go-review.googlesource.com/c/go/+/393394
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-16 15:47:57 +00:00
|
|
|
// Now that we own a P, gcBlackenEnabled can't change (as it requires STW).
|
|
|
|
|
if gcBlackenEnabled == 0 || !gcController.addIdleMarkWorker() {
|
2022-06-02 21:26:49 +00:00
|
|
|
pidleput(pp, now)
|
2021-04-07 09:58:18 -04:00
|
|
|
unlock(&sched.lock)
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
node := (*gcBgMarkWorkerNode)(gcBgMarkWorkerPool.pop())
|
|
|
|
|
if node == nil {
|
2022-06-02 21:26:49 +00:00
|
|
|
pidleput(pp, now)
|
2021-04-07 09:58:18 -04:00
|
|
|
unlock(&sched.lock)
|
runtime: reduce max idle mark workers during periodic GC cycles
This change reduces the maximum number of idle mark workers during
periodic (currently every 2 minutes) GC cycles to 1.
Idle mark workers soak up all available and unused Ps, up to GOMAXPROCS.
While this provides some throughput and latency benefit in general, it
can cause what appear to be massive CPU utilization spikes in otherwise
idle applications. This is mostly an issue for *very* idle applications,
ones idle enough to trigger periodic GC cycles. This spike also tends to
interact poorly with auto-scaling systems, as the system might assume
the load average is very low and suddenly see a massive burst in
activity.
The result of this change is not to bring down this 100% (of GOMAXPROCS)
CPU utilization spike to 0%, but rather
min(25% + 1/GOMAXPROCS*100%, 100%)
Idle mark workers also do incur a small latency penalty as they must be
descheduled for other work that might pop up. Luckily the runtime is
pretty good about getting idle mark workers off of Ps, so in general
the latency benefit from shorter GC cycles outweighs this cost. But, the
cost is still non-zero and may be more significant in idle applications
that aren't invoking assists and write barriers quite as often.
We can't completely eliminate idle mark workers because they're
currently necessary for GC progress in some circumstances. Namely,
they're critical for progress when all we have is fractional workers. If
a fractional worker meets its quota, and all user goroutines are blocked
directly or indirectly on a GC cycle (via runtime.GOMAXPROCS, or
runtime.GC), the program may deadlock without GC workers, since the
fractional worker will go to sleep with nothing to wake it.
Fixes #37116.
For #44163.
Change-Id: Ib74793bb6b88d1765c52d445831310b0d11ef423
Reviewed-on: https://go-review.googlesource.com/c/go/+/393394
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-16 15:47:57 +00:00
|
|
|
gcController.removeIdleMarkWorker()
|
2021-04-07 09:58:18 -04:00
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-10 16:50:32 -04:00
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
2021-04-07 09:58:18 -04:00
|
|
|
return pp, node.gp.ptr()
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 17:04:36 -04:00
|
|
|
// wakeNetPoller wakes up the thread sleeping in the network poller if it isn't
|
|
|
|
|
// going to wake up before the when argument; or it wakes an idle P to service
|
|
|
|
|
// timers and the network poller if there isn't one already.
|
2019-04-05 16:24:14 -07:00
|
|
|
func wakeNetPoller(when int64) {
|
2022-07-20 17:39:12 -04:00
|
|
|
if sched.lastpoll.Load() == 0 {
|
2019-04-05 16:24:14 -07:00
|
|
|
// In findrunnable we ensure that when polling the pollUntil
|
|
|
|
|
// field is either zero or the time to which the current
|
|
|
|
|
// poll is expected to run. This can have a spurious wakeup
|
|
|
|
|
// but should never miss a wakeup.
|
2022-07-20 17:42:53 -04:00
|
|
|
pollerPollUntil := sched.pollUntil.Load()
|
2019-04-05 16:24:14 -07:00
|
|
|
if pollerPollUntil == 0 || pollerPollUntil > when {
|
|
|
|
|
netpollBreak()
|
|
|
|
|
}
|
2020-05-01 17:04:36 -04:00
|
|
|
} else {
|
|
|
|
|
// There are no threads in the network poller, try to get
|
|
|
|
|
// one there so it can handle new timers.
|
2020-12-05 19:53:08 +00:00
|
|
|
if GOOS != "plan9" { // Temporary workaround - see issue #42303.
|
|
|
|
|
wakep()
|
|
|
|
|
}
|
2019-04-05 16:24:14 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
func resetspinning() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
if !gp.m.spinning {
|
2015-12-08 15:11:27 +01:00
|
|
|
throw("resetspinning: not a spinning m")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.spinning = false
|
2022-07-25 15:20:22 -04:00
|
|
|
nmspinning := sched.nmspinning.Add(-1)
|
|
|
|
|
if nmspinning < 0 {
|
2015-12-08 15:11:27 +01:00
|
|
|
throw("findrunnable: negative nmspinning")
|
|
|
|
|
}
|
|
|
|
|
// M wakeup policy is deliberately somewhat conservative, so check if we
|
|
|
|
|
// need to wakeup another P here. See "Worker thread parking/unparking"
|
|
|
|
|
// comment at the top of the file for details.
|
2020-04-28 20:54:31 -04:00
|
|
|
wakep()
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2020-01-23 20:38:20 -08:00
|
|
|
// injectglist adds each runnable G on the list to some run queue,
|
|
|
|
|
// and clears glist. If there is no current P, they are added to the
|
|
|
|
|
// global queue, and up to npidle M's are started to run them.
|
|
|
|
|
// Otherwise, for each idle P, this adds a G to the global queue
|
|
|
|
|
// and starts an M. Any remaining G's are added to the current P's
|
|
|
|
|
// local run queue.
|
2020-08-21 11:51:25 -04:00
|
|
|
// This may temporarily acquire sched.lock.
|
2015-10-18 17:04:05 -07:00
|
|
|
// Can run concurrently with GC.
|
2018-08-10 10:33:05 -04:00
|
|
|
func injectglist(glist *gList) {
|
|
|
|
|
if glist.empty() {
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if trace.enabled {
|
2018-08-10 10:33:05 -04:00
|
|
|
for gp := glist.head.ptr(); gp != nil; gp = gp.schedlink.ptr() {
|
2015-10-18 17:04:05 -07:00
|
|
|
traceGoUnpark(gp, 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-01-23 20:38:20 -08:00
|
|
|
|
|
|
|
|
// Mark all the goroutines as runnable before we put them
|
|
|
|
|
// on the run queues.
|
|
|
|
|
head := glist.head.ptr()
|
|
|
|
|
var tail *g
|
|
|
|
|
qsize := 0
|
|
|
|
|
for gp := head; gp != nil; gp = gp.schedlink.ptr() {
|
|
|
|
|
tail = gp
|
|
|
|
|
qsize++
|
|
|
|
|
casgstatus(gp, _Gwaiting, _Grunnable)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Turn the gList into a gQueue.
|
|
|
|
|
var q gQueue
|
|
|
|
|
q.head.set(head)
|
|
|
|
|
q.tail.set(tail)
|
|
|
|
|
*glist = gList{}
|
|
|
|
|
|
|
|
|
|
startIdle := func(n int) {
|
2022-03-01 15:06:37 -05:00
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
mp := acquirem() // See comment in startm.
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
|
|
|
|
|
pp, _ := pidlegetSpinning(0)
|
|
|
|
|
if pp == nil {
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
releasem(mp)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
startm(pp, false)
|
|
|
|
|
releasem(mp)
|
2020-01-23 20:38:20 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pp := getg().m.p.ptr()
|
|
|
|
|
if pp == nil {
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
globrunqputbatch(&q, int32(qsize))
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
startIdle(qsize)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-20 18:01:31 -04:00
|
|
|
npidle := int(sched.npidle.Load())
|
2020-05-11 11:18:57 +08:00
|
|
|
var globq gQueue
|
2015-10-18 17:04:05 -07:00
|
|
|
var n int
|
2020-01-23 20:38:20 -08:00
|
|
|
for n = 0; n < npidle && !q.empty(); n++ {
|
2020-05-11 11:18:57 +08:00
|
|
|
g := q.pop()
|
|
|
|
|
globq.pushBack(g)
|
|
|
|
|
}
|
|
|
|
|
if n > 0 {
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
globrunqputbatch(&globq, int32(n))
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
startIdle(n)
|
|
|
|
|
qsize -= n
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2020-01-23 20:38:20 -08:00
|
|
|
|
|
|
|
|
if !q.empty() {
|
|
|
|
|
runqputbatch(pp, &q, qsize)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// One round of scheduler: find a runnable goroutine and execute it.
|
|
|
|
|
// Never returns.
|
|
|
|
|
func schedule() {
|
2021-02-10 12:46:09 -05:00
|
|
|
mp := getg().m
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-10 12:46:09 -05:00
|
|
|
if mp.locks != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("schedule: holding locks")
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-10 12:46:09 -05:00
|
|
|
if mp.lockedg != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
stoplockedm()
|
2021-02-10 12:46:09 -05:00
|
|
|
execute(mp.lockedg.ptr(), false) // Never returns.
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2017-09-13 15:53:47 -07:00
|
|
|
// We should not schedule away from a g that is executing a cgo call,
|
|
|
|
|
// since the cgo call is using the m's g0 stack.
|
2021-02-10 12:46:09 -05:00
|
|
|
if mp.incgo {
|
2017-09-13 15:53:47 -07:00
|
|
|
throw("schedule: in cgo")
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
top:
|
2021-02-10 12:46:09 -05:00
|
|
|
pp := mp.p.ptr()
|
2019-10-12 21:23:29 -04:00
|
|
|
pp.preempt = false
|
|
|
|
|
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
// Safety check: if we are spinning, the run queue should be empty.
|
2019-04-05 16:24:14 -07:00
|
|
|
// Check this before calling checkTimers, as that might call
|
|
|
|
|
// goready to put a ready goroutine on the local run queue.
|
2021-02-10 12:46:09 -05:00
|
|
|
if mp.spinning && (pp.runnext != 0 || pp.runqhead != pp.runqtail) {
|
2019-04-05 16:24:14 -07:00
|
|
|
throw("schedule: spinning with local work")
|
|
|
|
|
}
|
|
|
|
|
|
runtime: move scheduling decisions by schedule into findrunnable
This change moves several scheduling decisions made by schedule into
findrunnable. The main motivation behind this change is the fact that
stopped Ms can't become dedicated or fractional GC workers. The main
reason for this is that when a stopped M wakes up, it stays in
findrunnable until it finds work, which means it will never consider GC
work. On that note, it'll also never consider becoming the trace reader,
either.
Another way of looking at it is that this change tries to make
findrunnable aware of more sources of work than it was before. With this
change, any M in findrunnable should be capable of becoming a GC worker,
resolving #44313. While we're here, let's also make more sources of
work, such as the trace reader, visible to handoffp, which should really
be checking all sources of work. With that, we also now correctly handle
the case where StopTrace is called from the last live M that is also
locked (#39004). stoplockedm calls handoffp to start a new M and handle
the work it cannot, and once we include the trace reader in that, we
ensure that the trace reader gets scheduled.
This change attempts to preserve the exact same ordering of work
checking to reduce its impact.
One consequence of this change is that upon entering schedule, some
sources of work won't be checked twice (i.e. the local and global
runqs, and timers) as they do now, which in some sense gives them a
lower priority than they had before.
Fixes #39004.
Fixes #44313.
Change-Id: I5d8b7f63839db8d9a3e47cdda604baac1fe615ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/393880
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-18 23:59:12 +00:00
|
|
|
gp, inheritTime, tryWakeP := findRunnable() // blocks until work is available
|
2015-12-08 15:11:27 +01:00
|
|
|
|
|
|
|
|
// 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.
|
2021-02-10 12:46:09 -05:00
|
|
|
if mp.spinning {
|
2015-10-18 17:04:05 -07:00
|
|
|
resetspinning()
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-11 11:28:24 -04:00
|
|
|
if sched.disable.user && !schedEnabled(gp) {
|
|
|
|
|
// Scheduling of this goroutine is disabled. Put it on
|
|
|
|
|
// the list of pending runnable goroutines for when we
|
|
|
|
|
// re-enable user scheduling and look again.
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
if schedEnabled(gp) {
|
|
|
|
|
// Something re-enabled scheduling while we
|
|
|
|
|
// were acquiring the lock.
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
} else {
|
|
|
|
|
sched.disable.runnable.pushBack(gp)
|
|
|
|
|
sched.disable.n++
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
goto top
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-01 17:31:20 -04:00
|
|
|
// If about to schedule a not-normal goroutine (a GCworker or tracereader),
|
|
|
|
|
// wake a P if there is one.
|
|
|
|
|
if tryWakeP {
|
2020-04-28 20:54:31 -04:00
|
|
|
wakep()
|
2018-11-01 17:31:20 -04:00
|
|
|
}
|
2017-09-13 10:14:02 -07:00
|
|
|
if gp.lockedm != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
// Hands off own p to the locked m,
|
|
|
|
|
// then blocks waiting for a new p.
|
|
|
|
|
startlockedm(gp)
|
|
|
|
|
goto top
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
execute(gp, inheritTime)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dropg removes the association between m and the current goroutine m->curg (gp for short).
|
|
|
|
|
// Typically a caller sets gp's status away from Grunning and then
|
|
|
|
|
// immediately calls dropg to finish the job. The caller is also responsible
|
|
|
|
|
// for arranging that gp will be restarted using ready at an
|
|
|
|
|
// appropriate time. After calling dropg and arranging for gp to be
|
|
|
|
|
// readied later, the caller can do other work but eventually should
|
|
|
|
|
// call schedule to restart the scheduling of goroutines on this m.
|
|
|
|
|
func dropg() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
setMNoWB(&gp.m.curg.m, nil)
|
|
|
|
|
setGNoWB(&gp.m.curg, nil)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2019-04-05 16:24:14 -07:00
|
|
|
// checkTimers runs any timers for the P that are ready.
|
|
|
|
|
// If now is not 0 it is the current time.
|
2021-02-16 15:50:49 -05:00
|
|
|
// It returns the passed time or the current time if now was passed as 0.
|
2019-04-05 16:24:14 -07:00
|
|
|
// and the time when the next timer should run or 0 if there is no next timer,
|
|
|
|
|
// and reports whether it ran any timers.
|
|
|
|
|
// If the time when the next timer should run is not 0,
|
|
|
|
|
// it is always larger than the returned time.
|
|
|
|
|
// We pass now in and out to avoid extra calls of nanotime.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2019-04-05 16:24:14 -07:00
|
|
|
//go:yeswritebarrierrec
|
|
|
|
|
func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool) {
|
2020-09-29 17:01:33 -07:00
|
|
|
// If it's not yet time for the first timer, or the first adjusted
|
|
|
|
|
// timer, then there is nothing to do.
|
2022-08-18 00:21:36 +07:00
|
|
|
next := pp.timer0When.Load()
|
|
|
|
|
nextAdj := pp.timerModifiedEarliest.Load()
|
2020-09-29 17:01:33 -07:00
|
|
|
if next == 0 || (nextAdj != 0 && nextAdj < next) {
|
|
|
|
|
next = nextAdj
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if next == 0 {
|
|
|
|
|
// No timers to run or adjust.
|
|
|
|
|
return now, 0, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if now == 0 {
|
|
|
|
|
now = nanotime()
|
|
|
|
|
}
|
|
|
|
|
if now < next {
|
|
|
|
|
// Next timer is not ready to run, but keep going
|
|
|
|
|
// if we would clear deleted timers.
|
|
|
|
|
// This corresponds to the condition below where
|
|
|
|
|
// we decide whether to call clearDeletedTimers.
|
2022-08-26 09:54:32 +08:00
|
|
|
if pp != getg().m.p.ptr() || int(pp.deletedTimers.Load()) <= int(pp.numTimers.Load()/4) {
|
2020-09-29 17:01:33 -07:00
|
|
|
return now, next, false
|
2020-01-13 12:17:26 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-05 16:24:14 -07:00
|
|
|
lock(&pp.timersLock)
|
|
|
|
|
|
|
|
|
|
if len(pp.timers) > 0 {
|
2020-09-29 17:01:33 -07:00
|
|
|
adjusttimers(pp, now)
|
2019-04-05 16:24:14 -07:00
|
|
|
for len(pp.timers) > 0 {
|
2019-11-15 10:05:13 -08:00
|
|
|
// Note that runtimer may temporarily unlock
|
|
|
|
|
// pp.timersLock.
|
2020-09-29 17:01:33 -07:00
|
|
|
if tw := runtimer(pp, now); tw != 0 {
|
2019-04-05 16:24:14 -07:00
|
|
|
if tw > 0 {
|
|
|
|
|
pollUntil = tw
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
ran = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-09 23:03:25 -08:00
|
|
|
// If this is the local P, and there are a lot of deleted timers,
|
|
|
|
|
// clear them out. We only do this for the local P to reduce
|
|
|
|
|
// lock contention on timersLock.
|
2022-08-26 09:54:32 +08:00
|
|
|
if pp == getg().m.p.ptr() && int(pp.deletedTimers.Load()) > len(pp.timers)/4 {
|
2020-01-09 23:03:25 -08:00
|
|
|
clearDeletedTimers(pp)
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-05 16:24:14 -07:00
|
|
|
unlock(&pp.timersLock)
|
|
|
|
|
|
2020-09-29 17:01:33 -07:00
|
|
|
return now, pollUntil, ran
|
2019-04-05 16:24:14 -07:00
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
func parkunlock_c(gp *g, lock unsafe.Pointer) bool {
|
|
|
|
|
unlock((*mutex)(lock))
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// park continuation on g0.
|
|
|
|
|
func park_m(gp *g) {
|
2021-02-10 12:46:09 -05:00
|
|
|
mp := getg().m
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
if trace.enabled {
|
2021-02-10 12:46:09 -05:00
|
|
|
traceGoPark(mp.waittraceev, mp.waittraceskip)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
runtime: set G wait reason more consistently
Currently, wait reasons are set somewhat inconsistently. In a follow-up
CL, we're going to want to rely on the wait reason being there for
casgstatus, so the status quo isn't really going to work for that. Plus
this inconsistency means there are a whole bunch of cases where we could
be more specific about the G's status but aren't.
So, this change adds a new function, casGToWaiting which is like
casgstatus but also sets the wait reason. The goal is that by using this
API it'll be harder to forget to set a wait reason (or the lack thereof
will at least be explicit). This change then updates all casgstatus(gp,
..., _Gwaiting) calls to casGToWaiting(gp, ..., waitReasonX) instead.
For a number of these cases, we're missing a wait reason, and it
wouldn't hurt to add a wait reason for them, so this change also adds
those wait reasons.
For #49881.
Change-Id: Ia95e06ecb74ed17bb7bb94f1a362ebfe6bec1518
Reviewed-on: https://go-review.googlesource.com/c/go/+/427617
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-08-31 18:21:48 +00:00
|
|
|
// N.B. Not using casGToWaiting here because the waitreason is
|
|
|
|
|
// set by park_m's caller.
|
2015-10-18 17:04:05 -07:00
|
|
|
casgstatus(gp, _Grunning, _Gwaiting)
|
|
|
|
|
dropg()
|
|
|
|
|
|
2021-02-10 12:46:09 -05:00
|
|
|
if fn := mp.waitunlockf; fn != nil {
|
|
|
|
|
ok := fn(gp, mp.waitlock)
|
|
|
|
|
mp.waitunlockf = nil
|
|
|
|
|
mp.waitlock = nil
|
2015-10-18 17:04:05 -07:00
|
|
|
if !ok {
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoUnpark(gp, 2)
|
|
|
|
|
}
|
|
|
|
|
casgstatus(gp, _Gwaiting, _Grunnable)
|
|
|
|
|
execute(gp, true) // Schedule it back, never returns.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
schedule()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func goschedImpl(gp *g) {
|
|
|
|
|
status := readgstatus(gp)
|
|
|
|
|
if status&^_Gscan != _Grunning {
|
|
|
|
|
dumpgstatus(gp)
|
|
|
|
|
throw("bad g status")
|
|
|
|
|
}
|
|
|
|
|
casgstatus(gp, _Grunning, _Grunnable)
|
|
|
|
|
dropg()
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
globrunqput(gp)
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
|
|
|
|
schedule()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Gosched continuation on g0.
|
|
|
|
|
func gosched_m(gp *g) {
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoSched()
|
|
|
|
|
}
|
|
|
|
|
goschedImpl(gp)
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-11 19:22:35 +08:00
|
|
|
// goschedguarded is a forbidden-states-avoided version of gosched_m.
|
2017-02-02 11:53:41 -05:00
|
|
|
func goschedguarded_m(gp *g) {
|
|
|
|
|
|
2019-10-04 18:54:00 -04:00
|
|
|
if !canPreemptM(gp.m) {
|
2017-02-02 11:53:41 -05:00
|
|
|
gogo(&gp.sched) // never return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoSched()
|
|
|
|
|
}
|
|
|
|
|
goschedImpl(gp)
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
func gopreempt_m(gp *g) {
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoPreempt()
|
|
|
|
|
}
|
|
|
|
|
goschedImpl(gp)
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-27 12:27:51 -04:00
|
|
|
// preemptPark parks gp and puts it in _Gpreempted.
|
|
|
|
|
//
|
|
|
|
|
//go:systemstack
|
|
|
|
|
func preemptPark(gp *g) {
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoPark(traceEvGoBlock, 0)
|
|
|
|
|
}
|
|
|
|
|
status := readgstatus(gp)
|
|
|
|
|
if status&^_Gscan != _Grunning {
|
|
|
|
|
dumpgstatus(gp)
|
|
|
|
|
throw("bad g status")
|
|
|
|
|
}
|
2021-05-06 11:38:46 -04:00
|
|
|
|
|
|
|
|
if gp.asyncSafePoint {
|
|
|
|
|
// Double-check that async preemption does not
|
|
|
|
|
// happen in SPWRITE assembly functions.
|
|
|
|
|
// isAsyncSafePoint must exclude this case.
|
|
|
|
|
f := findfunc(gp.sched.pc)
|
|
|
|
|
if !f.valid() {
|
|
|
|
|
throw("preempt at unknown pc")
|
|
|
|
|
}
|
|
|
|
|
if f.flag&funcFlag_SPWRITE != 0 {
|
|
|
|
|
println("runtime: unexpected SPWRITE function", funcname(f), "in async preempt")
|
|
|
|
|
throw("preempt SPWRITE")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-27 12:27:51 -04:00
|
|
|
// Transition from _Grunning to _Gscan|_Gpreempted. We can't
|
|
|
|
|
// be in _Grunning when we dropg because then we'd be running
|
|
|
|
|
// without an M, but the moment we're in _Gpreempted,
|
|
|
|
|
// something could claim this G before we've fully cleaned it
|
|
|
|
|
// up. Hence, we set the scan bit to lock down further
|
|
|
|
|
// transitions until we can dropg.
|
|
|
|
|
casGToPreemptScan(gp, _Grunning, _Gscan|_Gpreempted)
|
|
|
|
|
dropg()
|
|
|
|
|
casfrom_Gscanstatus(gp, _Gscan|_Gpreempted, _Gpreempted)
|
2019-11-08 10:30:24 -08:00
|
|
|
schedule()
|
|
|
|
|
}
|
2019-09-27 12:27:51 -04:00
|
|
|
|
2019-11-08 10:30:24 -08:00
|
|
|
// goyield is like Gosched, but it:
|
2019-12-17 16:40:46 -08:00
|
|
|
// - emits a GoPreempt trace event instead of a GoSched trace event
|
2019-11-08 10:30:24 -08:00
|
|
|
// - puts the current G on the runq of the current P instead of the globrunq
|
|
|
|
|
func goyield() {
|
|
|
|
|
checkTimeouts()
|
|
|
|
|
mcall(goyield_m)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func goyield_m(gp *g) {
|
2019-12-17 16:40:46 -08:00
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoPreempt()
|
|
|
|
|
}
|
2019-11-08 10:30:24 -08:00
|
|
|
pp := gp.m.p.ptr()
|
|
|
|
|
casgstatus(gp, _Grunning, _Grunnable)
|
|
|
|
|
dropg()
|
|
|
|
|
runqput(pp, gp, false)
|
2019-09-27 12:27:51 -04:00
|
|
|
schedule()
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Finishes execution of the current goroutine.
|
|
|
|
|
func goexit1() {
|
|
|
|
|
if raceenabled {
|
|
|
|
|
racegoend()
|
|
|
|
|
}
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoEnd()
|
|
|
|
|
}
|
|
|
|
|
mcall(goexit0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// goexit continuation on g0.
|
|
|
|
|
func goexit0(gp *g) {
|
2021-02-10 12:46:09 -05:00
|
|
|
mp := getg().m
|
|
|
|
|
pp := mp.p.ptr()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
casgstatus(gp, _Grunning, _Gdead)
|
2021-02-09 15:48:41 -05:00
|
|
|
gcController.addScannableStack(pp, -int64(gp.stack.hi-gp.stack.lo))
|
2018-08-13 16:08:03 -04:00
|
|
|
if isSystemGoroutine(gp, false) {
|
2022-07-20 17:48:19 -04:00
|
|
|
sched.ngsys.Add(-1)
|
2016-01-06 21:16:01 -05:00
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
gp.m = nil
|
2017-06-16 16:21:12 -04:00
|
|
|
locked := gp.lockedm != 0
|
2017-09-13 10:14:02 -07:00
|
|
|
gp.lockedm = 0
|
2021-02-10 12:46:09 -05:00
|
|
|
mp.lockedg = 0
|
2019-09-27 12:27:51 -04:00
|
|
|
gp.preemptStop = false
|
2015-10-18 17:04:05 -07:00
|
|
|
gp.paniconfault = false
|
|
|
|
|
gp._defer = nil // should be true already but just in case.
|
|
|
|
|
gp._panic = nil // non-nil for Goexit during panic. points at stack-allocated data.
|
|
|
|
|
gp.writebuf = nil
|
runtime: set G wait reason more consistently
Currently, wait reasons are set somewhat inconsistently. In a follow-up
CL, we're going to want to rely on the wait reason being there for
casgstatus, so the status quo isn't really going to work for that. Plus
this inconsistency means there are a whole bunch of cases where we could
be more specific about the G's status but aren't.
So, this change adds a new function, casGToWaiting which is like
casgstatus but also sets the wait reason. The goal is that by using this
API it'll be harder to forget to set a wait reason (or the lack thereof
will at least be explicit). This change then updates all casgstatus(gp,
..., _Gwaiting) calls to casGToWaiting(gp, ..., waitReasonX) instead.
For a number of these cases, we're missing a wait reason, and it
wouldn't hurt to add a wait reason for them, so this change also adds
those wait reasons.
For #49881.
Change-Id: Ia95e06ecb74ed17bb7bb94f1a362ebfe6bec1518
Reviewed-on: https://go-review.googlesource.com/c/go/+/427617
Reviewed-by: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2022-08-31 18:21:48 +00:00
|
|
|
gp.waitreason = waitReasonZero
|
2015-10-18 17:04:05 -07:00
|
|
|
gp.param = nil
|
2016-12-09 16:00:02 -05:00
|
|
|
gp.labels = nil
|
2017-02-17 10:17:42 -05:00
|
|
|
gp.timer = nil
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2016-07-01 11:31:04 -04:00
|
|
|
if gcBlackenEnabled != 0 && gp.gcAssistBytes > 0 {
|
|
|
|
|
// Flush assist credit to the global pool. This gives
|
|
|
|
|
// better information to pacing if the application is
|
|
|
|
|
// rapidly creating an exiting goroutines.
|
2021-09-24 16:06:07 +00:00
|
|
|
assistWorkPerByte := gcController.assistWorkPerByte.Load()
|
2020-07-23 20:48:06 +00:00
|
|
|
scanCredit := int64(assistWorkPerByte * float64(gp.gcAssistBytes))
|
2022-07-15 15:49:18 -04:00
|
|
|
gcController.bgScanCredit.Add(scanCredit)
|
2016-07-01 11:31:04 -04:00
|
|
|
gp.gcAssistBytes = 0
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
dropg()
|
|
|
|
|
|
2018-03-31 23:14:17 +02:00
|
|
|
if GOARCH == "wasm" { // no threads yet on wasm
|
2021-02-09 15:48:41 -05:00
|
|
|
gfput(pp, gp)
|
2018-03-31 23:14:17 +02:00
|
|
|
schedule() // never returns
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-10 12:46:09 -05:00
|
|
|
if mp.lockedInt != 0 {
|
|
|
|
|
print("invalid m->lockedInt = ", mp.lockedInt, "\n")
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("internal lockOSThread error")
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
gfput(pp, gp)
|
2017-06-16 16:21:12 -04:00
|
|
|
if locked {
|
|
|
|
|
// The goroutine may have locked this thread because
|
|
|
|
|
// it put it in an unusual kernel state. Kill it
|
|
|
|
|
// rather than returning it to the thread pool.
|
|
|
|
|
|
|
|
|
|
// Return to mstart, which will release the P and exit
|
|
|
|
|
// the thread.
|
2017-10-17 00:31:56 +02:00
|
|
|
if GOOS != "plan9" { // See golang.org/issue/22227.
|
2021-02-10 12:46:09 -05:00
|
|
|
gogo(&mp.g0.sched)
|
2018-12-07 00:07:43 +00:00
|
|
|
} else {
|
|
|
|
|
// Clear lockedExt on plan9 since we may end up re-using
|
|
|
|
|
// this thread.
|
2021-02-10 12:46:09 -05:00
|
|
|
mp.lockedExt = 0
|
2017-10-17 00:31:56 +02:00
|
|
|
}
|
2017-06-16 16:21:12 -04:00
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
schedule()
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-19 16:16:40 -04:00
|
|
|
// save updates getg().sched to refer to pc and sp so that a following
|
|
|
|
|
// gogo will restore pc and sp.
|
|
|
|
|
//
|
|
|
|
|
// save must not have write barriers because invoking a write barrier
|
|
|
|
|
// can clobber getg().sched.
|
|
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2015-10-18 17:04:05 -07:00
|
|
|
func save(pc, sp uintptr) {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp == gp.m.g0 || gp == gp.m.gsignal {
|
2021-02-15 09:25:55 -05:00
|
|
|
// m.g0.sched is special and must describe the context
|
|
|
|
|
// for exiting the thread. mstart1 writes to it directly.
|
|
|
|
|
// m.gsignal.sched should not be used at all.
|
|
|
|
|
// This check makes sure save calls do not accidentally
|
|
|
|
|
// run in contexts where they'd write to system g's.
|
|
|
|
|
throw("save on system g not allowed")
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.sched.pc = pc
|
|
|
|
|
gp.sched.sp = sp
|
|
|
|
|
gp.sched.lr = 0
|
|
|
|
|
gp.sched.ret = 0
|
2016-10-19 16:16:40 -04:00
|
|
|
// We need to ensure ctxt is zero, but can't have a write
|
|
|
|
|
// barrier here. However, it should always already be zero.
|
|
|
|
|
// Assert that.
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.sched.ctxt != nil {
|
2016-10-19 16:16:40 -04:00
|
|
|
badctxt()
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The goroutine g is about to enter a system call.
|
|
|
|
|
// Record that it's not using the cpu anymore.
|
|
|
|
|
// This is called only from the go syscall library and cgocall,
|
|
|
|
|
// not from the low-level system calls used by the runtime.
|
|
|
|
|
//
|
2021-02-15 11:26:58 -05:00
|
|
|
// Entersyscall cannot split the stack: the save must
|
2015-10-18 17:04:05 -07:00
|
|
|
// make g->sched refer to the caller's stack segment, because
|
|
|
|
|
// entersyscall is going to return immediately after.
|
|
|
|
|
//
|
|
|
|
|
// Nothing entersyscall calls can split the stack either.
|
|
|
|
|
// We cannot safely move the stack during an active call to syscall,
|
|
|
|
|
// because we do not know which of the uintptr arguments are
|
|
|
|
|
// really pointers (back into the stack).
|
|
|
|
|
// In practice, this means that we make the fast path run through
|
|
|
|
|
// entersyscall doing no-split things, and the slow path has to use systemstack
|
|
|
|
|
// to run bigger things on the system stack.
|
|
|
|
|
//
|
|
|
|
|
// reentersyscall is the entry point used by cgo callbacks, where explicitly
|
|
|
|
|
// saved SP and PC are restored. This is needed when exitsyscall will be called
|
|
|
|
|
// from a function further up in the call stack than the parent, as g->syscallsp
|
|
|
|
|
// must always point to a valid stack frame. entersyscall below is the normal
|
|
|
|
|
// entry point for syscalls, which obtains the SP and PC from the caller.
|
|
|
|
|
//
|
|
|
|
|
// Syscall tracing:
|
|
|
|
|
// At the start of a syscall we emit traceGoSysCall to capture the stack trace.
|
|
|
|
|
// If the syscall does not block, that is it, we do not emit any other events.
|
|
|
|
|
// If the syscall blocks (that is, P is retaken), retaker emits traceGoSysBlock;
|
|
|
|
|
// when syscall returns we emit traceGoSysExit and when the goroutine starts running
|
|
|
|
|
// (potentially instantly, if exitsyscallfast returns true) we emit traceGoStart.
|
|
|
|
|
// To ensure that traceGoSysExit is emitted strictly after traceGoSysBlock,
|
2021-02-11 11:15:53 -05:00
|
|
|
// we remember current value of syscalltick in m (gp.m.syscalltick = gp.m.p.ptr().syscalltick),
|
2015-10-18 17:04:05 -07:00
|
|
|
// whoever emits traceGoSysBlock increments p.syscalltick afterwards;
|
|
|
|
|
// and we wait for the increment before emitting traceGoSysExit.
|
|
|
|
|
// Note that the increment is done even if tracing is not enabled,
|
|
|
|
|
// because tracing can be enabled in the middle of syscall. We don't want the wait to hang.
|
|
|
|
|
//
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func reentersyscall(pc, sp uintptr) {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// Disable preemption because during this function g is in Gsyscall status,
|
|
|
|
|
// but can have inconsistent g->sched, do not let GC observe it.
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.locks++
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// Entersyscall must not call any function that might split/grow the stack.
|
|
|
|
|
// (See details in comment above.)
|
|
|
|
|
// Catch calls that might, by replacing the stack guard with something that
|
|
|
|
|
// will trip any stack check and leaving a flag to tell newstack to die.
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.stackguard0 = stackPreempt
|
|
|
|
|
gp.throwsplit = true
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// Leave SP around for GC and traceback.
|
|
|
|
|
save(pc, sp)
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.syscallsp = sp
|
|
|
|
|
gp.syscallpc = pc
|
|
|
|
|
casgstatus(gp, _Grunning, _Gsyscall)
|
|
|
|
|
if gp.syscallsp < gp.stack.lo || gp.stack.hi < gp.syscallsp {
|
2015-10-18 17:04:05 -07:00
|
|
|
systemstack(func() {
|
2021-02-11 11:15:53 -05:00
|
|
|
print("entersyscall inconsistent ", hex(gp.syscallsp), " [", hex(gp.stack.lo), ",", hex(gp.stack.hi), "]\n")
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("entersyscall")
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
systemstack(traceGoSysCall)
|
|
|
|
|
// systemstack itself clobbers g.sched.{pc,sp} and we might
|
|
|
|
|
// need them later when the G is genuinely blocked in a
|
|
|
|
|
// syscall
|
|
|
|
|
save(pc, sp)
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-25 15:39:07 -04:00
|
|
|
if sched.sysmonwait.Load() {
|
2015-10-18 17:04:05 -07:00
|
|
|
systemstack(entersyscall_sysmon)
|
|
|
|
|
save(pc, sp)
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.p.ptr().runSafePointFn != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
// runSafePointFn may stack split if run on this stack
|
|
|
|
|
systemstack(runSafePointFn)
|
|
|
|
|
save(pc, sp)
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.syscalltick = gp.m.p.ptr().syscalltick
|
|
|
|
|
gp.sysblocktraced = true
|
|
|
|
|
pp := gp.m.p.ptr()
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
pp.m = 0
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.oldp.set(pp)
|
|
|
|
|
gp.m.p = 0
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
atomic.Store(&pp.status, _Psyscall)
|
2022-07-25 15:31:03 -04:00
|
|
|
if sched.gcwaiting.Load() {
|
2015-10-18 17:04:05 -07:00
|
|
|
systemstack(entersyscall_gcwait)
|
|
|
|
|
save(pc, sp)
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.locks--
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Standard syscall entry used by the go syscall library and normal cgo calls.
|
2019-05-31 16:38:56 -04:00
|
|
|
//
|
2022-02-24 16:54:13 -05:00
|
|
|
// This is exported via linkname to assembly in the syscall package and x/sys.
|
2019-05-31 16:38:56 -04:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
2019-05-31 16:38:56 -04:00
|
|
|
//go:linkname entersyscall
|
2018-04-26 14:06:08 -04:00
|
|
|
func entersyscall() {
|
|
|
|
|
reentersyscall(getcallerpc(), getcallersp())
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func entersyscall_sysmon() {
|
|
|
|
|
lock(&sched.lock)
|
2022-07-25 15:39:07 -04:00
|
|
|
if sched.sysmonwait.Load() {
|
|
|
|
|
sched.sysmonwait.Store(false)
|
2015-10-18 17:04:05 -07:00
|
|
|
notewakeup(&sched.sysmonnote)
|
|
|
|
|
}
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func entersyscall_gcwait() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
pp := gp.m.oldp.ptr()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
lock(&sched.lock)
|
2021-02-09 15:48:41 -05:00
|
|
|
if sched.stopwait > 0 && atomic.Cas(&pp.status, _Psyscall, _Pgcstop) {
|
2015-10-18 17:04:05 -07:00
|
|
|
if trace.enabled {
|
2021-02-09 15:48:41 -05:00
|
|
|
traceGoSysBlock(pp)
|
|
|
|
|
traceProcStop(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.syscalltick++
|
2015-10-18 17:04:05 -07:00
|
|
|
if sched.stopwait--; sched.stopwait == 0 {
|
|
|
|
|
notewakeup(&sched.stopnote)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The same as entersyscall(), but with a hint that the syscall is blocking.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
2018-04-26 14:06:08 -04:00
|
|
|
func entersyscallblock() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.locks++ // see comment in entersyscall
|
|
|
|
|
gp.throwsplit = true
|
|
|
|
|
gp.stackguard0 = stackPreempt // see comment in entersyscall
|
|
|
|
|
gp.m.syscalltick = gp.m.p.ptr().syscalltick
|
|
|
|
|
gp.sysblocktraced = true
|
|
|
|
|
gp.m.p.ptr().syscalltick++
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// Leave SP around for GC and traceback.
|
2017-09-22 15:16:26 -04:00
|
|
|
pc := getcallerpc()
|
2018-04-26 14:06:08 -04:00
|
|
|
sp := getcallersp()
|
2015-10-18 17:04:05 -07:00
|
|
|
save(pc, sp)
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.syscallsp = gp.sched.sp
|
|
|
|
|
gp.syscallpc = gp.sched.pc
|
|
|
|
|
if gp.syscallsp < gp.stack.lo || gp.stack.hi < gp.syscallsp {
|
2015-10-18 17:04:05 -07:00
|
|
|
sp1 := sp
|
2021-02-11 11:15:53 -05:00
|
|
|
sp2 := gp.sched.sp
|
|
|
|
|
sp3 := gp.syscallsp
|
2015-10-18 17:04:05 -07:00
|
|
|
systemstack(func() {
|
2021-02-11 11:15:53 -05:00
|
|
|
print("entersyscallblock inconsistent ", hex(sp1), " ", hex(sp2), " ", hex(sp3), " [", hex(gp.stack.lo), ",", hex(gp.stack.hi), "]\n")
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("entersyscallblock")
|
|
|
|
|
})
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
casgstatus(gp, _Grunning, _Gsyscall)
|
|
|
|
|
if gp.syscallsp < gp.stack.lo || gp.stack.hi < gp.syscallsp {
|
2015-10-18 17:04:05 -07:00
|
|
|
systemstack(func() {
|
2021-02-11 11:15:53 -05:00
|
|
|
print("entersyscallblock inconsistent ", hex(sp), " ", hex(gp.sched.sp), " ", hex(gp.syscallsp), " [", hex(gp.stack.lo), ",", hex(gp.stack.hi), "]\n")
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("entersyscallblock")
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
systemstack(entersyscallblock_handoff)
|
|
|
|
|
|
|
|
|
|
// Resave for traceback during blocked call.
|
2018-04-26 14:06:08 -04:00
|
|
|
save(getcallerpc(), getcallersp())
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.locks--
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func entersyscallblock_handoff() {
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoSysCall()
|
|
|
|
|
traceGoSysBlock(getg().m.p.ptr())
|
|
|
|
|
}
|
|
|
|
|
handoffp(releasep())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The goroutine g exited its system call.
|
|
|
|
|
// Arrange for it to run on a cpu again.
|
|
|
|
|
// This is called only from the go syscall library, not
|
2016-02-20 23:24:27 -05:00
|
|
|
// from the low-level system calls used by the runtime.
|
2016-10-10 16:46:28 -04:00
|
|
|
//
|
|
|
|
|
// Write barriers are not allowed because our P may have been stolen.
|
|
|
|
|
//
|
2019-05-31 16:38:56 -04:00
|
|
|
// This is exported via linkname to assembly in the syscall package.
|
|
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
2016-10-10 16:46:28 -04:00
|
|
|
//go:nowritebarrierrec
|
2019-05-31 16:38:56 -04:00
|
|
|
//go:linkname exitsyscall
|
2018-04-26 14:06:08 -04:00
|
|
|
func exitsyscall() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.locks++ // see comment in entersyscall
|
|
|
|
|
if getcallersp() > gp.syscallsp {
|
2018-01-12 12:39:22 -05:00
|
|
|
throw("exitsyscall: syscall frame is no longer valid")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.waitsince = 0
|
|
|
|
|
oldp := gp.m.oldp.ptr()
|
|
|
|
|
gp.m.oldp = 0
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
if exitsyscallfast(oldp) {
|
2022-02-18 10:56:16 -08:00
|
|
|
// When exitsyscallfast returns success, we have a P so can now use
|
|
|
|
|
// write barriers
|
|
|
|
|
if goroutineProfile.active {
|
|
|
|
|
// Make sure that gp has had its stack written out to the goroutine
|
|
|
|
|
// profile, exactly as it was when the goroutine profiler first
|
|
|
|
|
// stopped the world.
|
|
|
|
|
systemstack(func() {
|
2021-02-11 11:15:53 -05:00
|
|
|
tryRecordGoroutineProfileWB(gp)
|
2022-02-18 10:56:16 -08:00
|
|
|
})
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
if trace.enabled {
|
2021-02-11 11:15:53 -05:00
|
|
|
if oldp != gp.m.p.ptr() || gp.m.syscalltick != gp.m.p.ptr().syscalltick {
|
2015-10-18 17:04:05 -07:00
|
|
|
systemstack(traceGoStart)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// There's a cpu for us, so we can run.
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.p.ptr().syscalltick++
|
2015-10-18 17:04:05 -07:00
|
|
|
// We need to cas the status and scan before resuming...
|
2021-02-11 11:15:53 -05:00
|
|
|
casgstatus(gp, _Gsyscall, _Grunning)
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// Garbage collector isn't running (since we are),
|
|
|
|
|
// so okay to clear syscallsp.
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.syscallsp = 0
|
|
|
|
|
gp.m.locks--
|
|
|
|
|
if gp.preempt {
|
2015-10-18 17:04:05 -07:00
|
|
|
// restore the preemption request in case we've cleared it in newstack
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.stackguard0 = stackPreempt
|
2015-10-18 17:04:05 -07:00
|
|
|
} else {
|
|
|
|
|
// otherwise restore the real _StackGuard, we've spoiled it in entersyscall/entersyscallblock
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.stackguard0 = gp.stack.lo + _StackGuard
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.throwsplit = false
|
2018-09-11 11:28:24 -04:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
if sched.disable.user && !schedEnabled(gp) {
|
2018-09-11 11:28:24 -04:00
|
|
|
// Scheduling of this goroutine is disabled.
|
|
|
|
|
Gosched()
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.sysexitticks = 0
|
2015-10-18 17:04:05 -07:00
|
|
|
if trace.enabled {
|
|
|
|
|
// Wait till traceGoSysBlock event is emitted.
|
|
|
|
|
// This ensures consistency of the trace (the goroutine is started after it is blocked).
|
2021-02-11 11:15:53 -05:00
|
|
|
for oldp != nil && oldp.syscalltick == gp.m.syscalltick {
|
2015-10-18 17:04:05 -07:00
|
|
|
osyield()
|
|
|
|
|
}
|
|
|
|
|
// We can't trace syscall exit right now because we don't have a P.
|
|
|
|
|
// 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.
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.sysexitticks = cputicks()
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.locks--
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// Call the scheduler.
|
|
|
|
|
mcall(exitsyscall0)
|
|
|
|
|
|
|
|
|
|
// Scheduler returned, so we're allowed to run now.
|
|
|
|
|
// Delete the syscallsp information that we left for
|
|
|
|
|
// the garbage collector during the system call.
|
|
|
|
|
// Must wait until now because until gosched returns
|
|
|
|
|
// we don't know for sure that the garbage collector
|
|
|
|
|
// is not running.
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.syscallsp = 0
|
|
|
|
|
gp.m.p.ptr().syscalltick++
|
|
|
|
|
gp.throwsplit = false
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:nosplit
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
func exitsyscallfast(oldp *p) bool {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// Freezetheworld sets stopwait but does not retake P's.
|
|
|
|
|
if sched.stopwait == freezeStopWait {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to re-acquire the last P.
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
if oldp != nil && oldp.status == _Psyscall && atomic.Cas(&oldp.status, _Psyscall, _Pidle) {
|
2015-10-18 17:04:05 -07:00
|
|
|
// There's a cpu for us, so we can run.
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
wirep(oldp)
|
2016-10-10 16:46:28 -04:00
|
|
|
exitsyscallfast_reacquired()
|
2015-10-18 17:04:05 -07:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to get any other idle P.
|
|
|
|
|
if sched.pidle != 0 {
|
|
|
|
|
var ok bool
|
|
|
|
|
systemstack(func() {
|
|
|
|
|
ok = exitsyscallfast_pidle()
|
|
|
|
|
if ok && trace.enabled {
|
|
|
|
|
if oldp != nil {
|
|
|
|
|
// Wait till traceGoSysBlock event is emitted.
|
|
|
|
|
// This ensures consistency of the trace (the goroutine is started after it is blocked).
|
2021-02-11 11:15:53 -05:00
|
|
|
for oldp.syscalltick == gp.m.syscalltick {
|
2015-10-18 17:04:05 -07:00
|
|
|
osyield()
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-04-05 15:29:14 +02:00
|
|
|
traceGoSysExit(0)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
if ok {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-10 16:46:28 -04:00
|
|
|
// exitsyscallfast_reacquired is the exitsyscall path on which this G
|
|
|
|
|
// has successfully reacquired the P it was running on before the
|
|
|
|
|
// syscall.
|
|
|
|
|
//
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func exitsyscallfast_reacquired() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
if gp.m.syscalltick != gp.m.p.ptr().syscalltick {
|
2016-10-10 16:46:28 -04:00
|
|
|
if trace.enabled {
|
2021-02-11 11:15:53 -05:00
|
|
|
// The p was retaken and then enter into syscall again (since gp.m.syscalltick has changed).
|
2016-10-10 16:46:28 -04:00
|
|
|
// 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.
|
2021-02-11 11:15:53 -05:00
|
|
|
traceGoSysBlock(gp.m.p.ptr())
|
2016-10-10 16:46:28 -04:00
|
|
|
// Denote completion of the current syscall.
|
|
|
|
|
traceGoSysExit(0)
|
|
|
|
|
})
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.p.ptr().syscalltick++
|
2016-10-10 16:46:28 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
func exitsyscallfast_pidle() bool {
|
|
|
|
|
lock(&sched.lock)
|
2021-02-09 15:48:41 -05:00
|
|
|
pp, _ := pidleget(0)
|
2022-07-25 15:39:07 -04:00
|
|
|
if pp != nil && sched.sysmonwait.Load() {
|
|
|
|
|
sched.sysmonwait.Store(false)
|
2015-10-18 17:04:05 -07:00
|
|
|
notewakeup(&sched.sysmonnote)
|
|
|
|
|
}
|
|
|
|
|
unlock(&sched.lock)
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp != nil {
|
|
|
|
|
acquirep(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// exitsyscall slow path on g0.
|
|
|
|
|
// Failed to acquire P, enqueue gp as runnable.
|
2016-10-10 17:14:14 -04:00
|
|
|
//
|
runtime: fix G passed to schedEnabled and cleanup
exitsyscall0 contains two G variables: _g_ and gp. _g_ is the active G,
g0, while gp is the G to run (which just exited from a syscall).
It is passing _g_ to schedEnabled, which is incorrect; we are about to
execute gp, so that is what we should be checking the schedulability of.
While this is incorrect and should be fixed, I don't think it has ever
caused a problem in practice:
* g0 does not have g.startpc set, so schedEnabled simplifies to
just !sched.disable.user.
* This is correct provided gp is never a system goroutine.
* As far as I know, system goroutines never use entersyscall /
exitsyscall.
As far I can tell, this was a simple copy/paste error from exitsyscall,
where variable _g_ is the G to run.
While we are here, eliminate _g_ entirely, as the one other use is
identical to using gp.
Change-Id: I5df98a34569238b89ab13ff7012cd756fefb10dc
Reviewed-on: https://go-review.googlesource.com/c/go/+/291329
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2021-02-11 10:44:34 -05:00
|
|
|
// Called via mcall, so gp is the calling g from this M.
|
|
|
|
|
//
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2015-10-18 17:04:05 -07:00
|
|
|
func exitsyscall0(gp *g) {
|
|
|
|
|
casgstatus(gp, _Gsyscall, _Grunnable)
|
|
|
|
|
dropg()
|
|
|
|
|
lock(&sched.lock)
|
2021-02-09 15:48:41 -05:00
|
|
|
var pp *p
|
runtime: fix G passed to schedEnabled and cleanup
exitsyscall0 contains two G variables: _g_ and gp. _g_ is the active G,
g0, while gp is the G to run (which just exited from a syscall).
It is passing _g_ to schedEnabled, which is incorrect; we are about to
execute gp, so that is what we should be checking the schedulability of.
While this is incorrect and should be fixed, I don't think it has ever
caused a problem in practice:
* g0 does not have g.startpc set, so schedEnabled simplifies to
just !sched.disable.user.
* This is correct provided gp is never a system goroutine.
* As far as I know, system goroutines never use entersyscall /
exitsyscall.
As far I can tell, this was a simple copy/paste error from exitsyscall,
where variable _g_ is the G to run.
While we are here, eliminate _g_ entirely, as the one other use is
identical to using gp.
Change-Id: I5df98a34569238b89ab13ff7012cd756fefb10dc
Reviewed-on: https://go-review.googlesource.com/c/go/+/291329
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2021-02-11 10:44:34 -05:00
|
|
|
if schedEnabled(gp) {
|
2021-02-09 15:48:41 -05:00
|
|
|
pp, _ = pidleget(0)
|
2018-09-11 11:28:24 -04:00
|
|
|
}
|
2021-06-02 17:44:43 -04:00
|
|
|
var locked bool
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp == nil {
|
2015-10-18 17:04:05 -07:00
|
|
|
globrunqput(gp)
|
2021-06-02 17:44:43 -04:00
|
|
|
|
|
|
|
|
// Below, we stoplockedm if gp is locked. globrunqput releases
|
|
|
|
|
// ownership of gp, so we must check if gp is locked prior to
|
|
|
|
|
// committing the release by unlocking sched.lock, otherwise we
|
|
|
|
|
// could race with another M transitioning gp from unlocked to
|
|
|
|
|
// locked.
|
|
|
|
|
locked = gp.lockedm != 0
|
2022-07-25 15:39:07 -04:00
|
|
|
} else if sched.sysmonwait.Load() {
|
|
|
|
|
sched.sysmonwait.Store(false)
|
2015-10-18 17:04:05 -07:00
|
|
|
notewakeup(&sched.sysmonnote)
|
|
|
|
|
}
|
|
|
|
|
unlock(&sched.lock)
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp != nil {
|
|
|
|
|
acquirep(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
execute(gp, false) // Never returns.
|
|
|
|
|
}
|
2021-06-02 17:44:43 -04:00
|
|
|
if locked {
|
2015-10-18 17:04:05 -07:00
|
|
|
// Wait until another thread schedules gp and so m again.
|
runtime: fix G passed to schedEnabled and cleanup
exitsyscall0 contains two G variables: _g_ and gp. _g_ is the active G,
g0, while gp is the G to run (which just exited from a syscall).
It is passing _g_ to schedEnabled, which is incorrect; we are about to
execute gp, so that is what we should be checking the schedulability of.
While this is incorrect and should be fixed, I don't think it has ever
caused a problem in practice:
* g0 does not have g.startpc set, so schedEnabled simplifies to
just !sched.disable.user.
* This is correct provided gp is never a system goroutine.
* As far as I know, system goroutines never use entersyscall /
exitsyscall.
As far I can tell, this was a simple copy/paste error from exitsyscall,
where variable _g_ is the G to run.
While we are here, eliminate _g_ entirely, as the one other use is
identical to using gp.
Change-Id: I5df98a34569238b89ab13ff7012cd756fefb10dc
Reviewed-on: https://go-review.googlesource.com/c/go/+/291329
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2021-02-11 10:44:34 -05:00
|
|
|
//
|
|
|
|
|
// N.B. lockedm must be this M, as this g was running on this M
|
|
|
|
|
// before entersyscall.
|
2015-10-18 17:04:05 -07:00
|
|
|
stoplockedm()
|
|
|
|
|
execute(gp, false) // Never returns.
|
|
|
|
|
}
|
|
|
|
|
stopm()
|
|
|
|
|
schedule() // Never returns.
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-11 15:34:29 -07:00
|
|
|
// Called from syscall package before fork.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2021-08-11 15:34:29 -07:00
|
|
|
//go:linkname syscall_runtime_BeforeFork syscall.runtime_BeforeFork
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func syscall_runtime_BeforeFork() {
|
2015-10-18 17:04:05 -07:00
|
|
|
gp := getg().m.curg
|
|
|
|
|
|
2017-06-12 22:36:03 -07:00
|
|
|
// 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.
|
2015-10-18 17:04:05 -07:00
|
|
|
gp.m.locks++
|
2020-10-27 16:09:40 -07:00
|
|
|
sigsave(&gp.m.sigmask)
|
2020-11-12 21:19:52 -08:00
|
|
|
sigblock(false)
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// This function is called before fork in syscall package.
|
|
|
|
|
// Code between fork and exec must not allocate memory nor even try to grow stack.
|
|
|
|
|
// Here we spoil g->_StackGuard to reliably detect any attempts to grow stack.
|
|
|
|
|
// runtime_AfterFork will undo this in parent process, but not in child.
|
|
|
|
|
gp.stackguard0 = stackFork
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-11 15:34:29 -07:00
|
|
|
// Called from syscall package after fork in parent.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2021-08-11 15:34:29 -07:00
|
|
|
//go:linkname syscall_runtime_AfterFork syscall.runtime_AfterFork
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
2021-08-11 15:34:29 -07:00
|
|
|
func syscall_runtime_AfterFork() {
|
2015-10-18 17:04:05 -07:00
|
|
|
gp := getg().m.curg
|
|
|
|
|
|
2017-06-12 22:36:03 -07:00
|
|
|
// See the comments in beforefork.
|
2015-10-18 17:04:05 -07:00
|
|
|
gp.stackguard0 = gp.stack.lo + _StackGuard
|
|
|
|
|
|
2017-06-12 22:36:03 -07:00
|
|
|
msigrestore(gp.m.sigmask)
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
gp.m.locks--
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-19 23:42:27 -07:00
|
|
|
// 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
|
|
|
|
|
|
2017-06-12 22:36:03 -07:00
|
|
|
// 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.
|
2017-07-19 23:42:27 -07:00
|
|
|
//
|
|
|
|
|
// 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.
|
|
|
|
|
//
|
2017-06-12 22:36:03 -07:00
|
|
|
//go:linkname syscall_runtime_AfterForkInChild syscall.runtime_AfterForkInChild
|
|
|
|
|
//go:nosplit
|
|
|
|
|
//go:nowritebarrierrec
|
|
|
|
|
func syscall_runtime_AfterForkInChild() {
|
2017-07-19 23:42:27 -07:00
|
|
|
// 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
|
|
|
|
|
|
2017-06-12 22:36:03 -07:00
|
|
|
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)
|
2017-07-19 23:42:27 -07:00
|
|
|
|
|
|
|
|
inForkedChild = false
|
2017-06-12 22:36:03 -07:00
|
|
|
}
|
|
|
|
|
|
2020-10-15 14:39:12 -07:00
|
|
|
// pendingPreemptSignals is the number of preemption signals
|
|
|
|
|
// that have been sent but not received. This is only used on Darwin.
|
|
|
|
|
// For #41702.
|
2022-07-14 16:58:25 -04:00
|
|
|
var pendingPreemptSignals atomic.Int32
|
2020-10-15 14:39:12 -07:00
|
|
|
|
2017-05-20 17:22:36 +01:00
|
|
|
// Called from syscall package before Exec.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2017-05-20 17:22:36 +01:00
|
|
|
//go:linkname syscall_runtime_BeforeExec syscall.runtime_BeforeExec
|
|
|
|
|
func syscall_runtime_BeforeExec() {
|
2017-06-28 15:58:59 -04:00
|
|
|
// Prevent thread creation during exec.
|
|
|
|
|
execLock.lock()
|
2020-10-15 14:39:12 -07:00
|
|
|
|
|
|
|
|
// On Darwin, wait for all pending preemption signals to
|
|
|
|
|
// be received. See issue #41702.
|
2020-12-03 16:53:30 -05:00
|
|
|
if GOOS == "darwin" || GOOS == "ios" {
|
2022-07-14 16:58:25 -04:00
|
|
|
for pendingPreemptSignals.Load() > 0 {
|
2020-10-15 14:39:12 -07:00
|
|
|
osyield()
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-20 17:22:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from syscall package after Exec.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2017-05-20 17:22:36 +01:00
|
|
|
//go:linkname syscall_runtime_AfterExec syscall.runtime_AfterExec
|
|
|
|
|
func syscall_runtime_AfterExec() {
|
2017-06-28 15:58:59 -04:00
|
|
|
execLock.unlock()
|
2017-05-20 17:22:36 +01:00
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Allocate a new g, with a stack big enough for stacksize bytes.
|
|
|
|
|
func malg(stacksize int32) *g {
|
|
|
|
|
newg := new(g)
|
|
|
|
|
if stacksize >= 0 {
|
|
|
|
|
stacksize = round2(_StackSystem + stacksize)
|
|
|
|
|
systemstack(func() {
|
2017-02-09 14:03:49 -05:00
|
|
|
newg.stack = stackalloc(uint32(stacksize))
|
2015-10-18 17:04:05 -07:00
|
|
|
})
|
|
|
|
|
newg.stackguard0 = newg.stack.lo + _StackGuard
|
|
|
|
|
newg.stackguard1 = ^uintptr(0)
|
runtime: save/fetch g register during VDSO on ARM and ARM64
On ARM and ARM64, during a VDSO call, the g register may be
temporarily clobbered by the VDSO code. If a signal is received
during the execution of VDSO code, we may not find a valid g
reading the g register. In CL 192937, we conservatively assume
g is nil. But this approach has a problem: we cannot handle
the signal in this case. Further, if the signal is not a
profiling signal, we'll call badsignal, which calls needm, which
wants to get an extra m, but we don't have one in a non-cgo
binary, which cuases the program to hang.
This is even more of a problem with async preemption, where we
will receive more signals than before. I ran into this problem
while working on async preemption support on ARM64.
In this CL, before making a VDSO call, we save the g on the
gsignal stack. When we receive a signal, we will be running on
the gsignal stack, so we can fetch the g from there and move on.
We probably want to do the same for PPC64. Currently we rely on
that the VDSO code doesn't actually clobber the g register, but
this is not guaranteed and we don't have control with.
Idea from discussion with Dan Cross and Austin.
Should fix #34391.
Change-Id: Idbefc5e4c2f4373192c2be797be0140ae08b26e3
Reviewed-on: https://go-review.googlesource.com/c/go/+/202759
Run-TryBot: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Austin Clements <austin@google.com>
2019-10-23 11:42:23 -04:00
|
|
|
// Clear the bottom word of the stack. We record g
|
|
|
|
|
// there on gsignal stack during VDSO on ARM and ARM64.
|
|
|
|
|
*(*uintptr)(unsafe.Pointer(newg.stack.lo)) = 0
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
return newg
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-04 18:11:59 -04:00
|
|
|
// Create a new g running fn.
|
2015-10-18 17:04:05 -07:00
|
|
|
// Put it on the queue of g's waiting to run.
|
|
|
|
|
// The compiler turns a go statement into a call to this.
|
2021-06-04 18:11:59 -04:00
|
|
|
func newproc(fn *funcval) {
|
2018-04-03 21:35:46 -04:00
|
|
|
gp := getg()
|
2017-09-22 15:16:26 -04:00
|
|
|
pc := getcallerpc()
|
2015-10-18 17:04:05 -07:00
|
|
|
systemstack(func() {
|
2021-06-04 18:11:59 -04:00
|
|
|
newg := newproc1(fn, gp, pc)
|
2020-04-15 13:56:05 -04:00
|
|
|
|
2021-02-09 15:48:41 -05:00
|
|
|
pp := getg().m.p.ptr()
|
|
|
|
|
runqput(pp, newg, true)
|
2020-04-15 13:56:05 -04:00
|
|
|
|
2020-04-28 20:54:31 -04:00
|
|
|
if mainStarted {
|
2020-04-15 13:56:05 -04:00
|
|
|
wakep()
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-04 18:11:59 -04:00
|
|
|
// Create a new g in state _Grunnable, starting at fn. callerpc is the
|
|
|
|
|
// address of the go statement that created this. The caller is responsible
|
|
|
|
|
// for adding the new g to the scheduler.
|
|
|
|
|
func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
|
2015-10-18 17:04:05 -07:00
|
|
|
if fn == nil {
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("go of nil func value")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2021-02-10 12:46:09 -05:00
|
|
|
mp := acquirem() // disable preemption because we hold M and P in local vars.
|
|
|
|
|
pp := mp.p.ptr()
|
2021-02-09 15:48:41 -05:00
|
|
|
newg := gfget(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
if newg == nil {
|
|
|
|
|
newg = malg(_StackMin)
|
|
|
|
|
casgstatus(newg, _Gidle, _Gdead)
|
|
|
|
|
allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
|
|
|
|
|
}
|
|
|
|
|
if newg.stack.hi == 0 {
|
|
|
|
|
throw("newproc1: newg missing stack")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if readgstatus(newg) != _Gdead {
|
|
|
|
|
throw("newproc1: new g is not Gdead")
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-16 23:05:44 +00:00
|
|
|
totalSize := uintptr(4*goarch.PtrSize + sys.MinFrameSize) // extra space in case of reads slightly beyond frame
|
2021-06-04 17:04:46 -04:00
|
|
|
totalSize = alignUp(totalSize, sys.StackAlign)
|
2015-10-18 17:04:05 -07:00
|
|
|
sp := newg.stack.hi - totalSize
|
|
|
|
|
spArg := sp
|
|
|
|
|
if usesLR {
|
|
|
|
|
// caller's LR
|
2016-10-26 14:34:20 -04:00
|
|
|
*(*uintptr)(unsafe.Pointer(sp)) = 0
|
cmd/compile, cmd/link, runtime: on ppc64x, maintain the TOC pointer in R2 when compiling PIC
The PowerPC ISA does not have a PC-relative load instruction, which poses
obvious challenges when generating position-independent code. The way the ELFv2
ABI addresses this is to specify that r2 points to a per "module" (shared
library or executable) TOC pointer. Maintaining this pointer requires
cooperation between codegen and the system linker:
* Non-leaf functions leave space on the stack at r1+24 to save the TOC pointer.
* A call to a function that *might* have to go via a PLT stub must be followed
by a nop instruction that the system linker can replace with "ld r1, 24(r1)"
to restore the TOC pointer (only when dynamically linking Go code).
* When calling a function via a function pointer, the address of the function
must be in r12, and the first couple of instructions (the "global entry
point") of the called function use this to derive the address of the TOC
for the module it is in.
* When calling a function that is implemented in the same module, the system
linker adjusts the call to skip over the instructions mentioned above (the
"local entry point"), assuming that r2 is already correctly set.
So this changeset adds the global entry point instructions, sets the metadata so
the system linker knows where the local entry point is, inserts code to save the
TOC pointer at 24(r1), adds a nop after any call not known to be local and copes
with the odd non-local code transfer in the runtime (e.g. the stuff around
jmpdefer). It does not actually compile PIC yet.
Change-Id: I7522e22bdfd2f891745a900c60254fe9e372c854
Reviewed-on: https://go-review.googlesource.com/15967
Reviewed-by: Russ Cox <rsc@golang.org>
2015-10-16 15:42:09 +13:00
|
|
|
prepGoExitFrame(sp)
|
2015-11-11 12:39:30 -05:00
|
|
|
spArg += sys.MinFrameSize
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2016-10-17 18:41:56 -04:00
|
|
|
memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
|
2015-10-18 17:04:05 -07:00
|
|
|
newg.sched.sp = sp
|
|
|
|
|
newg.stktopsp = sp
|
2021-04-02 13:24:35 -04:00
|
|
|
newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
|
2015-10-18 17:04:05 -07:00
|
|
|
newg.sched.g = guintptr(unsafe.Pointer(newg))
|
|
|
|
|
gostartcallfn(&newg.sched, fn)
|
2022-09-28 14:44:56 -04:00
|
|
|
newg.parentGoid = callergp.goid
|
2015-10-18 17:04:05 -07:00
|
|
|
newg.gopc = callerpc
|
2018-04-03 21:35:46 -04:00
|
|
|
newg.ancestors = saveAncestors(callergp)
|
2015-10-18 17:04:05 -07:00
|
|
|
newg.startpc = fn.fn
|
2018-08-13 16:08:03 -04:00
|
|
|
if isSystemGoroutine(newg, false) {
|
2022-07-20 17:48:19 -04:00
|
|
|
sched.ngsys.Add(1)
|
2021-12-07 15:59:14 -05:00
|
|
|
} else {
|
|
|
|
|
// Only user goroutines inherit pprof labels.
|
2021-02-10 12:46:09 -05:00
|
|
|
if mp.curg != nil {
|
|
|
|
|
newg.labels = mp.curg.labels
|
2021-12-07 15:59:14 -05:00
|
|
|
}
|
2022-02-18 10:56:16 -08:00
|
|
|
if goroutineProfile.active {
|
|
|
|
|
// A concurrent goroutine profile is running. It should include
|
|
|
|
|
// exactly the set of goroutines that were alive when the goroutine
|
|
|
|
|
// profiler first stopped the world. That does not include newg, so
|
|
|
|
|
// mark it as not needing a profile before transitioning it from
|
|
|
|
|
// _Gdead.
|
|
|
|
|
newg.goroutineProfiled.Store(goroutineProfileSatisfied)
|
|
|
|
|
}
|
2016-01-06 21:16:01 -05:00
|
|
|
}
|
2020-08-07 16:28:35 +00:00
|
|
|
// Track initial transition?
|
|
|
|
|
newg.trackingSeq = uint8(fastrand())
|
|
|
|
|
if newg.trackingSeq%gTrackingPeriod == 0 {
|
|
|
|
|
newg.tracking = true
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
casgstatus(newg, _Gdead, _Grunnable)
|
2021-02-09 15:48:41 -05:00
|
|
|
gcController.addScannableStack(pp, int64(newg.stack.hi-newg.stack.lo))
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp.goidcache == pp.goidcacheend {
|
2015-10-18 17:04:05 -07:00
|
|
|
// Sched.goidgen is the last allocated id,
|
|
|
|
|
// this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].
|
|
|
|
|
// At startup sched.goidgen=0, so main goroutine receives goid=1.
|
2022-07-19 13:34:29 -04:00
|
|
|
pp.goidcache = sched.goidgen.Add(_GoidCacheBatch)
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.goidcache -= _GoidCacheBatch - 1
|
|
|
|
|
pp.goidcacheend = pp.goidcache + _GoidCacheBatch
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2022-07-19 13:49:33 -04:00
|
|
|
newg.goid = pp.goidcache
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.goidcache++
|
2015-10-18 17:04:05 -07:00
|
|
|
if raceenabled {
|
|
|
|
|
newg.racectx = racegostart(callerpc)
|
2022-02-14 12:16:22 -08:00
|
|
|
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))
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoCreate(newg, newg.startpc)
|
|
|
|
|
}
|
2021-02-10 12:46:09 -05:00
|
|
|
releasem(mp)
|
2020-04-15 13:56:05 -04:00
|
|
|
|
|
|
|
|
return newg
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2018-04-03 21:35:46 -04:00
|
|
|
// saveAncestors copies previous ancestors of the given caller g and
|
2023-02-07 09:09:24 +00:00
|
|
|
// includes info for the current caller into a new set of tracebacks for
|
2018-04-03 21:35:46 -04:00
|
|
|
// a g being created.
|
|
|
|
|
func saveAncestors(callergp *g) *[]ancestorInfo {
|
|
|
|
|
// Copy all prior info, except for the root goroutine (goid 0).
|
|
|
|
|
if debug.tracebackancestors <= 0 || callergp.goid == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
var callerAncestors []ancestorInfo
|
|
|
|
|
if callergp.ancestors != nil {
|
|
|
|
|
callerAncestors = *callergp.ancestors
|
|
|
|
|
}
|
|
|
|
|
n := int32(len(callerAncestors)) + 1
|
|
|
|
|
if n > debug.tracebackancestors {
|
|
|
|
|
n = debug.tracebackancestors
|
|
|
|
|
}
|
|
|
|
|
ancestors := make([]ancestorInfo, n)
|
|
|
|
|
copy(ancestors[1:], callerAncestors)
|
|
|
|
|
|
2023-03-13 14:02:16 -04:00
|
|
|
var pcs [tracebackInnerFrames]uintptr
|
2018-04-03 21:35:46 -04:00
|
|
|
npcs := gcallers(callergp, 0, pcs[:])
|
|
|
|
|
ipcs := make([]uintptr, npcs)
|
|
|
|
|
copy(ipcs, pcs[:])
|
|
|
|
|
ancestors[0] = ancestorInfo{
|
|
|
|
|
pcs: ipcs,
|
|
|
|
|
goid: callergp.goid,
|
|
|
|
|
gopc: callergp.gopc,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ancestorsp := new([]ancestorInfo)
|
|
|
|
|
*ancestorsp = ancestors
|
|
|
|
|
return ancestorsp
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Put on gfree list.
|
|
|
|
|
// If local list is too long, transfer a batch to the global list.
|
2021-02-09 15:48:41 -05:00
|
|
|
func gfput(pp *p, gp *g) {
|
2015-10-18 17:04:05 -07:00
|
|
|
if readgstatus(gp) != _Gdead {
|
|
|
|
|
throw("gfput: bad status (not Gdead)")
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-09 14:11:13 -05:00
|
|
|
stksize := gp.stack.hi - gp.stack.lo
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-08-28 15:50:52 -07:00
|
|
|
if stksize != uintptr(startingStackSize) {
|
2015-10-18 17:04:05 -07:00
|
|
|
// non-standard stack size - free it.
|
2017-02-09 14:11:13 -05:00
|
|
|
stackfree(gp.stack)
|
2015-10-18 17:04:05 -07:00
|
|
|
gp.stack.lo = 0
|
|
|
|
|
gp.stack.hi = 0
|
|
|
|
|
gp.stackguard0 = 0
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.gFree.push(gp)
|
|
|
|
|
pp.gFree.n++
|
|
|
|
|
if pp.gFree.n >= 64 {
|
2020-11-23 15:42:48 +08:00
|
|
|
var (
|
|
|
|
|
inc int32
|
|
|
|
|
stackQ gQueue
|
|
|
|
|
noStackQ gQueue
|
|
|
|
|
)
|
2021-02-09 15:48:41 -05:00
|
|
|
for pp.gFree.n >= 32 {
|
2022-05-21 00:20:47 +08:00
|
|
|
gp := pp.gFree.pop()
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.gFree.n--
|
2016-03-11 16:27:51 -05:00
|
|
|
if gp.stack.lo == 0 {
|
2020-11-23 15:42:48 +08:00
|
|
|
noStackQ.push(gp)
|
2016-03-11 16:27:51 -05:00
|
|
|
} else {
|
2020-11-23 15:42:48 +08:00
|
|
|
stackQ.push(gp)
|
2016-03-11 16:27:51 -05:00
|
|
|
}
|
2020-11-23 15:42:48 +08:00
|
|
|
inc++
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2020-11-23 15:42:48 +08:00
|
|
|
lock(&sched.gFree.lock)
|
|
|
|
|
sched.gFree.noStack.pushAll(noStackQ)
|
|
|
|
|
sched.gFree.stack.pushAll(stackQ)
|
|
|
|
|
sched.gFree.n += inc
|
2018-08-10 10:19:03 -04:00
|
|
|
unlock(&sched.gFree.lock)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get from gfree list.
|
|
|
|
|
// If local list is empty, grab a batch from global list.
|
2021-02-09 15:48:41 -05:00
|
|
|
func gfget(pp *p) *g {
|
2015-10-18 17:04:05 -07:00
|
|
|
retry:
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp.gFree.empty() && (!sched.gFree.stack.empty() || !sched.gFree.noStack.empty()) {
|
2018-08-10 10:19:03 -04:00
|
|
|
lock(&sched.gFree.lock)
|
|
|
|
|
// Move a batch of free Gs to the P.
|
2021-02-09 15:48:41 -05:00
|
|
|
for pp.gFree.n < 32 {
|
2018-08-10 10:19:03 -04:00
|
|
|
// Prefer Gs with stacks.
|
|
|
|
|
gp := sched.gFree.stack.pop()
|
|
|
|
|
if gp == nil {
|
|
|
|
|
gp = sched.gFree.noStack.pop()
|
|
|
|
|
if gp == nil {
|
|
|
|
|
break
|
|
|
|
|
}
|
2016-03-11 16:27:51 -05:00
|
|
|
}
|
2018-08-10 10:19:03 -04:00
|
|
|
sched.gFree.n--
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.gFree.push(gp)
|
|
|
|
|
pp.gFree.n++
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2018-08-10 10:19:03 -04:00
|
|
|
unlock(&sched.gFree.lock)
|
2015-10-18 17:04:05 -07:00
|
|
|
goto retry
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
gp := pp.gFree.pop()
|
2018-08-10 10:19:03 -04:00
|
|
|
if gp == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.gFree.n--
|
2021-08-28 15:50:52 -07:00
|
|
|
if gp.stack.lo != 0 && gp.stack.hi-gp.stack.lo != uintptr(startingStackSize) {
|
|
|
|
|
// Deallocate old stack. We kept it in gfput because it was the
|
|
|
|
|
// right size when the goroutine was put on the free list, but
|
|
|
|
|
// the right size has changed since then.
|
|
|
|
|
systemstack(func() {
|
|
|
|
|
stackfree(gp.stack)
|
|
|
|
|
gp.stack.lo = 0
|
|
|
|
|
gp.stack.hi = 0
|
|
|
|
|
gp.stackguard0 = 0
|
|
|
|
|
})
|
|
|
|
|
}
|
2018-08-10 10:19:03 -04:00
|
|
|
if gp.stack.lo == 0 {
|
2021-08-28 15:50:52 -07:00
|
|
|
// Stack was deallocated in gfput or just above. Allocate a new one.
|
2018-08-10 10:19:03 -04:00
|
|
|
systemstack(func() {
|
2021-08-28 15:50:52 -07:00
|
|
|
gp.stack = stackalloc(startingStackSize)
|
2018-08-10 10:19:03 -04:00
|
|
|
})
|
|
|
|
|
gp.stackguard0 = gp.stack.lo + _StackGuard
|
|
|
|
|
} else {
|
|
|
|
|
if raceenabled {
|
|
|
|
|
racemalloc(unsafe.Pointer(gp.stack.lo), gp.stack.hi-gp.stack.lo)
|
|
|
|
|
}
|
|
|
|
|
if msanenabled {
|
|
|
|
|
msanmalloc(unsafe.Pointer(gp.stack.lo), gp.stack.hi-gp.stack.lo)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-01-05 17:52:43 +08:00
|
|
|
if asanenabled {
|
|
|
|
|
asanunpoison(unsafe.Pointer(gp.stack.lo), gp.stack.hi-gp.stack.lo)
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
return gp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Purge all cached G's from gfree list to the global list.
|
2021-02-09 15:48:41 -05:00
|
|
|
func gfpurge(pp *p) {
|
2020-11-23 15:42:48 +08:00
|
|
|
var (
|
|
|
|
|
inc int32
|
|
|
|
|
stackQ gQueue
|
|
|
|
|
noStackQ gQueue
|
|
|
|
|
)
|
2021-02-09 15:48:41 -05:00
|
|
|
for !pp.gFree.empty() {
|
|
|
|
|
gp := pp.gFree.pop()
|
|
|
|
|
pp.gFree.n--
|
2016-03-11 16:27:51 -05:00
|
|
|
if gp.stack.lo == 0 {
|
2020-11-23 15:42:48 +08:00
|
|
|
noStackQ.push(gp)
|
2016-03-11 16:27:51 -05:00
|
|
|
} else {
|
2020-11-23 15:42:48 +08:00
|
|
|
stackQ.push(gp)
|
2016-03-11 16:27:51 -05:00
|
|
|
}
|
2020-11-23 15:42:48 +08:00
|
|
|
inc++
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2020-11-23 15:42:48 +08:00
|
|
|
lock(&sched.gFree.lock)
|
|
|
|
|
sched.gFree.noStack.pushAll(noStackQ)
|
|
|
|
|
sched.gFree.stack.pushAll(stackQ)
|
|
|
|
|
sched.gFree.n += inc
|
2018-08-10 10:19:03 -04:00
|
|
|
unlock(&sched.gFree.lock)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Breakpoint executes a breakpoint trap.
|
|
|
|
|
func Breakpoint() {
|
|
|
|
|
breakpoint()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dolockOSThread is called by LockOSThread and lockOSThread below
|
|
|
|
|
// after they modify m.locked. Do not allow preemption during this call,
|
|
|
|
|
// or else the m might be different in this function than in the caller.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
|
|
|
|
func dolockOSThread() {
|
2018-03-31 23:14:17 +02:00
|
|
|
if GOARCH == "wasm" {
|
|
|
|
|
return // no threads on wasm yet
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
gp.m.lockedg.set(gp)
|
|
|
|
|
gp.lockedm.set(gp.m)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LockOSThread wires the calling goroutine to its current operating system thread.
|
2017-06-14 11:46:35 -04:00
|
|
|
// The calling goroutine will always execute in that thread,
|
|
|
|
|
// and no other goroutine will execute in it,
|
2017-06-16 16:21:12 -04:00
|
|
|
// until the calling goroutine has made as many calls to
|
2017-06-14 11:46:35 -04:00
|
|
|
// UnlockOSThread as to LockOSThread.
|
2017-06-16 16:21:12 -04:00
|
|
|
// If the calling goroutine exits without unlocking the thread,
|
|
|
|
|
// the thread will be terminated.
|
2017-08-02 15:54:05 -04:00
|
|
|
//
|
2018-03-29 11:26:27 -07:00
|
|
|
// All init functions are run on the startup thread. Calling LockOSThread
|
|
|
|
|
// from an init function will cause the main function to be invoked on
|
|
|
|
|
// that thread.
|
|
|
|
|
//
|
2017-08-02 15:54:05 -04:00
|
|
|
// A goroutine should call LockOSThread before calling OS services or
|
|
|
|
|
// non-Go library functions that depend on per-thread state.
|
2023-03-02 17:09:22 -05:00
|
|
|
//
|
|
|
|
|
//go:nosplit
|
2015-10-18 17:04:05 -07:00
|
|
|
func LockOSThread() {
|
2017-10-12 22:59:16 +02:00
|
|
|
if atomic.Load(&newmHandoff.haveTemplateThread) == 0 && GOOS != "plan9" {
|
2017-06-15 10:51:15 -04:00
|
|
|
// If we need to start a new thread from the locked
|
|
|
|
|
// thread, we need the template thread. Start it now
|
|
|
|
|
// while we're in a known-good state.
|
|
|
|
|
startTemplateThread()
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
gp.m.lockedExt++
|
|
|
|
|
if gp.m.lockedExt == 0 {
|
|
|
|
|
gp.m.lockedExt--
|
2017-06-14 11:46:35 -04:00
|
|
|
panic("LockOSThread nesting overflow")
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
dolockOSThread()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func lockOSThread() {
|
2017-06-14 11:46:35 -04:00
|
|
|
getg().m.lockedInt++
|
2015-10-18 17:04:05 -07:00
|
|
|
dolockOSThread()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dounlockOSThread is called by UnlockOSThread and unlockOSThread below
|
|
|
|
|
// after they update m->locked. Do not allow preemption during this call,
|
|
|
|
|
// or else the m might be in different in this function than in the caller.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:nosplit
|
|
|
|
|
func dounlockOSThread() {
|
2018-03-31 23:14:17 +02:00
|
|
|
if GOARCH == "wasm" {
|
|
|
|
|
return // no threads on wasm yet
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
if gp.m.lockedInt != 0 || gp.m.lockedExt != 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.lockedg = 0
|
|
|
|
|
gp.lockedm = 0
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2017-06-14 11:46:35 -04:00
|
|
|
// UnlockOSThread undoes an earlier call to LockOSThread.
|
|
|
|
|
// If this drops the number of active LockOSThread calls on the
|
|
|
|
|
// calling goroutine to zero, it unwires the calling goroutine from
|
|
|
|
|
// its fixed operating system thread.
|
|
|
|
|
// If there are no active LockOSThread calls, this is a no-op.
|
2017-08-02 15:54:05 -04:00
|
|
|
//
|
|
|
|
|
// Before calling UnlockOSThread, the caller must ensure that the OS
|
|
|
|
|
// thread is suitable for running other goroutines. If the caller made
|
|
|
|
|
// any permanent changes to the state of the thread that would affect
|
|
|
|
|
// other goroutines, it should not call this function and thus leave
|
|
|
|
|
// the goroutine locked to the OS thread until the goroutine (and
|
|
|
|
|
// hence the thread) exits.
|
2023-03-02 17:09:22 -05:00
|
|
|
//
|
|
|
|
|
//go:nosplit
|
2015-10-18 17:04:05 -07:00
|
|
|
func UnlockOSThread() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
if gp.m.lockedExt == 0 {
|
2017-06-14 11:46:35 -04:00
|
|
|
return
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.lockedExt--
|
2015-10-18 17:04:05 -07:00
|
|
|
dounlockOSThread()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func unlockOSThread() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
if gp.m.lockedInt == 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
systemstack(badunlockosthread)
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.lockedInt--
|
2015-10-18 17:04:05 -07:00
|
|
|
dounlockOSThread()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func badunlockosthread() {
|
|
|
|
|
throw("runtime: internal error: misuse of lockOSThread/unlockOSThread")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func gcount() int32 {
|
2022-07-20 17:48:19 -04:00
|
|
|
n := int32(atomic.Loaduintptr(&allglen)) - sched.gFree.n - sched.ngsys.Load()
|
2021-02-09 15:48:41 -05:00
|
|
|
for _, pp := range allp {
|
|
|
|
|
n -= pp.gFree.n
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All these variables can be changed concurrently, so the result can be inconsistent.
|
|
|
|
|
// But at least the current goroutine is running.
|
|
|
|
|
if n < 1 {
|
|
|
|
|
n = 1
|
|
|
|
|
}
|
|
|
|
|
return n
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func mcount() int32 {
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
return int32(sched.mnext - sched.nmfreed)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var prof struct {
|
2022-07-29 14:52:42 -04:00
|
|
|
signalLock atomic.Uint32
|
2022-07-29 14:48:48 -04:00
|
|
|
|
|
|
|
|
// Must hold signalLock to write. Reads may be lock-free, but
|
|
|
|
|
// signalLock should be taken to synchronize with changes.
|
|
|
|
|
hz atomic.Int32
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2017-05-04 16:45:29 +02:00
|
|
|
func _System() { _System() }
|
|
|
|
|
func _ExternalCode() { _ExternalCode() }
|
|
|
|
|
func _LostExternalCode() { _LostExternalCode() }
|
|
|
|
|
func _GC() { _GC() }
|
|
|
|
|
func _LostSIGPROFDuringAtomic64() { _LostSIGPROFDuringAtomic64() }
|
2018-02-26 14:03:47 -08:00
|
|
|
func _VDSO() { _VDSO() }
|
2017-05-04 16:45:29 +02:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Called if we receive a SIGPROF signal.
|
2016-06-07 21:46:25 -07:00
|
|
|
// Called by the signal handler, may run during STW.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2016-06-07 21:46:25 -07:00
|
|
|
//go:nowritebarrierrec
|
2015-10-18 17:04:05 -07:00
|
|
|
func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
|
2022-07-29 14:48:48 -04:00
|
|
|
if prof.hz.Load() == 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-09 10:40:11 -07:00
|
|
|
// If mp.profilehz is 0, then profiling is not enabled for this thread.
|
|
|
|
|
// We must check this to avoid a deadlock between setcpuprofilerate
|
|
|
|
|
// and the call to cpuprof.add, below.
|
|
|
|
|
if mp != nil && mp.profilehz == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-12 17:17:51 -04:00
|
|
|
// On mips{,le}/arm, 64bit atomics are emulated with spinlocks, in
|
2017-05-04 16:45:29 +02:00
|
|
|
// runtime/internal/atomic. If SIGPROF arrives while the program is inside
|
|
|
|
|
// the critical section, it creates a deadlock (when writing the sample).
|
|
|
|
|
// As a workaround, create a counter of SIGPROFs while in critical section
|
|
|
|
|
// to store the count, and pass it to sigprof.add() later when SIGPROF is
|
|
|
|
|
// received from somewhere else (with _LostSIGPROFDuringAtomic64 as pc).
|
2018-06-06 12:05:36 -07:00
|
|
|
if GOARCH == "mips" || GOARCH == "mipsle" || GOARCH == "arm" {
|
2017-05-04 16:45:29 +02:00
|
|
|
if f := findfunc(pc); f.valid() {
|
2018-06-01 19:25:57 +02:00
|
|
|
if hasPrefix(funcname(f), "runtime/internal/atomic") {
|
2019-06-28 01:42:08 -04:00
|
|
|
cpuprof.lostAtomic++
|
2017-05-04 16:45:29 +02:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-08-12 17:17:51 -04:00
|
|
|
if GOARCH == "arm" && goarm < 7 && GOOS == "linux" && pc&0xffff0000 == 0xffff0000 {
|
|
|
|
|
// runtime/internal/atomic functions call into kernel
|
|
|
|
|
// helpers on arm < 7. See
|
|
|
|
|
// runtime/internal/atomic/sys_linux_arm.s.
|
|
|
|
|
cpuprof.lostAtomic++
|
|
|
|
|
return
|
|
|
|
|
}
|
2017-05-04 16:45:29 +02:00
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Profiling runs concurrently with GC, so it must not allocate.
|
runtime: fix Windows profiling crash
I don't have any way to test or reproduce this problem,
but the current code is clearly wrong for Windows.
Make it better.
As I said on #17165:
But the borrowing of M's and the profiling of M's by the CPU profiler
seem not synchronized enough. This code implements the CPU profiler
on Windows:
func profileloop1(param uintptr) uint32 {
stdcall2(_SetThreadPriority, currentThread, _THREAD_PRIORITY_HIGHEST)
for {
stdcall2(_WaitForSingleObject, profiletimer, _INFINITE)
first := (*m)(atomic.Loadp(unsafe.Pointer(&allm)))
for mp := first; mp != nil; mp = mp.alllink {
thread := atomic.Loaduintptr(&mp.thread)
// Do not profile threads blocked on Notes,
// this includes idle worker threads,
// idle timer thread, idle heap scavenger, etc.
if thread == 0 || mp.profilehz == 0 || mp.blocked {
continue
}
stdcall1(_SuspendThread, thread)
if mp.profilehz != 0 && !mp.blocked {
profilem(mp)
}
stdcall1(_ResumeThread, thread)
}
}
}
func profilem(mp *m) {
var r *context
rbuf := make([]byte, unsafe.Sizeof(*r)+15)
tls := &mp.tls[0]
gp := *((**g)(unsafe.Pointer(tls)))
// align Context to 16 bytes
r = (*context)(unsafe.Pointer((uintptr(unsafe.Pointer(&rbuf[15]))) &^ 15))
r.contextflags = _CONTEXT_CONTROL
stdcall2(_GetThreadContext, mp.thread, uintptr(unsafe.Pointer(r)))
sigprof(r.ip(), r.sp(), 0, gp, mp)
}
func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
if prof.hz == 0 {
return
}
// Profiling runs concurrently with GC, so it must not allocate.
mp.mallocing++
... lots of code ...
mp.mallocing--
}
A borrowed M may migrate between threads. Between the
atomic.Loaduintptr(&mp.thread) and the SuspendThread, mp may have
moved to a new thread, so that it's in active use. In particular
it might be calling malloc, as in the crash stack trace. If so, the
mp.mallocing++ in sigprof would provoke the crash.
Those lines are trying to guard against allocation during sigprof.
But on Windows, mp is the thread being traced, not the current
thread. Those lines should really be using getg().m.mallocing, which
is the same on Unix but not on Windows. With that change, it's
possible the race on the actual thread is not a problem: the traceback
would get confused and eventually return an error, but that's fine.
The code expects that possibility.
Fixes #17165.
Change-Id: If6619731910d65ca4b1a6e7de761fa2518ef339e
Reviewed-on: https://go-review.googlesource.com/33132
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-11-11 10:27:36 -05:00
|
|
|
// Set a trap in case the code does allocate.
|
|
|
|
|
// Note that on windows, one thread takes profiles of all the
|
|
|
|
|
// other threads, so mp is usually not getg().m.
|
|
|
|
|
// In fact mp may not even be stopped.
|
|
|
|
|
// See golang.org/issue/17165.
|
|
|
|
|
getg().m.mallocing++
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2023-02-14 12:25:11 -05:00
|
|
|
var u unwinder
|
2015-10-18 17:04:05 -07:00
|
|
|
var stk [maxCPUProfStack]uintptr
|
|
|
|
|
n := 0
|
runtime: fix sigprof stack barrier locking
f90b48e intended to require the stack barrier lock in all cases of
sigprof that walked the user stack, but got it wrong. In particular,
if sp < gp.stack.lo || gp.stack.hi < sp, tracebackUser would be true,
but we wouldn't acquire the stack lock. If it then turned out that we
were in a cgo call, it would walk the stack without the lock.
In fact, the whole structure of stack locking is sigprof is somewhat
wrong because it assumes the G to lock is gp.m.curg, but all three
gentraceback calls start from potentially different Gs.
To fix this, we lower the gcTryLockStackBarriers calls much closer to
the gentraceback calls. There are now three separate trylock calls,
each clearly associated with a gentraceback and the locked G clearly
matches the G from which the gentraceback starts. This actually brings
the sigprof logic closer to what it originally was before stack
barrier locking.
This depends on "runtime: increase assumed stack size in
externalthreadhandler" because it very slightly increases the stack
used by sigprof; without this other commit, this is enough to blow the
profiler thread's assumed stack size.
Fixes #12528 (hopefully for real this time!).
For the 1.5 branch, though it will require some backporting. On the
1.5 branch, this will *not* require the "runtime: increase assumed
stack size in externalthreadhandler" commit: there's no pcvalue cache,
so the used stack is smaller.
Change-Id: Id2f6446ac276848f6fc158bee550cccd03186b83
Reviewed-on: https://go-review.googlesource.com/18328
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
2016-01-05 15:21:27 -05:00
|
|
|
if mp.ncgo > 0 && mp.curg != nil && mp.curg.syscallpc != 0 && mp.curg.syscallsp != 0 {
|
2016-04-29 15:20:27 -07:00
|
|
|
cgoOff := 0
|
|
|
|
|
// Check cgoCallersUse to make sure that we are not
|
|
|
|
|
// interrupting other code that is fiddling with
|
|
|
|
|
// cgoCallers. We are running in a signal handler
|
|
|
|
|
// with all signals blocked, so we don't have to worry
|
|
|
|
|
// about any other code interrupting us.
|
2022-08-17 17:39:01 +07:00
|
|
|
if mp.cgoCallersUse.Load() == 0 && mp.cgoCallers != nil && mp.cgoCallers[0] != 0 {
|
2016-04-29 15:20:27 -07:00
|
|
|
for cgoOff < len(mp.cgoCallers) && mp.cgoCallers[cgoOff] != 0 {
|
|
|
|
|
cgoOff++
|
|
|
|
|
}
|
2023-02-14 12:25:11 -05:00
|
|
|
n += copy(stk[:], mp.cgoCallers[:cgoOff])
|
2016-04-29 15:20:27 -07:00
|
|
|
mp.cgoCallers[0] = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect Go stack that leads to the cgo call.
|
2023-02-14 12:25:11 -05:00
|
|
|
u.initAt(mp.curg.syscallpc, mp.curg.syscallsp, 0, mp.curg, unwindSilentErrors)
|
2022-12-05 13:02:22 -05:00
|
|
|
} else if usesLibcall() && mp.libcallg != 0 && mp.libcallpc != 0 && mp.libcallsp != 0 {
|
|
|
|
|
// Libcall, i.e. runtime syscall on windows.
|
|
|
|
|
// Collect Go stack that leads to the call.
|
2023-02-14 12:25:11 -05:00
|
|
|
u.initAt(mp.libcallpc, mp.libcallsp, 0, mp.libcallg.ptr(), unwindSilentErrors)
|
2022-12-05 13:02:22 -05:00
|
|
|
} else if mp != nil && mp.vdsoSP != 0 {
|
|
|
|
|
// VDSO call, e.g. nanotime1 on Linux.
|
|
|
|
|
// Collect Go stack that leads to the call.
|
2023-02-14 12:25:11 -05:00
|
|
|
u.initAt(mp.vdsoPC, mp.vdsoSP, 0, gp, unwindSilentErrors|unwindJumpStack)
|
2021-01-28 16:10:58 -05:00
|
|
|
} else {
|
2023-02-14 12:25:11 -05:00
|
|
|
u.initAt(pc, sp, lr, gp, unwindSilentErrors|unwindTrap|unwindJumpStack)
|
runtime: fix sigprof stack barrier locking
f90b48e intended to require the stack barrier lock in all cases of
sigprof that walked the user stack, but got it wrong. In particular,
if sp < gp.stack.lo || gp.stack.hi < sp, tracebackUser would be true,
but we wouldn't acquire the stack lock. If it then turned out that we
were in a cgo call, it would walk the stack without the lock.
In fact, the whole structure of stack locking is sigprof is somewhat
wrong because it assumes the G to lock is gp.m.curg, but all three
gentraceback calls start from potentially different Gs.
To fix this, we lower the gcTryLockStackBarriers calls much closer to
the gentraceback calls. There are now three separate trylock calls,
each clearly associated with a gentraceback and the locked G clearly
matches the G from which the gentraceback starts. This actually brings
the sigprof logic closer to what it originally was before stack
barrier locking.
This depends on "runtime: increase assumed stack size in
externalthreadhandler" because it very slightly increases the stack
used by sigprof; without this other commit, this is enough to blow the
profiler thread's assumed stack size.
Fixes #12528 (hopefully for real this time!).
For the 1.5 branch, though it will require some backporting. On the
1.5 branch, this will *not* require the "runtime: increase assumed
stack size in externalthreadhandler" commit: there's no pcvalue cache,
so the used stack is smaller.
Change-Id: Id2f6446ac276848f6fc158bee550cccd03186b83
Reviewed-on: https://go-review.googlesource.com/18328
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
2016-01-05 15:21:27 -05:00
|
|
|
}
|
2023-02-14 12:25:11 -05:00
|
|
|
n += tracebackPCs(&u, 0, stk[n:])
|
runtime: fix sigprof stack barrier locking
f90b48e intended to require the stack barrier lock in all cases of
sigprof that walked the user stack, but got it wrong. In particular,
if sp < gp.stack.lo || gp.stack.hi < sp, tracebackUser would be true,
but we wouldn't acquire the stack lock. If it then turned out that we
were in a cgo call, it would walk the stack without the lock.
In fact, the whole structure of stack locking is sigprof is somewhat
wrong because it assumes the G to lock is gp.m.curg, but all three
gentraceback calls start from potentially different Gs.
To fix this, we lower the gcTryLockStackBarriers calls much closer to
the gentraceback calls. There are now three separate trylock calls,
each clearly associated with a gentraceback and the locked G clearly
matches the G from which the gentraceback starts. This actually brings
the sigprof logic closer to what it originally was before stack
barrier locking.
This depends on "runtime: increase assumed stack size in
externalthreadhandler" because it very slightly increases the stack
used by sigprof; without this other commit, this is enough to blow the
profiler thread's assumed stack size.
Fixes #12528 (hopefully for real this time!).
For the 1.5 branch, though it will require some backporting. On the
1.5 branch, this will *not* require the "runtime: increase assumed
stack size in externalthreadhandler" commit: there's no pcvalue cache,
so the used stack is smaller.
Change-Id: Id2f6446ac276848f6fc158bee550cccd03186b83
Reviewed-on: https://go-review.googlesource.com/18328
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
2016-01-05 15:21:27 -05:00
|
|
|
|
2016-01-06 14:02:50 -05:00
|
|
|
if n <= 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
// Normal traceback is impossible or has failed.
|
2022-12-05 13:02:22 -05:00
|
|
|
// Account it against abstract "System" or "GC".
|
|
|
|
|
n = 2
|
|
|
|
|
if inVDSOPage(pc) {
|
|
|
|
|
pc = abi.FuncPCABIInternal(_VDSO) + sys.PCQuantum
|
|
|
|
|
} else if pc > firstmoduledata.etext {
|
|
|
|
|
// "ExternalCode" is better than "etext".
|
|
|
|
|
pc = abi.FuncPCABIInternal(_ExternalCode) + sys.PCQuantum
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2022-12-05 13:02:22 -05:00
|
|
|
stk[0] = pc
|
|
|
|
|
if mp.preemptoff != "" {
|
|
|
|
|
stk[1] = abi.FuncPCABIInternal(_GC) + sys.PCQuantum
|
|
|
|
|
} else {
|
|
|
|
|
stk[1] = abi.FuncPCABIInternal(_System) + sys.PCQuantum
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-29 14:48:48 -04:00
|
|
|
if prof.hz.Load() != 0 {
|
2021-09-20 16:09:47 +02:00
|
|
|
// Note: it can happen on Windows that we interrupted a system thread
|
|
|
|
|
// with no g, so gp could nil. The other nil checks are done out of
|
|
|
|
|
// caution, but not expected to be nil in practice.
|
|
|
|
|
var tagPtr *unsafe.Pointer
|
|
|
|
|
if gp != nil && gp.m != nil && gp.m.curg != nil {
|
|
|
|
|
tagPtr = &gp.m.curg.labels
|
|
|
|
|
}
|
|
|
|
|
cpuprof.add(tagPtr, stk[:n])
|
2022-04-18 12:32:37 -07:00
|
|
|
|
2022-05-04 07:44:50 -07:00
|
|
|
gprof := gp
|
2022-04-18 12:32:37 -07:00
|
|
|
var pp *p
|
|
|
|
|
if gp != nil && gp.m != nil {
|
2022-05-04 07:44:50 -07:00
|
|
|
if gp.m.curg != nil {
|
|
|
|
|
gprof = gp.m.curg
|
|
|
|
|
}
|
2022-04-18 12:32:37 -07:00
|
|
|
pp = gp.m.p.ptr()
|
|
|
|
|
}
|
2022-05-04 07:44:50 -07:00
|
|
|
traceCPUSample(gprof, pp, stk[:n])
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
runtime: fix Windows profiling crash
I don't have any way to test or reproduce this problem,
but the current code is clearly wrong for Windows.
Make it better.
As I said on #17165:
But the borrowing of M's and the profiling of M's by the CPU profiler
seem not synchronized enough. This code implements the CPU profiler
on Windows:
func profileloop1(param uintptr) uint32 {
stdcall2(_SetThreadPriority, currentThread, _THREAD_PRIORITY_HIGHEST)
for {
stdcall2(_WaitForSingleObject, profiletimer, _INFINITE)
first := (*m)(atomic.Loadp(unsafe.Pointer(&allm)))
for mp := first; mp != nil; mp = mp.alllink {
thread := atomic.Loaduintptr(&mp.thread)
// Do not profile threads blocked on Notes,
// this includes idle worker threads,
// idle timer thread, idle heap scavenger, etc.
if thread == 0 || mp.profilehz == 0 || mp.blocked {
continue
}
stdcall1(_SuspendThread, thread)
if mp.profilehz != 0 && !mp.blocked {
profilem(mp)
}
stdcall1(_ResumeThread, thread)
}
}
}
func profilem(mp *m) {
var r *context
rbuf := make([]byte, unsafe.Sizeof(*r)+15)
tls := &mp.tls[0]
gp := *((**g)(unsafe.Pointer(tls)))
// align Context to 16 bytes
r = (*context)(unsafe.Pointer((uintptr(unsafe.Pointer(&rbuf[15]))) &^ 15))
r.contextflags = _CONTEXT_CONTROL
stdcall2(_GetThreadContext, mp.thread, uintptr(unsafe.Pointer(r)))
sigprof(r.ip(), r.sp(), 0, gp, mp)
}
func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
if prof.hz == 0 {
return
}
// Profiling runs concurrently with GC, so it must not allocate.
mp.mallocing++
... lots of code ...
mp.mallocing--
}
A borrowed M may migrate between threads. Between the
atomic.Loaduintptr(&mp.thread) and the SuspendThread, mp may have
moved to a new thread, so that it's in active use. In particular
it might be calling malloc, as in the crash stack trace. If so, the
mp.mallocing++ in sigprof would provoke the crash.
Those lines are trying to guard against allocation during sigprof.
But on Windows, mp is the thread being traced, not the current
thread. Those lines should really be using getg().m.mallocing, which
is the same on Unix but not on Windows. With that change, it's
possible the race on the actual thread is not a problem: the traceback
would get confused and eventually return an error, but that's fine.
The code expects that possibility.
Fixes #17165.
Change-Id: If6619731910d65ca4b1a6e7de761fa2518ef339e
Reviewed-on: https://go-review.googlesource.com/33132
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-11-11 10:27:36 -05:00
|
|
|
getg().m.mallocing--
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2017-02-09 15:57:57 -05:00
|
|
|
// setcpuprofilerate sets the CPU profiling rate to hz times per second.
|
|
|
|
|
// If hz <= 0, setcpuprofilerate turns off CPU profiling.
|
|
|
|
|
func setcpuprofilerate(hz int32) {
|
2015-10-18 17:04:05 -07:00
|
|
|
// Force sane arguments.
|
|
|
|
|
if hz < 0 {
|
|
|
|
|
hz = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Disable preemption, otherwise we can be rescheduled to another thread
|
|
|
|
|
// that has profiling enabled.
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
gp.m.locks++
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// 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.
|
2016-12-06 20:54:41 -08:00
|
|
|
setThreadCPUProfiler(0)
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2022-07-29 14:52:42 -04:00
|
|
|
for !prof.signalLock.CompareAndSwap(0, 1) {
|
2015-10-18 17:04:05 -07:00
|
|
|
osyield()
|
|
|
|
|
}
|
2022-07-29 14:48:48 -04:00
|
|
|
if prof.hz.Load() != hz {
|
2016-12-06 20:54:41 -08:00
|
|
|
setProcessCPUProfiler(hz)
|
2022-07-29 14:48:48 -04:00
|
|
|
prof.hz.Store(hz)
|
2016-12-06 20:54:41 -08:00
|
|
|
}
|
2022-07-29 14:52:42 -04:00
|
|
|
prof.signalLock.Store(0)
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
sched.profilehz = hz
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
|
|
|
|
if hz != 0 {
|
2016-12-06 20:54:41 -08:00
|
|
|
setThreadCPUProfiler(hz)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.locks--
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2019-04-11 15:43:54 -04:00
|
|
|
// init initializes pp, which may be a freshly allocated p or a
|
|
|
|
|
// previously destroyed p, and transitions it to status _Pgcstop.
|
|
|
|
|
func (pp *p) init(id int32) {
|
|
|
|
|
pp.id = id
|
|
|
|
|
pp.status = _Pgcstop
|
|
|
|
|
pp.sudogcache = pp.sudogbuf[:0]
|
2021-06-08 18:45:18 -04:00
|
|
|
pp.deferpool = pp.deferpoolbuf[:0]
|
2019-04-11 15:43:54 -04:00
|
|
|
pp.wbBuf.reset()
|
|
|
|
|
if pp.mcache == nil {
|
|
|
|
|
if id == 0 {
|
2019-11-04 14:25:22 -08:00
|
|
|
if mcache0 == nil {
|
2019-04-11 15:43:54 -04:00
|
|
|
throw("missing mcache?")
|
|
|
|
|
}
|
2019-11-04 14:25:22 -08:00
|
|
|
// Use the bootstrap mcache0. Only one P will get
|
|
|
|
|
// mcache0: the one with ID 0.
|
|
|
|
|
pp.mcache = mcache0
|
2019-04-11 15:43:54 -04:00
|
|
|
} else {
|
|
|
|
|
pp.mcache = allocmcache()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if raceenabled && pp.raceprocctx == 0 {
|
|
|
|
|
if id == 0 {
|
|
|
|
|
pp.raceprocctx = raceprocctx0
|
|
|
|
|
raceprocctx0 = 0 // bootstrap
|
|
|
|
|
} else {
|
|
|
|
|
pp.raceprocctx = raceproccreate()
|
|
|
|
|
}
|
|
|
|
|
}
|
runtime: static lock ranking for the runtime (enabled by GOEXPERIMENT)
I took some of the infrastructure from Austin's lock logging CR
https://go-review.googlesource.com/c/go/+/192704 (with deadlock
detection from the logs), and developed a setup to give static lock
ranking for runtime locks.
Static lock ranking establishes a documented total ordering among locks,
and then reports an error if the total order is violated. This can
happen if a deadlock happens (by acquiring a sequence of locks in
different orders), or if just one side of a possible deadlock happens.
Lock ordering deadlocks cannot happen as long as the lock ordering is
followed.
Along the way, I found a deadlock involving the new timer code, which Ian fixed
via https://go-review.googlesource.com/c/go/+/207348, as well as two other
potential deadlocks.
See the constants at the top of runtime/lockrank.go to show the static
lock ranking that I ended up with, along with some comments. This is
great documentation of the current intended lock ordering when acquiring
multiple locks in the runtime.
I also added an array lockPartialOrder[] which shows and enforces the
current partial ordering among locks (which is embedded within the total
ordering). This is more specific about the dependencies among locks.
I don't try to check the ranking within a lock class with multiple locks
that can be acquired at the same time (i.e. check the ranking when
multiple hchan locks are acquired).
Currently, I am doing a lockInit() call to set the lock rank of most
locks. Any lock that is not otherwise initialized is assumed to be a
leaf lock (a very high rank lock), so that eliminates the need to do
anything for a bunch of locks (including all architecture-dependent
locks). For two locks, root.lock and notifyList.lock (only in the
runtime/sema.go file), it is not as easy to do lock initialization, so
instead, I am passing the lock rank with the lock calls.
For Windows compilation, I needed to increase the StackGuard size from
896 to 928 because of the new lock-rank checking functions.
Checking of the static lock ranking is enabled by setting
GOEXPERIMENT=staticlockranking before doing a run.
To make sure that the static lock ranking code has no overhead in memory
or CPU when not enabled by GOEXPERIMENT, I changed 'go build/install' so
that it defines a build tag (with the same name) whenever any experiment
has been baked into the toolchain (by checking Expstring()). This allows
me to avoid increasing the size of the 'mutex' type when static lock
ranking is not enabled.
Fixes #38029
Change-Id: I154217ff307c47051f8dae9c2a03b53081acd83a
Reviewed-on: https://go-review.googlesource.com/c/go/+/207619
Reviewed-by: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-11-13 17:34:47 -08:00
|
|
|
lockInit(&pp.timersLock, lockRankTimers)
|
2020-10-05 18:12:35 -04:00
|
|
|
|
|
|
|
|
// This P may get timers when it starts running. Set the mask here
|
|
|
|
|
// since the P may not go through pidleget (notably P 0 on startup).
|
|
|
|
|
timerpMask.set(id)
|
|
|
|
|
// Similarly, we may not go through pidleget before this P starts
|
|
|
|
|
// running if it is P 0 on startup.
|
|
|
|
|
idlepMask.clear(id)
|
2019-04-11 15:43:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// destroy releases all of the resources associated with pp and
|
|
|
|
|
// transitions it to status _Pdead.
|
|
|
|
|
//
|
|
|
|
|
// sched.lock must be held and the world must be stopped.
|
|
|
|
|
func (pp *p) destroy() {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
2020-10-28 18:06:05 -04:00
|
|
|
assertWorldStopped()
|
2020-08-21 11:51:25 -04:00
|
|
|
|
2019-04-11 15:43:54 -04:00
|
|
|
// Move all runnable goroutines to the global queue
|
|
|
|
|
for pp.runqhead != pp.runqtail {
|
|
|
|
|
// Pop from tail of local queue
|
|
|
|
|
pp.runqtail--
|
|
|
|
|
gp := pp.runq[pp.runqtail%uint32(len(pp.runq))].ptr()
|
|
|
|
|
// Push onto head of global queue
|
|
|
|
|
globrunqputhead(gp)
|
|
|
|
|
}
|
|
|
|
|
if pp.runnext != 0 {
|
|
|
|
|
globrunqputhead(pp.runnext.ptr())
|
|
|
|
|
pp.runnext = 0
|
|
|
|
|
}
|
2019-04-05 16:53:13 -07:00
|
|
|
if len(pp.timers) > 0 {
|
|
|
|
|
plocal := getg().m.p.ptr()
|
2019-11-12 17:22:28 -08:00
|
|
|
// The world is stopped, but we acquire timersLock to
|
|
|
|
|
// protect against sysmon calling timeSleepUntil.
|
|
|
|
|
// This is the only case where we hold the timersLock of
|
|
|
|
|
// more than one P, so there are no deadlock concerns.
|
|
|
|
|
lock(&plocal.timersLock)
|
|
|
|
|
lock(&pp.timersLock)
|
2019-04-05 16:53:13 -07:00
|
|
|
moveTimers(plocal, pp.timers)
|
|
|
|
|
pp.timers = nil
|
2022-08-26 09:54:32 +08:00
|
|
|
pp.numTimers.Store(0)
|
|
|
|
|
pp.deletedTimers.Store(0)
|
2022-08-17 14:56:18 +07:00
|
|
|
pp.timer0When.Store(0)
|
2019-11-12 17:22:28 -08:00
|
|
|
unlock(&pp.timersLock)
|
|
|
|
|
unlock(&plocal.timersLock)
|
2019-04-05 16:53:13 -07:00
|
|
|
}
|
2019-04-11 15:43:54 -04:00
|
|
|
// Flush p's write barrier buffer.
|
|
|
|
|
if gcphase != _GCoff {
|
|
|
|
|
wbBufFlush1(pp)
|
|
|
|
|
pp.gcw.dispose()
|
|
|
|
|
}
|
|
|
|
|
for i := range pp.sudogbuf {
|
|
|
|
|
pp.sudogbuf[i] = nil
|
|
|
|
|
}
|
|
|
|
|
pp.sudogcache = pp.sudogbuf[:0]
|
2021-06-08 18:45:18 -04:00
|
|
|
for j := range pp.deferpoolbuf {
|
|
|
|
|
pp.deferpoolbuf[j] = nil
|
2019-04-11 15:43:54 -04:00
|
|
|
}
|
2021-06-08 18:45:18 -04:00
|
|
|
pp.deferpool = pp.deferpoolbuf[:0]
|
2019-09-18 15:57:36 +00:00
|
|
|
systemstack(func() {
|
|
|
|
|
for i := 0; i < pp.mspancache.len; i++ {
|
|
|
|
|
// Safe to call since the world is stopped.
|
|
|
|
|
mheap_.spanalloc.free(unsafe.Pointer(pp.mspancache.buf[i]))
|
|
|
|
|
}
|
|
|
|
|
pp.mspancache.len = 0
|
2020-08-21 11:59:55 -04:00
|
|
|
lock(&mheap_.lock)
|
2019-09-16 21:23:24 +00:00
|
|
|
pp.pcache.flush(&mheap_.pages)
|
2020-08-21 11:59:55 -04:00
|
|
|
unlock(&mheap_.lock)
|
2019-09-18 15:57:36 +00:00
|
|
|
})
|
2020-08-04 17:29:03 +00:00
|
|
|
freemcache(pp.mcache)
|
2019-04-11 15:43:54 -04:00
|
|
|
pp.mcache = nil
|
|
|
|
|
gfpurge(pp)
|
|
|
|
|
traceProcFree(pp)
|
|
|
|
|
if raceenabled {
|
2019-04-11 14:20:54 -07:00
|
|
|
if pp.timerRaceCtx != 0 {
|
|
|
|
|
// The race detector code uses a callback to fetch
|
|
|
|
|
// the proc context, so arrange for that callback
|
|
|
|
|
// to see the right thing.
|
|
|
|
|
// This hack only works because we are the only
|
|
|
|
|
// thread running.
|
|
|
|
|
mp := getg().m
|
|
|
|
|
phold := mp.p.ptr()
|
|
|
|
|
mp.p.set(pp)
|
|
|
|
|
|
|
|
|
|
racectxend(pp.timerRaceCtx)
|
|
|
|
|
pp.timerRaceCtx = 0
|
|
|
|
|
|
|
|
|
|
mp.p.set(phold)
|
|
|
|
|
}
|
2019-04-11 15:43:54 -04:00
|
|
|
raceprocdestroy(pp.raceprocctx)
|
|
|
|
|
pp.raceprocctx = 0
|
|
|
|
|
}
|
|
|
|
|
pp.gcAssistTime = 0
|
|
|
|
|
pp.status = _Pdead
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 11:51:25 -04:00
|
|
|
// Change number of processors.
|
|
|
|
|
//
|
|
|
|
|
// sched.lock must be held, and the world must be stopped.
|
|
|
|
|
//
|
|
|
|
|
// gcworkbufs must not be being modified by either the GC or the write barrier
|
|
|
|
|
// code, so the GC must not be running if the number of Ps actually changes.
|
|
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
// Returns list of Ps with local work, they need to be scheduled by the caller.
|
|
|
|
|
func procresize(nprocs int32) *p {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
2020-10-28 18:06:05 -04:00
|
|
|
assertWorldStopped()
|
2020-08-21 11:51:25 -04:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
old := gomaxprocs
|
2017-06-13 11:33:57 -04:00
|
|
|
if old < 0 || nprocs <= 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("procresize: invalid arg")
|
|
|
|
|
}
|
|
|
|
|
if trace.enabled {
|
|
|
|
|
traceGomaxprocs(nprocs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update statistics
|
|
|
|
|
now := nanotime()
|
|
|
|
|
if sched.procresizetime != 0 {
|
|
|
|
|
sched.totaltime += int64(old) * (now - sched.procresizetime)
|
|
|
|
|
}
|
|
|
|
|
sched.procresizetime = now
|
|
|
|
|
|
2020-05-01 17:04:36 -04:00
|
|
|
maskWords := (nprocs + 31) / 32
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
|
2017-06-13 11:32:17 -04:00
|
|
|
// Grow allp if necessary.
|
|
|
|
|
if nprocs > int32(len(allp)) {
|
|
|
|
|
// Synchronize with retake, which could be running
|
|
|
|
|
// concurrently since it doesn't run on a P.
|
|
|
|
|
lock(&allpLock)
|
|
|
|
|
if nprocs <= int32(cap(allp)) {
|
|
|
|
|
allp = allp[:nprocs]
|
|
|
|
|
} else {
|
|
|
|
|
nallp := make([]*p, nprocs)
|
|
|
|
|
// Copy everything up to allp's cap so we
|
|
|
|
|
// never lose old allocated Ps.
|
|
|
|
|
copy(nallp, allp[:cap(allp)])
|
|
|
|
|
allp = nallp
|
|
|
|
|
}
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
|
|
|
|
|
if maskWords <= int32(cap(idlepMask)) {
|
|
|
|
|
idlepMask = idlepMask[:maskWords]
|
2020-10-05 18:12:35 -04:00
|
|
|
timerpMask = timerpMask[:maskWords]
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
} else {
|
|
|
|
|
nidlepMask := make([]uint32, maskWords)
|
|
|
|
|
// No need to copy beyond len, old Ps are irrelevant.
|
|
|
|
|
copy(nidlepMask, idlepMask)
|
|
|
|
|
idlepMask = nidlepMask
|
2020-10-05 18:12:35 -04:00
|
|
|
|
|
|
|
|
ntimerpMask := make([]uint32, maskWords)
|
|
|
|
|
copy(ntimerpMask, timerpMask)
|
|
|
|
|
timerpMask = ntimerpMask
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
}
|
2017-06-13 11:32:17 -04:00
|
|
|
unlock(&allpLock)
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// initialize new P's
|
2019-04-11 15:43:54 -04:00
|
|
|
for i := old; i < nprocs; i++ {
|
2015-10-18 17:04:05 -07:00
|
|
|
pp := allp[i]
|
|
|
|
|
if pp == nil {
|
|
|
|
|
pp = new(p)
|
2016-02-26 21:57:16 +01:00
|
|
|
}
|
2019-04-11 15:43:54 -04:00
|
|
|
pp.init(i)
|
|
|
|
|
atomicstorep(unsafe.Pointer(&allp[i]), unsafe.Pointer(pp))
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
if gp.m.p != 0 && gp.m.p.ptr().id < nprocs {
|
2015-10-18 17:04:05 -07:00
|
|
|
// continue to use the current P
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.p.ptr().status = _Prunning
|
|
|
|
|
gp.m.p.ptr().mcache.prepareForSweep()
|
2015-10-18 17:04:05 -07:00
|
|
|
} else {
|
2019-04-19 17:50:37 -04:00
|
|
|
// release the current P and acquire allp[0].
|
|
|
|
|
//
|
|
|
|
|
// We must do this before destroying our current P
|
|
|
|
|
// because p.destroy itself has write barriers, so we
|
|
|
|
|
// need to do that from a valid P.
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.p != 0 {
|
2019-04-19 17:50:37 -04:00
|
|
|
if trace.enabled {
|
|
|
|
|
// Pretend that we were descheduled
|
|
|
|
|
// and then scheduled again to keep
|
|
|
|
|
// the trace sane.
|
|
|
|
|
traceGoSched()
|
2021-02-11 11:15:53 -05:00
|
|
|
traceProcStop(gp.m.p.ptr())
|
2019-04-19 17:50:37 -04:00
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.p.ptr().m = 0
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.p = 0
|
2021-02-09 15:48:41 -05:00
|
|
|
pp := allp[0]
|
|
|
|
|
pp.m = 0
|
|
|
|
|
pp.status = _Pidle
|
|
|
|
|
acquirep(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
if trace.enabled {
|
|
|
|
|
traceGoStart()
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-19 17:50:37 -04:00
|
|
|
|
2019-11-04 14:25:22 -08:00
|
|
|
// g.m.p is now set, so we no longer need mcache0 for bootstrapping.
|
|
|
|
|
mcache0 = nil
|
|
|
|
|
|
2019-04-19 17:50:37 -04:00
|
|
|
// release resources from unused P's
|
|
|
|
|
for i := nprocs; i < old; i++ {
|
2021-02-09 15:48:41 -05:00
|
|
|
pp := allp[i]
|
|
|
|
|
pp.destroy()
|
2019-04-19 17:50:37 -04:00
|
|
|
// can't free P itself because it can be referenced by an M in syscall
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trim allp.
|
|
|
|
|
if int32(len(allp)) != nprocs {
|
|
|
|
|
lock(&allpLock)
|
|
|
|
|
allp = allp[:nprocs]
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
idlepMask = idlepMask[:maskWords]
|
2020-10-05 18:12:35 -04:00
|
|
|
timerpMask = timerpMask[:maskWords]
|
2019-04-19 17:50:37 -04:00
|
|
|
unlock(&allpLock)
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
var runnablePs *p
|
|
|
|
|
for i := nprocs - 1; i >= 0; i-- {
|
2021-02-09 15:48:41 -05:00
|
|
|
pp := allp[i]
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.p.ptr() == pp {
|
2015-10-18 17:04:05 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.status = _Pidle
|
|
|
|
|
if runqempty(pp) {
|
|
|
|
|
pidleput(pp, now)
|
2015-10-18 17:04:05 -07:00
|
|
|
} else {
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.m.set(mget())
|
|
|
|
|
pp.link.set(runnablePs)
|
|
|
|
|
runnablePs = pp
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
2016-03-18 12:52:52 +01:00
|
|
|
stealOrder.reset(uint32(nprocs))
|
2015-10-18 17:04:05 -07:00
|
|
|
var int32p *int32 = &gomaxprocs // make compiler check that gomaxprocs is an int32
|
2015-11-02 14:09:24 -05:00
|
|
|
atomic.Store((*uint32)(unsafe.Pointer(int32p)), uint32(nprocs))
|
runtime: add GC CPU utilization limiter
This change adds a GC CPU utilization limiter to the GC. It disables
assists to ensure GC CPU utilization remains under 50%. It uses a leaky
bucket mechanism that will only fill if GC CPU utilization exceeds 50%.
Once the bucket begins to overflow, GC assists are limited until the
bucket empties, at the risk of GC overshoot. The limiter is primarily
updated by assists. The scheduler may also update it, but only if the
GC is on and a few milliseconds have passed since the last update. This
second case exists to ensure that if the limiter is on, and no assists
are happening, we're still updating the limiter regularly.
The purpose of this limiter is to mitigate GC death spirals, opting to
use more memory instead.
This change turns the limiter on always. In practice, 50% overall GC CPU
utilization is very difficult to hit unless you're trying; even the most
allocation-heavy applications with complex heaps still need to do
something with that memory. Note that small GOGC values (i.e.
single-digit, or low teens) are more likely to trigger the limiter,
which means the GOGC tradeoff may no longer be respected. Even so, it
should still be relatively rare.
This change also introduces the feature flag for code to support the
memory limit feature.
For #48409.
Change-Id: Ia30f914e683e491a00900fd27868446c65e5d3c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/353989
Reviewed-by: Michael Pratt <mpratt@google.com>
2021-10-01 22:52:12 -04:00
|
|
|
if old != nprocs {
|
|
|
|
|
// Notify the limiter that the amount of procs has changed.
|
|
|
|
|
gcCPULimiter.resetCapacity(now, nprocs)
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
return runnablePs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Associate p and the current m.
|
2016-10-10 17:14:14 -04:00
|
|
|
//
|
|
|
|
|
// This function is allowed to have write barriers even if the caller
|
2021-02-09 15:48:41 -05:00
|
|
|
// isn't because it immediately acquires pp.
|
2016-10-10 17:14:14 -04:00
|
|
|
//
|
|
|
|
|
//go:yeswritebarrierrec
|
2021-02-09 15:48:41 -05:00
|
|
|
func acquirep(pp *p) {
|
2016-10-10 17:14:14 -04:00
|
|
|
// Do the part that isn't allowed to have write barriers.
|
2021-02-09 15:48:41 -05:00
|
|
|
wirep(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
// Have p; write barriers now allowed.
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2018-08-23 13:14:19 -04:00
|
|
|
// Perform deferred mcache flush before this P can allocate
|
|
|
|
|
// from a potentially stale mcache.
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.mcache.prepareForSweep()
|
2018-08-23 13:14:19 -04:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
if trace.enabled {
|
|
|
|
|
traceProcStart()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
// wirep is the first step of acquirep, which actually associates the
|
2021-02-09 15:48:41 -05:00
|
|
|
// current M to pp. This is broken out so we can disallow write
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
// barriers for this part, since we don't yet have a P.
|
2016-10-10 17:14:14 -04:00
|
|
|
//
|
|
|
|
|
//go:nowritebarrierrec
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
//go:nosplit
|
2021-02-09 15:48:41 -05:00
|
|
|
func wirep(pp *p) {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.p != 0 {
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
throw("wirep: already in go")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp.m != 0 || pp.status != _Pidle {
|
2017-10-05 21:28:01 -04:00
|
|
|
id := int64(0)
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp.m != 0 {
|
|
|
|
|
id = pp.m.ptr().id
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
print("wirep: p->m=", pp.m, "(", id, ") p->status=", pp.status, "\n")
|
runtime: ensure m.p is never stale
When a goroutine enters a syscall, its M unwires from its P to allow
the P to be retaken by another M if the syscall is slow. The M retains a
reference to its old P, however, so that if its old P has not been
retaken when the syscall returns, it can quickly reacquire that P.
The implementation, however, was confusing, as it left the reference to
the potentially-retaken P in m.p, which implied that the P was still
wired.
Make the code clearer by enforcing the invariant that m.p is never
stale. entersyscall now moves m.p to m.oldp and sets m.p to 0;
exitsyscall does the reverse, provided m.oldp has not been retaken.
With this scheme in place, the issue described in #27660 (assertion
failures in the race detector) would have resulted in a clean segfault
instead of silently corrupting memory.
Change-Id: Ib3e03623ebed4f410e852a716919fe4538858f0a
Reviewed-on: https://go-review.googlesource.com/c/148899
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-11-09 00:55:13 -05:00
|
|
|
throw("wirep: invalid p state")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.p.set(pp)
|
|
|
|
|
pp.m.set(gp.m)
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.status = _Prunning
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Disassociate p and the current m.
|
|
|
|
|
func releasep() *p {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
2015-10-18 17:04:05 -07:00
|
|
|
|
2021-02-11 11:15:53 -05:00
|
|
|
if gp.m.p == 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("releasep: invalid arg")
|
|
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
pp := gp.m.p.ptr()
|
|
|
|
|
if pp.m.ptr() != gp.m || pp.status != _Prunning {
|
|
|
|
|
print("releasep: m=", gp.m, " m->p=", gp.m.p.ptr(), " p->m=", hex(pp.m), " p->status=", pp.status, "\n")
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("releasep: invalid p state")
|
|
|
|
|
}
|
|
|
|
|
if trace.enabled {
|
2021-02-11 11:15:53 -05:00
|
|
|
traceProcStop(gp.m.p.ptr())
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-11 11:15:53 -05:00
|
|
|
gp.m.p = 0
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.m = 0
|
|
|
|
|
pp.status = _Pidle
|
|
|
|
|
return pp
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func incidlelocked(v int32) {
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
sched.nmidlelocked += v
|
|
|
|
|
if v > 0 {
|
|
|
|
|
checkdead()
|
|
|
|
|
}
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for deadlock situation.
|
|
|
|
|
// The check is based on number of running M's, if 0 -> deadlock.
|
runtime: make it possible to exit Go-created threads
Currently, threads created by the runtime exist until the whole
program exits. For #14592 and #20395, we want to be able to exit and
clean up threads created by the runtime. This commit implements that
mechanism.
The main difficulty is how to clean up the g0 stack. In cgo mode and
on Solaris and Windows where the OS manages thread stacks, we simply
arrange to return from mstart and let the system clean up the thread.
If the runtime allocated the g0 stack, then we use a new exitThread
syscall wrapper that arranges to clear a flag in the M once the stack
can safely be reaped and call the thread termination syscall.
exitThread is based on the existing exit1 wrapper, which was always
meant to terminate the calling thread. However, exit1 has never been
used since it was introduced 9 years ago, so it was broken on several
platforms. exitThread also has the additional complication of having
to flag that the stack is unused, which requires some tricks on
platforms that use the stack for syscalls.
This still leaves the problem of how to reap the unused g0 stacks. For
this, we move the M from allm to a new freem list as part of the M
exiting. Later, allocm scans the freem list, finds Ms that are marked
as done with their stack, removes these from the list and frees their
g0 stacks. This also allows these Ms to be garbage collected.
This CL does not yet use any of this functionality. Follow-up CLs
will. Likewise, there are no new tests in this CL because we'll need
follow-up functionality to test it.
Change-Id: Ic851ee74227b6d39c6fc1219fc71b45d3004bc63
Reviewed-on: https://go-review.googlesource.com/46037
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2017-06-16 15:54:21 -04:00
|
|
|
// sched.lock must be held.
|
2015-10-18 17:04:05 -07:00
|
|
|
func checkdead() {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// For -buildmode=c-shared or -buildmode=c-archive it's OK if
|
2016-03-01 23:21:55 +00:00
|
|
|
// there are no running goroutines. The calling program is
|
2015-10-18 17:04:05 -07:00
|
|
|
// assumed to be running.
|
|
|
|
|
if islibrary || isarchive {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we are dying because of a signal caught on an already idle thread,
|
|
|
|
|
// freezetheworld will cause all running threads to block.
|
|
|
|
|
// And runtime will essentially enter into deadlock state,
|
|
|
|
|
// except that there is a thread that will call exit soon.
|
2022-07-14 17:36:59 -04:00
|
|
|
if panicking.Load() > 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-03 17:25:29 +00:00
|
|
|
// If we are not running under cgo, but we have an extra M then account
|
|
|
|
|
// for it. (It is possible to have an extra M on Windows without cgo to
|
|
|
|
|
// accommodate callbacks created by syscall.NewCallback. See issue #6751
|
|
|
|
|
// for details.)
|
|
|
|
|
var run0 int32
|
|
|
|
|
if !iscgo && cgoHasExtraM {
|
2019-04-25 22:39:56 -07:00
|
|
|
mp := lockextra(true)
|
|
|
|
|
haveExtraM := extraMCount > 0
|
|
|
|
|
unlockextra(mp)
|
|
|
|
|
if haveExtraM {
|
|
|
|
|
run0 = 1
|
|
|
|
|
}
|
2018-06-03 17:25:29 +00:00
|
|
|
}
|
|
|
|
|
|
2017-10-05 21:28:01 -04:00
|
|
|
run := mcount() - sched.nmidle - sched.nmidlelocked - sched.nmsys
|
2018-06-03 17:25:29 +00:00
|
|
|
if run > run0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if run < 0 {
|
2017-10-05 21:28:01 -04:00
|
|
|
print("runtime: checkdead: nmidle=", sched.nmidle, " nmidlelocked=", sched.nmidlelocked, " mcount=", mcount(), " nmsys=", sched.nmsys, "\n")
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("checkdead: inconsistent counts")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
grunning := 0
|
2020-12-23 15:05:37 -05:00
|
|
|
forEachG(func(gp *g) {
|
2018-08-13 16:08:03 -04:00
|
|
|
if isSystemGoroutine(gp, false) {
|
2020-12-23 15:05:37 -05:00
|
|
|
return
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
s := readgstatus(gp)
|
|
|
|
|
switch s &^ _Gscan {
|
2019-09-27 12:27:51 -04:00
|
|
|
case _Gwaiting,
|
|
|
|
|
_Gpreempted:
|
2015-10-18 17:04:05 -07:00
|
|
|
grunning++
|
|
|
|
|
case _Grunnable,
|
|
|
|
|
_Grunning,
|
|
|
|
|
_Gsyscall:
|
|
|
|
|
print("runtime: checkdead: find g ", gp.goid, " in status ", s, "\n")
|
|
|
|
|
throw("checkdead: runnable g")
|
|
|
|
|
}
|
2020-12-23 15:05:37 -05:00
|
|
|
})
|
2015-10-18 17:04:05 -07:00
|
|
|
if grunning == 0 { // possible if main goroutine calls runtime·Goexit()
|
2019-11-01 11:06:21 -07:00
|
|
|
unlock(&sched.lock) // unlock so that GODEBUG=scheddetail=1 doesn't hang
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("no goroutines (main called runtime.Goexit) - deadlock!")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Maybe jump time forward for playground.
|
2020-01-13 12:17:26 -08:00
|
|
|
if faketime != 0 {
|
runtime: use pidleget for faketime jump
In faketime mode, checkdead is responsible for jumping time forward to
the next timer expiration, and waking an M to handle the newly ready
timer.
Currently it pulls the exact P that owns the next timer off of the pidle
list. In theory this is efficient because that P is immediately eligible
to run the timer without stealing. Unfortunately it is also fraught with
peril because we are skipping all of the bookkeeping in pidleget:
* Skipped updates to timerpMask mean that our timers may not be eligible
for stealing, as they should be.
* Skipped updates to idlepMask mean that our runq may not be eligible
for stealing, as they should be.
* Skipped updates to sched.npidle may break tracking of spinning Ms,
potentially resulting in lost work.
* Finally, as of CL 410122, skipped updates to p.limiterEvent may affect
the GC limiter, or cause a fatal throw when another event occurs.
The last case has finally undercovered this issue since it quickly
results in a hard crash.
We could add all of these updates into checkdead, but it is much more
maintainable to keep this logic in one place and use pidleget here like
everywhere else in the runtime. This means we probably won't wake the
P owning the timer, meaning that the P will need to steal the timer,
which is less efficient, but faketime is not a performance-sensitive
build mode. Note that the M will automatically make itself a spinning M
to make it eligible to steal since it is the only one running.
Fixes #53294
For #52890
Change-Id: I4acc3d259b9b4d7dc02608581c8b4fd259f272e9
Reviewed-on: https://go-review.googlesource.com/c/go/+/411119
Run-TryBot: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2022-06-08 15:59:37 -04:00
|
|
|
if when := timeSleepUntil(); when < maxWhen {
|
2020-01-13 12:17:26 -08:00
|
|
|
faketime = when
|
runtime: use pidleget for faketime jump
In faketime mode, checkdead is responsible for jumping time forward to
the next timer expiration, and waking an M to handle the newly ready
timer.
Currently it pulls the exact P that owns the next timer off of the pidle
list. In theory this is efficient because that P is immediately eligible
to run the timer without stealing. Unfortunately it is also fraught with
peril because we are skipping all of the bookkeeping in pidleget:
* Skipped updates to timerpMask mean that our timers may not be eligible
for stealing, as they should be.
* Skipped updates to idlepMask mean that our runq may not be eligible
for stealing, as they should be.
* Skipped updates to sched.npidle may break tracking of spinning Ms,
potentially resulting in lost work.
* Finally, as of CL 410122, skipped updates to p.limiterEvent may affect
the GC limiter, or cause a fatal throw when another event occurs.
The last case has finally undercovered this issue since it quickly
results in a hard crash.
We could add all of these updates into checkdead, but it is much more
maintainable to keep this logic in one place and use pidleget here like
everywhere else in the runtime. This means we probably won't wake the
P owning the timer, meaning that the P will need to steal the timer,
which is less efficient, but faketime is not a performance-sensitive
build mode. Note that the M will automatically make itself a spinning M
to make it eligible to steal since it is the only one running.
Fixes #53294
For #52890
Change-Id: I4acc3d259b9b4d7dc02608581c8b4fd259f272e9
Reviewed-on: https://go-review.googlesource.com/c/go/+/411119
Run-TryBot: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2022-06-08 15:59:37 -04:00
|
|
|
|
|
|
|
|
// Start an M to steal the timer.
|
|
|
|
|
pp, _ := pidleget(faketime)
|
|
|
|
|
if pp == nil {
|
|
|
|
|
// There should always be a free P since
|
|
|
|
|
// nothing is running.
|
|
|
|
|
throw("checkdead: no p for timer")
|
2019-04-11 08:53:42 -07:00
|
|
|
}
|
2020-01-13 12:17:26 -08:00
|
|
|
mp := mget()
|
|
|
|
|
if mp == nil {
|
|
|
|
|
// There should always be a free M since
|
|
|
|
|
// nothing is running.
|
|
|
|
|
throw("checkdead: no m for timer")
|
|
|
|
|
}
|
runtime: use pidleget for faketime jump
In faketime mode, checkdead is responsible for jumping time forward to
the next timer expiration, and waking an M to handle the newly ready
timer.
Currently it pulls the exact P that owns the next timer off of the pidle
list. In theory this is efficient because that P is immediately eligible
to run the timer without stealing. Unfortunately it is also fraught with
peril because we are skipping all of the bookkeeping in pidleget:
* Skipped updates to timerpMask mean that our timers may not be eligible
for stealing, as they should be.
* Skipped updates to idlepMask mean that our runq may not be eligible
for stealing, as they should be.
* Skipped updates to sched.npidle may break tracking of spinning Ms,
potentially resulting in lost work.
* Finally, as of CL 410122, skipped updates to p.limiterEvent may affect
the GC limiter, or cause a fatal throw when another event occurs.
The last case has finally undercovered this issue since it quickly
results in a hard crash.
We could add all of these updates into checkdead, but it is much more
maintainable to keep this logic in one place and use pidleget here like
everywhere else in the runtime. This means we probably won't wake the
P owning the timer, meaning that the P will need to steal the timer,
which is less efficient, but faketime is not a performance-sensitive
build mode. Note that the M will automatically make itself a spinning M
to make it eligible to steal since it is the only one running.
Fixes #53294
For #52890
Change-Id: I4acc3d259b9b4d7dc02608581c8b4fd259f272e9
Reviewed-on: https://go-review.googlesource.com/c/go/+/411119
Run-TryBot: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2022-06-08 15:59:37 -04:00
|
|
|
// M must be spinning to steal. We set this to be
|
|
|
|
|
// explicit, but since this is the only M it would
|
|
|
|
|
// become spinning on its own anyways.
|
2022-07-25 15:20:22 -04:00
|
|
|
sched.nmspinning.Add(1)
|
runtime: use pidleget for faketime jump
In faketime mode, checkdead is responsible for jumping time forward to
the next timer expiration, and waking an M to handle the newly ready
timer.
Currently it pulls the exact P that owns the next timer off of the pidle
list. In theory this is efficient because that P is immediately eligible
to run the timer without stealing. Unfortunately it is also fraught with
peril because we are skipping all of the bookkeeping in pidleget:
* Skipped updates to timerpMask mean that our timers may not be eligible
for stealing, as they should be.
* Skipped updates to idlepMask mean that our runq may not be eligible
for stealing, as they should be.
* Skipped updates to sched.npidle may break tracking of spinning Ms,
potentially resulting in lost work.
* Finally, as of CL 410122, skipped updates to p.limiterEvent may affect
the GC limiter, or cause a fatal throw when another event occurs.
The last case has finally undercovered this issue since it quickly
results in a hard crash.
We could add all of these updates into checkdead, but it is much more
maintainable to keep this logic in one place and use pidleget here like
everywhere else in the runtime. This means we probably won't wake the
P owning the timer, meaning that the P will need to steal the timer,
which is less efficient, but faketime is not a performance-sensitive
build mode. Note that the M will automatically make itself a spinning M
to make it eligible to steal since it is the only one running.
Fixes #53294
For #52890
Change-Id: I4acc3d259b9b4d7dc02608581c8b4fd259f272e9
Reviewed-on: https://go-review.googlesource.com/c/go/+/411119
Run-TryBot: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2022-06-08 15:59:37 -04:00
|
|
|
mp.spinning = true
|
|
|
|
|
mp.nextp.set(pp)
|
2020-01-13 12:17:26 -08:00
|
|
|
notewakeup(&mp.park)
|
|
|
|
|
return
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-05 16:24:14 -07:00
|
|
|
// There are no goroutines running, so we can look at the P's.
|
2021-02-09 15:48:41 -05:00
|
|
|
for _, pp := range allp {
|
|
|
|
|
if len(pp.timers) > 0 {
|
2019-04-05 16:24:14 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-18 10:42:17 -08:00
|
|
|
unlock(&sched.lock) // unlock so that GODEBUG=scheddetail=1 doesn't hang
|
2022-03-04 13:24:04 -05:00
|
|
|
fatal("all goroutines are asleep - deadlock!")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// forcegcperiod is the maximum time in nanoseconds between garbage
|
|
|
|
|
// collections. If we go this long without a garbage collection, one
|
|
|
|
|
// is forced to run.
|
|
|
|
|
//
|
|
|
|
|
// This is a variable for testing purposes. It normally doesn't change.
|
|
|
|
|
var forcegcperiod int64 = 2 * 60 * 1e9
|
|
|
|
|
|
2021-06-03 16:57:54 +02:00
|
|
|
// needSysmonWorkaround is true if the workaround for
|
|
|
|
|
// golang.org/issue/42515 is needed on NetBSD.
|
|
|
|
|
var needSysmonWorkaround bool = false
|
|
|
|
|
|
2015-11-17 17:31:04 -05:00
|
|
|
// Always runs without a P, so write barriers are not allowed.
|
|
|
|
|
//
|
|
|
|
|
//go:nowritebarrierrec
|
2015-10-18 17:04:05 -07:00
|
|
|
func sysmon() {
|
2017-06-15 10:51:15 -04:00
|
|
|
lock(&sched.lock)
|
|
|
|
|
sched.nmsys++
|
|
|
|
|
checkdead()
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
lasttrace := int64(0)
|
|
|
|
|
idle := 0 // how many cycles in succession we had not wokeup somebody
|
|
|
|
|
delay := uint32(0)
|
syscall: support POSIX semantics for Linux syscalls
This change adds two new methods for invoking system calls
under Linux: syscall.AllThreadsSyscall() and
syscall.AllThreadsSyscall6().
These system call wrappers ensure that all OSThreads mirror
a common system call. The wrappers serialize execution of the
runtime to ensure no race conditions where any Go code observes
a non-atomic OS state change. As such, the syscalls have
higher runtime overhead than regular system calls, and only
need to be used where such thread (or 'm' in the parlance
of the runtime sources) consistency is required.
The new support is used to enable these functions under Linux:
syscall.Setegid(), syscall.Seteuid(), syscall.Setgroups(),
syscall.Setgid(), syscall.Setregid(), syscall.Setreuid(),
syscall.Setresgid(), syscall.Setresuid() and syscall.Setuid().
They work identically to their glibc counterparts.
Extensive discussion of the background issue addressed in this
patch can be found here:
https://github.com/golang/go/issues/1435
In the case where cgo is used, the C runtime can launch pthreads that
are not managed by the Go runtime. As such, the added
syscall.AllThreadsSyscall*() return ENOTSUP when cgo is enabled.
However, for the 9 syscall.Set*() functions listed above, when cgo is
active, these functions redirect to invoke their C.set*() equivalents
in glibc, which wraps the raw system calls with a nptl:setxid fixup
mechanism. This achieves POSIX semantics for these functions in the
combined Go and C runtime.
As a side note, the glibc/nptl:setxid support (2019-11-30) does not
extend to all security related system calls under Linux so using
native Go (CGO_ENABLED=0) and these AllThreadsSyscall*()s, where
needed, will yield more well defined/consistent behavior over all
threads of a Go program. That is, using the
syscall.AllThreadsSyscall*() wrappers for things like setting state
through SYS_PRCTL and SYS_CAPSET etc.
Fixes #1435
Change-Id: Ib1a3e16b9180f64223196a32fc0f9dce14d9105c
Reviewed-on: https://go-review.googlesource.com/c/go/+/210639
Trust: Emmanuel Odeke <emm.odeke@gmail.com>
Trust: Ian Lance Taylor <iant@golang.org>
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Emmanuel Odeke <emm.odeke@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Austin Clements <austin@google.com>
2019-12-09 21:50:16 -08:00
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
for {
|
|
|
|
|
if idle == 0 { // start with 20us sleep...
|
|
|
|
|
delay = 20
|
|
|
|
|
} else if idle > 50 { // start doubling the sleep after 1ms...
|
|
|
|
|
delay *= 2
|
|
|
|
|
}
|
|
|
|
|
if delay > 10*1000 { // up to 10ms
|
|
|
|
|
delay = 10 * 1000
|
|
|
|
|
}
|
|
|
|
|
usleep(delay)
|
2020-05-01 17:04:36 -04:00
|
|
|
|
|
|
|
|
// sysmon should not enter deep sleep if schedtrace is enabled so that
|
|
|
|
|
// it can print that information at the right time.
|
|
|
|
|
//
|
|
|
|
|
// It should also not enter deep sleep if there are any active P's so
|
|
|
|
|
// that it can retake P's from syscalls, preempt long running G's, and
|
|
|
|
|
// poll the network if all P's are busy for long stretches.
|
|
|
|
|
//
|
|
|
|
|
// It should wakeup from deep sleep if any P's become active either due
|
|
|
|
|
// to exiting a syscall or waking up due to a timer expiring so that it
|
|
|
|
|
// can resume performing those duties. If it wakes from a syscall it
|
|
|
|
|
// resets idle and delay as a bet that since it had retaken a P from a
|
|
|
|
|
// syscall before, it may need to do it again shortly after the
|
|
|
|
|
// application starts work again. It does not reset idle when waking
|
|
|
|
|
// from a timer to avoid adding system load to applications that spend
|
|
|
|
|
// most of their time sleeping.
|
runtime: wake netpoller when dropping P, don't sleep too long in sysmon
When dropping a P, if it has any timers, and if some thread is
sleeping in the netpoller, wake the netpoller to run the P's timers.
This mitigates races between the netpoller deciding how long to sleep
and a new timer being added.
In sysmon, if all P's are idle, check the timers to decide how long to sleep.
This avoids oversleeping if no thread is using the netpoller.
This can happen in particular if some threads use runtime.LockOSThread,
as those threads do not block in the netpoller.
Also, print the number of timers per P for GODEBUG=scheddetail=1.
Before this CL, TestLockedDeadlock2 would fail about 1% of the time.
With this CL, I ran it 150,000 times with no failures.
Updates #6239
Updates #27707
Fixes #35274
Fixes #35288
Change-Id: I7e5193e6c885e567f0b1ee023664aa3e2902fcd1
Reviewed-on: https://go-review.googlesource.com/c/go/+/204800
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2019-11-01 12:38:10 -07:00
|
|
|
now := nanotime()
|
2022-07-25 15:31:03 -04:00
|
|
|
if debug.schedtrace <= 0 && (sched.gcwaiting.Load() || sched.npidle.Load() == gomaxprocs) {
|
2015-10-18 17:04:05 -07:00
|
|
|
lock(&sched.lock)
|
2022-07-25 15:31:03 -04:00
|
|
|
if sched.gcwaiting.Load() || sched.npidle.Load() == gomaxprocs {
|
2020-05-01 17:04:36 -04:00
|
|
|
syscallWake := false
|
runtime: use pidleget for faketime jump
In faketime mode, checkdead is responsible for jumping time forward to
the next timer expiration, and waking an M to handle the newly ready
timer.
Currently it pulls the exact P that owns the next timer off of the pidle
list. In theory this is efficient because that P is immediately eligible
to run the timer without stealing. Unfortunately it is also fraught with
peril because we are skipping all of the bookkeeping in pidleget:
* Skipped updates to timerpMask mean that our timers may not be eligible
for stealing, as they should be.
* Skipped updates to idlepMask mean that our runq may not be eligible
for stealing, as they should be.
* Skipped updates to sched.npidle may break tracking of spinning Ms,
potentially resulting in lost work.
* Finally, as of CL 410122, skipped updates to p.limiterEvent may affect
the GC limiter, or cause a fatal throw when another event occurs.
The last case has finally undercovered this issue since it quickly
results in a hard crash.
We could add all of these updates into checkdead, but it is much more
maintainable to keep this logic in one place and use pidleget here like
everywhere else in the runtime. This means we probably won't wake the
P owning the timer, meaning that the P will need to steal the timer,
which is less efficient, but faketime is not a performance-sensitive
build mode. Note that the M will automatically make itself a spinning M
to make it eligible to steal since it is the only one running.
Fixes #53294
For #52890
Change-Id: I4acc3d259b9b4d7dc02608581c8b4fd259f272e9
Reviewed-on: https://go-review.googlesource.com/c/go/+/411119
Run-TryBot: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2022-06-08 15:59:37 -04:00
|
|
|
next := timeSleepUntil()
|
runtime: wake netpoller when dropping P, don't sleep too long in sysmon
When dropping a P, if it has any timers, and if some thread is
sleeping in the netpoller, wake the netpoller to run the P's timers.
This mitigates races between the netpoller deciding how long to sleep
and a new timer being added.
In sysmon, if all P's are idle, check the timers to decide how long to sleep.
This avoids oversleeping if no thread is using the netpoller.
This can happen in particular if some threads use runtime.LockOSThread,
as those threads do not block in the netpoller.
Also, print the number of timers per P for GODEBUG=scheddetail=1.
Before this CL, TestLockedDeadlock2 would fail about 1% of the time.
With this CL, I ran it 150,000 times with no failures.
Updates #6239
Updates #27707
Fixes #35274
Fixes #35288
Change-Id: I7e5193e6c885e567f0b1ee023664aa3e2902fcd1
Reviewed-on: https://go-review.googlesource.com/c/go/+/204800
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2019-11-01 12:38:10 -07:00
|
|
|
if next > now {
|
2022-07-25 15:39:07 -04:00
|
|
|
sched.sysmonwait.Store(true)
|
runtime: wake netpoller when dropping P, don't sleep too long in sysmon
When dropping a P, if it has any timers, and if some thread is
sleeping in the netpoller, wake the netpoller to run the P's timers.
This mitigates races between the netpoller deciding how long to sleep
and a new timer being added.
In sysmon, if all P's are idle, check the timers to decide how long to sleep.
This avoids oversleeping if no thread is using the netpoller.
This can happen in particular if some threads use runtime.LockOSThread,
as those threads do not block in the netpoller.
Also, print the number of timers per P for GODEBUG=scheddetail=1.
Before this CL, TestLockedDeadlock2 would fail about 1% of the time.
With this CL, I ran it 150,000 times with no failures.
Updates #6239
Updates #27707
Fixes #35274
Fixes #35288
Change-Id: I7e5193e6c885e567f0b1ee023664aa3e2902fcd1
Reviewed-on: https://go-review.googlesource.com/c/go/+/204800
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2019-11-01 12:38:10 -07:00
|
|
|
unlock(&sched.lock)
|
|
|
|
|
// Make wake-up period small enough
|
|
|
|
|
// for the sampling to be correct.
|
|
|
|
|
sleep := forcegcperiod / 2
|
|
|
|
|
if next-now < sleep {
|
|
|
|
|
sleep = next - now
|
2017-07-07 12:03:22 -04:00
|
|
|
}
|
runtime: wake netpoller when dropping P, don't sleep too long in sysmon
When dropping a P, if it has any timers, and if some thread is
sleeping in the netpoller, wake the netpoller to run the P's timers.
This mitigates races between the netpoller deciding how long to sleep
and a new timer being added.
In sysmon, if all P's are idle, check the timers to decide how long to sleep.
This avoids oversleeping if no thread is using the netpoller.
This can happen in particular if some threads use runtime.LockOSThread,
as those threads do not block in the netpoller.
Also, print the number of timers per P for GODEBUG=scheddetail=1.
Before this CL, TestLockedDeadlock2 would fail about 1% of the time.
With this CL, I ran it 150,000 times with no failures.
Updates #6239
Updates #27707
Fixes #35274
Fixes #35288
Change-Id: I7e5193e6c885e567f0b1ee023664aa3e2902fcd1
Reviewed-on: https://go-review.googlesource.com/c/go/+/204800
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2019-11-01 12:38:10 -07:00
|
|
|
shouldRelax := sleep >= osRelaxMinNS
|
|
|
|
|
if shouldRelax {
|
|
|
|
|
osRelax(true)
|
|
|
|
|
}
|
2020-05-01 17:04:36 -04:00
|
|
|
syscallWake = notetsleep(&sched.sysmonnote, sleep)
|
runtime: wake netpoller when dropping P, don't sleep too long in sysmon
When dropping a P, if it has any timers, and if some thread is
sleeping in the netpoller, wake the netpoller to run the P's timers.
This mitigates races between the netpoller deciding how long to sleep
and a new timer being added.
In sysmon, if all P's are idle, check the timers to decide how long to sleep.
This avoids oversleeping if no thread is using the netpoller.
This can happen in particular if some threads use runtime.LockOSThread,
as those threads do not block in the netpoller.
Also, print the number of timers per P for GODEBUG=scheddetail=1.
Before this CL, TestLockedDeadlock2 would fail about 1% of the time.
With this CL, I ran it 150,000 times with no failures.
Updates #6239
Updates #27707
Fixes #35274
Fixes #35288
Change-Id: I7e5193e6c885e567f0b1ee023664aa3e2902fcd1
Reviewed-on: https://go-review.googlesource.com/c/go/+/204800
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2019-11-01 12:38:10 -07:00
|
|
|
if shouldRelax {
|
|
|
|
|
osRelax(false)
|
|
|
|
|
}
|
|
|
|
|
lock(&sched.lock)
|
2022-07-25 15:39:07 -04:00
|
|
|
sched.sysmonwait.Store(false)
|
runtime: wake netpoller when dropping P, don't sleep too long in sysmon
When dropping a P, if it has any timers, and if some thread is
sleeping in the netpoller, wake the netpoller to run the P's timers.
This mitigates races between the netpoller deciding how long to sleep
and a new timer being added.
In sysmon, if all P's are idle, check the timers to decide how long to sleep.
This avoids oversleeping if no thread is using the netpoller.
This can happen in particular if some threads use runtime.LockOSThread,
as those threads do not block in the netpoller.
Also, print the number of timers per P for GODEBUG=scheddetail=1.
Before this CL, TestLockedDeadlock2 would fail about 1% of the time.
With this CL, I ran it 150,000 times with no failures.
Updates #6239
Updates #27707
Fixes #35274
Fixes #35288
Change-Id: I7e5193e6c885e567f0b1ee023664aa3e2902fcd1
Reviewed-on: https://go-review.googlesource.com/c/go/+/204800
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2019-11-01 12:38:10 -07:00
|
|
|
noteclear(&sched.sysmonnote)
|
2017-07-07 12:03:22 -04:00
|
|
|
}
|
2020-05-01 17:04:36 -04:00
|
|
|
if syscallWake {
|
|
|
|
|
idle = 0
|
|
|
|
|
delay = 20
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
}
|
2020-05-01 17:04:36 -04:00
|
|
|
|
2020-05-19 16:33:17 +00:00
|
|
|
lock(&sched.sysmonlock)
|
2020-05-01 17:04:36 -04:00
|
|
|
// Update now in case we blocked on sysmonnote or spent a long time
|
|
|
|
|
// blocked on schedlock or sysmonlock above.
|
|
|
|
|
now = nanotime()
|
2020-05-19 16:33:17 +00:00
|
|
|
|
2017-03-08 15:40:28 -05:00
|
|
|
// trigger libc interceptors if needed
|
2017-03-23 22:47:56 -04:00
|
|
|
if *cgo_yield != nil {
|
|
|
|
|
asmcgocall(*cgo_yield, nil)
|
2017-03-08 15:40:28 -05:00
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
// poll network if not polled for more than 10ms
|
2022-07-20 17:39:12 -04:00
|
|
|
lastpoll := sched.lastpoll.Load()
|
2017-11-06 20:51:36 -08:00
|
|
|
if netpollinited() && lastpoll != 0 && lastpoll+10*1000*1000 < now {
|
2022-07-20 17:39:12 -04:00
|
|
|
sched.lastpoll.CompareAndSwap(lastpoll, now)
|
2019-04-02 20:27:35 -07:00
|
|
|
list := netpoll(0) // non-blocking - returns list of goroutines
|
2018-08-10 00:09:00 -04:00
|
|
|
if !list.empty() {
|
2015-10-18 17:04:05 -07:00
|
|
|
// Need to decrement number of idle locked M's
|
|
|
|
|
// (pretending that one more is running) before injectglist.
|
|
|
|
|
// Otherwise it can lead to the following situation:
|
|
|
|
|
// injectglist grabs all P's but before it starts M's to run the P's,
|
|
|
|
|
// another M returns from syscall, finishes running its G,
|
|
|
|
|
// observes that there is no work to do and no other running M's
|
|
|
|
|
// and reports deadlock.
|
|
|
|
|
incidlelocked(-1)
|
2018-08-10 10:33:05 -04:00
|
|
|
injectglist(&list)
|
2015-10-18 17:04:05 -07:00
|
|
|
incidlelocked(1)
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-03 16:57:54 +02:00
|
|
|
if GOOS == "netbsd" && needSysmonWorkaround {
|
2020-12-11 14:14:30 -05:00
|
|
|
// netpoll is responsible for waiting for timer
|
|
|
|
|
// expiration, so we typically don't have to worry
|
|
|
|
|
// about starting an M to service timers. (Note that
|
|
|
|
|
// sleep for timeSleepUntil above simply ensures sysmon
|
|
|
|
|
// starts running again when that timer expiration may
|
|
|
|
|
// cause Go code to run again).
|
|
|
|
|
//
|
|
|
|
|
// However, netbsd has a kernel bug that sometimes
|
|
|
|
|
// misses netpollBreak wake-ups, which can lead to
|
|
|
|
|
// unbounded delays servicing timers. If we detect this
|
|
|
|
|
// overrun, then startm to get something to handle the
|
|
|
|
|
// timer.
|
|
|
|
|
//
|
|
|
|
|
// See issue 42515 and
|
|
|
|
|
// https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=50094.
|
runtime: use pidleget for faketime jump
In faketime mode, checkdead is responsible for jumping time forward to
the next timer expiration, and waking an M to handle the newly ready
timer.
Currently it pulls the exact P that owns the next timer off of the pidle
list. In theory this is efficient because that P is immediately eligible
to run the timer without stealing. Unfortunately it is also fraught with
peril because we are skipping all of the bookkeeping in pidleget:
* Skipped updates to timerpMask mean that our timers may not be eligible
for stealing, as they should be.
* Skipped updates to idlepMask mean that our runq may not be eligible
for stealing, as they should be.
* Skipped updates to sched.npidle may break tracking of spinning Ms,
potentially resulting in lost work.
* Finally, as of CL 410122, skipped updates to p.limiterEvent may affect
the GC limiter, or cause a fatal throw when another event occurs.
The last case has finally undercovered this issue since it quickly
results in a hard crash.
We could add all of these updates into checkdead, but it is much more
maintainable to keep this logic in one place and use pidleget here like
everywhere else in the runtime. This means we probably won't wake the
P owning the timer, meaning that the P will need to steal the timer,
which is less efficient, but faketime is not a performance-sensitive
build mode. Note that the M will automatically make itself a spinning M
to make it eligible to steal since it is the only one running.
Fixes #53294
For #52890
Change-Id: I4acc3d259b9b4d7dc02608581c8b4fd259f272e9
Reviewed-on: https://go-review.googlesource.com/c/go/+/411119
Run-TryBot: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2022-06-08 15:59:37 -04:00
|
|
|
if next := timeSleepUntil(); next < now {
|
2020-12-11 14:14:30 -05:00
|
|
|
startm(nil, false)
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-10 00:49:44 +00:00
|
|
|
if scavenger.sysmonWake.Load() != 0 {
|
runtime: wake scavenger and update address on sweep done
This change modifies the semantics of waking the scavenger: rather than
wake on any update to pacing, wake when we know we will have work to do,
that is, when the sweeper is done. The current scavenger runs over the
address space just once per GC cycle, and we want to maximize the chance
that the scavenger observes the most attractive scavengable memory in
that pass (i.e. free memory with the highest address), so the timing is
important. By having the scavenger awaken and reset its search space
when the sweeper is done, we increase the chance that the scavenger will
observe the most attractive scavengable memory, because no more memory
will be freed that GC cycle (so the highest scavengable address should
now be available).
Furthermore, in applications that go idle, this means the background
scavenger will be awoken even if another GC doesn't happen, which isn't
true today.
However, we're unable to wake the scavenger directly from within the
sweeper; waking the scavenger involves modifying timers and readying
goroutines, the latter of which may trigger an allocation today (and the
sweeper may run during allocation!). Instead, we do the following:
1. Set a flag which is checked by sysmon. sysmon will clear the flag and
wake the scavenger.
2. Wake the scavenger unconditionally at sweep termination.
The idea behind this policy is that it gets us close enough to the state
above without having to deal with the complexity of waking the scavenger
in deep parts of the runtime. If the application goes idle and sweeping
finishes (so we don't reach sweep termination), then sysmon will wake
the scavenger. sysmon has a worst-case 20 ms delay in responding to this
signal, which is probably fine if the application is completely idle
anyway, but if the application is actively allocating, then the
proportional sweeper should help ensure that sweeping ends very close to
sweep termination, so sweep termination is a perfectly reasonable time
to wake up the scavenger.
Updates #35788.
Change-Id: I84289b37816a7d595d803c72a71b7f5c59d47e6b
Reviewed-on: https://go-review.googlesource.com/c/go/+/207998
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2019-11-19 17:32:17 +00:00
|
|
|
// Kick the scavenger awake if someone requested it.
|
2022-02-10 00:49:44 +00:00
|
|
|
scavenger.wake()
|
runtime: wake scavenger and update address on sweep done
This change modifies the semantics of waking the scavenger: rather than
wake on any update to pacing, wake when we know we will have work to do,
that is, when the sweeper is done. The current scavenger runs over the
address space just once per GC cycle, and we want to maximize the chance
that the scavenger observes the most attractive scavengable memory in
that pass (i.e. free memory with the highest address), so the timing is
important. By having the scavenger awaken and reset its search space
when the sweeper is done, we increase the chance that the scavenger will
observe the most attractive scavengable memory, because no more memory
will be freed that GC cycle (so the highest scavengable address should
now be available).
Furthermore, in applications that go idle, this means the background
scavenger will be awoken even if another GC doesn't happen, which isn't
true today.
However, we're unable to wake the scavenger directly from within the
sweeper; waking the scavenger involves modifying timers and readying
goroutines, the latter of which may trigger an allocation today (and the
sweeper may run during allocation!). Instead, we do the following:
1. Set a flag which is checked by sysmon. sysmon will clear the flag and
wake the scavenger.
2. Wake the scavenger unconditionally at sweep termination.
The idea behind this policy is that it gets us close enough to the state
above without having to deal with the complexity of waking the scavenger
in deep parts of the runtime. If the application goes idle and sweeping
finishes (so we don't reach sweep termination), then sysmon will wake
the scavenger. sysmon has a worst-case 20 ms delay in responding to this
signal, which is probably fine if the application is completely idle
anyway, but if the application is actively allocating, then the
proportional sweeper should help ensure that sweeping ends very close to
sweep termination, so sweep termination is a perfectly reasonable time
to wake up the scavenger.
Updates #35788.
Change-Id: I84289b37816a7d595d803c72a71b7f5c59d47e6b
Reviewed-on: https://go-review.googlesource.com/c/go/+/207998
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
2019-11-19 17:32:17 +00:00
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
// retake P's blocked in syscalls
|
|
|
|
|
// and preempt long running G's
|
|
|
|
|
if retake(now) != 0 {
|
|
|
|
|
idle = 0
|
|
|
|
|
} else {
|
|
|
|
|
idle++
|
|
|
|
|
}
|
|
|
|
|
// check if we need to force a GC
|
2022-08-25 03:03:35 +08:00
|
|
|
if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && forcegc.idle.Load() {
|
2015-10-18 17:04:05 -07:00
|
|
|
lock(&forcegc.lock)
|
2022-08-25 03:03:35 +08:00
|
|
|
forcegc.idle.Store(false)
|
2018-08-10 10:33:05 -04:00
|
|
|
var list gList
|
|
|
|
|
list.push(forcegc.g)
|
|
|
|
|
injectglist(&list)
|
2015-10-18 17:04:05 -07:00
|
|
|
unlock(&forcegc.lock)
|
|
|
|
|
}
|
2015-12-10 15:06:42 -08:00
|
|
|
if debug.schedtrace > 0 && lasttrace+int64(debug.schedtrace)*1000000 <= now {
|
2015-10-18 17:04:05 -07:00
|
|
|
lasttrace = now
|
|
|
|
|
schedtrace(debug.scheddetail > 0)
|
|
|
|
|
}
|
2020-05-19 16:33:17 +00:00
|
|
|
unlock(&sched.sysmonlock)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-13 11:14:43 -04:00
|
|
|
type sysmontick struct {
|
2015-10-18 17:04:05 -07:00
|
|
|
schedtick uint32
|
|
|
|
|
schedwhen int64
|
|
|
|
|
syscalltick uint32
|
|
|
|
|
syscallwhen int64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// forcePreemptNS is the time slice given to a G before it is
|
|
|
|
|
// preempted.
|
|
|
|
|
const forcePreemptNS = 10 * 1000 * 1000 // 10ms
|
|
|
|
|
|
|
|
|
|
func retake(now int64) uint32 {
|
|
|
|
|
n := 0
|
2017-06-13 11:32:17 -04:00
|
|
|
// Prevent allp slice changes. This lock will be completely
|
|
|
|
|
// uncontended unless we're already stopping the world.
|
|
|
|
|
lock(&allpLock)
|
2017-06-13 12:01:56 -04:00
|
|
|
// We can't use a range loop over allp because we may
|
|
|
|
|
// temporarily drop the allpLock. Hence, we need to re-fetch
|
|
|
|
|
// allp each time around the loop.
|
2017-06-13 11:32:17 -04:00
|
|
|
for i := 0; i < len(allp); i++ {
|
2021-02-09 15:48:41 -05:00
|
|
|
pp := allp[i]
|
|
|
|
|
if pp == nil {
|
2017-06-13 12:01:56 -04:00
|
|
|
// This can happen if procresize has grown
|
|
|
|
|
// allp but not yet created new Ps.
|
2015-10-18 17:04:05 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
pd := &pp.sysmontick
|
|
|
|
|
s := pp.status
|
2019-03-29 10:43:31 -07:00
|
|
|
sysretake := false
|
|
|
|
|
if s == _Prunning || s == _Psyscall {
|
|
|
|
|
// Preempt G if it's running for too long.
|
2021-02-09 15:48:41 -05:00
|
|
|
t := int64(pp.schedtick)
|
2019-03-29 10:43:31 -07:00
|
|
|
if int64(pd.schedtick) != t {
|
|
|
|
|
pd.schedtick = uint32(t)
|
|
|
|
|
pd.schedwhen = now
|
|
|
|
|
} else if pd.schedwhen+forcePreemptNS <= now {
|
2021-02-09 15:48:41 -05:00
|
|
|
preemptone(pp)
|
2019-03-29 10:43:31 -07:00
|
|
|
// In case of syscall, preemptone() doesn't
|
|
|
|
|
// work, because there is no M wired to P.
|
|
|
|
|
sysretake = true
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
if s == _Psyscall {
|
|
|
|
|
// Retake P from syscall if it's there for more than 1 sysmon tick (at least 20us).
|
2021-02-09 15:48:41 -05:00
|
|
|
t := int64(pp.syscalltick)
|
2019-03-29 10:43:31 -07:00
|
|
|
if !sysretake && int64(pd.syscalltick) != t {
|
2015-10-18 17:04:05 -07:00
|
|
|
pd.syscalltick = uint32(t)
|
|
|
|
|
pd.syscallwhen = now
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// On the one hand we don't want to retake Ps if there is no other work to do,
|
|
|
|
|
// but on the other hand we want to retake them eventually
|
|
|
|
|
// because they can prevent the sysmon thread from deep sleep.
|
2022-07-25 15:20:22 -04:00
|
|
|
if runqempty(pp) && sched.nmspinning.Load()+sched.npidle.Load() > 0 && pd.syscallwhen+10*1000*1000 > now {
|
2015-10-18 17:04:05 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2017-06-13 11:32:17 -04:00
|
|
|
// Drop allpLock so we can take sched.lock.
|
|
|
|
|
unlock(&allpLock)
|
2015-10-18 17:04:05 -07:00
|
|
|
// Need to decrement number of idle locked M's
|
|
|
|
|
// (pretending that one more is running) before the CAS.
|
|
|
|
|
// Otherwise the M from which we retake can exit the syscall,
|
|
|
|
|
// increment nmidle and report deadlock.
|
|
|
|
|
incidlelocked(-1)
|
2021-02-09 15:48:41 -05:00
|
|
|
if atomic.Cas(&pp.status, s, _Pidle) {
|
2015-10-18 17:04:05 -07:00
|
|
|
if trace.enabled {
|
2021-02-09 15:48:41 -05:00
|
|
|
traceGoSysBlock(pp)
|
|
|
|
|
traceProcStop(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
n++
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.syscalltick++
|
|
|
|
|
handoffp(pp)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
incidlelocked(1)
|
2017-06-13 11:32:17 -04:00
|
|
|
lock(&allpLock)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
2017-06-13 11:32:17 -04:00
|
|
|
unlock(&allpLock)
|
2015-10-18 17:04:05 -07:00
|
|
|
return uint32(n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tell all goroutines that they have been preempted and they should stop.
|
2016-03-01 23:21:55 +00:00
|
|
|
// This function is purely best-effort. It can fail to inform a goroutine if a
|
2015-10-18 17:04:05 -07:00
|
|
|
// processor just started running it.
|
|
|
|
|
// No locks need to be held.
|
|
|
|
|
// Returns true if preemption request was issued to at least one goroutine.
|
|
|
|
|
func preemptall() bool {
|
|
|
|
|
res := false
|
2021-02-09 15:48:41 -05:00
|
|
|
for _, pp := range allp {
|
|
|
|
|
if pp.status != _Prunning {
|
2015-10-18 17:04:05 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if preemptone(pp) {
|
2015-10-18 17:04:05 -07:00
|
|
|
res = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return res
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tell the goroutine running on processor P to stop.
|
2016-03-01 23:21:55 +00:00
|
|
|
// This function is purely best-effort. It can incorrectly fail to inform the
|
2021-03-28 12:17:35 +00:00
|
|
|
// goroutine. It can inform the wrong goroutine. Even if it informs the
|
2015-10-18 17:04:05 -07:00
|
|
|
// correct goroutine, that goroutine might ignore the request if it is
|
|
|
|
|
// simultaneously executing newstack.
|
|
|
|
|
// No lock needs to be held.
|
|
|
|
|
// Returns true if preemption request was issued.
|
|
|
|
|
// The actual preemption will happen at some point in the future
|
|
|
|
|
// and will be indicated by the gp->status no longer being
|
|
|
|
|
// Grunning
|
2021-02-09 15:48:41 -05:00
|
|
|
func preemptone(pp *p) bool {
|
|
|
|
|
mp := pp.m.ptr()
|
2015-10-18 17:04:05 -07:00
|
|
|
if mp == nil || mp == getg().m {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
gp := mp.curg
|
|
|
|
|
if gp == nil || gp == mp.g0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gp.preempt = true
|
|
|
|
|
|
2021-03-28 12:17:35 +00:00
|
|
|
// Every call in a goroutine checks for stack overflow by
|
2015-10-18 17:04:05 -07:00
|
|
|
// comparing the current stack pointer to gp->stackguard0.
|
|
|
|
|
// Setting gp->stackguard0 to StackPreempt folds
|
|
|
|
|
// preemption into the normal stack overflow check.
|
|
|
|
|
gp.stackguard0 = stackPreempt
|
2019-10-12 21:23:29 -04:00
|
|
|
|
|
|
|
|
// Request an async preemption of this P.
|
|
|
|
|
if preemptMSupported && debug.asyncpreemptoff == 0 {
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.preempt = true
|
2019-10-12 21:23:29 -04:00
|
|
|
preemptM(mp)
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var starttime int64
|
|
|
|
|
|
|
|
|
|
func schedtrace(detailed bool) {
|
|
|
|
|
now := nanotime()
|
|
|
|
|
if starttime == 0 {
|
|
|
|
|
starttime = now
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lock(&sched.lock)
|
2022-03-01 15:06:37 -05:00
|
|
|
print("SCHED ", (now-starttime)/1e6, "ms: gomaxprocs=", gomaxprocs, " idleprocs=", sched.npidle.Load(), " threads=", mcount(), " spinningthreads=", sched.nmspinning.Load(), " needspinning=", sched.needspinning.Load(), " idlethreads=", sched.nmidle, " runqueue=", sched.runqsize)
|
2015-10-18 17:04:05 -07:00
|
|
|
if detailed {
|
2022-07-25 15:39:07 -04:00
|
|
|
print(" gcwaiting=", sched.gcwaiting.Load(), " nmidlelocked=", sched.nmidlelocked, " stopwait=", sched.stopwait, " sysmonwait=", sched.sysmonwait.Load(), "\n")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
// We must be careful while reading data from P's, M's and G's.
|
|
|
|
|
// Even if we hold schedlock, most data can be changed concurrently.
|
|
|
|
|
// E.g. (p->m ? p->m->id : -1) can crash if p->m changes from non-nil to nil.
|
2021-02-09 15:48:41 -05:00
|
|
|
for i, pp := range allp {
|
|
|
|
|
mp := pp.m.ptr()
|
|
|
|
|
h := atomic.Load(&pp.runqhead)
|
|
|
|
|
t := atomic.Load(&pp.runqtail)
|
2015-10-18 17:04:05 -07:00
|
|
|
if detailed {
|
2022-07-19 14:19:09 -04:00
|
|
|
print(" P", i, ": status=", pp.status, " schedtick=", pp.schedtick, " syscalltick=", pp.syscalltick, " m=")
|
2015-10-18 17:04:05 -07:00
|
|
|
if mp != nil {
|
2022-07-19 14:19:09 -04:00
|
|
|
print(mp.id)
|
|
|
|
|
} else {
|
|
|
|
|
print("nil")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2022-07-19 14:19:09 -04:00
|
|
|
print(" runqsize=", t-h, " gfreecnt=", pp.gFree.n, " timerslen=", len(pp.timers), "\n")
|
2015-10-18 17:04:05 -07:00
|
|
|
} else {
|
|
|
|
|
// In non-detailed mode format lengths of per-P run queues as:
|
|
|
|
|
// [len1 len2 len3 len4]
|
|
|
|
|
print(" ")
|
|
|
|
|
if i == 0 {
|
|
|
|
|
print("[")
|
|
|
|
|
}
|
|
|
|
|
print(t - h)
|
2017-06-13 12:01:56 -04:00
|
|
|
if i == len(allp)-1 {
|
2015-10-18 17:04:05 -07:00
|
|
|
print("]\n")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !detailed {
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for mp := allm; mp != nil; mp = mp.alllink {
|
2021-02-09 15:48:41 -05:00
|
|
|
pp := mp.p.ptr()
|
2022-07-19 14:19:09 -04:00
|
|
|
print(" M", mp.id, ": p=")
|
2021-02-09 15:48:41 -05:00
|
|
|
if pp != nil {
|
2022-07-19 14:19:09 -04:00
|
|
|
print(pp.id)
|
|
|
|
|
} else {
|
|
|
|
|
print("nil")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2022-07-19 14:19:09 -04:00
|
|
|
print(" curg=")
|
|
|
|
|
if mp.curg != nil {
|
|
|
|
|
print(mp.curg.goid)
|
|
|
|
|
} else {
|
|
|
|
|
print("nil")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2022-07-19 14:19:09 -04:00
|
|
|
print(" mallocing=", mp.mallocing, " throwing=", mp.throwing, " preemptoff=", mp.preemptoff, " locks=", mp.locks, " dying=", mp.dying, " spinning=", mp.spinning, " blocked=", mp.blocked, " lockedg=")
|
|
|
|
|
if lockedg := mp.lockedg.ptr(); lockedg != nil {
|
|
|
|
|
print(lockedg.goid)
|
|
|
|
|
} else {
|
|
|
|
|
print("nil")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2022-07-19 14:19:09 -04:00
|
|
|
print("\n")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2020-12-23 15:05:37 -05:00
|
|
|
forEachG(func(gp *g) {
|
2022-07-19 14:19:09 -04:00
|
|
|
print(" G", gp.goid, ": status=", readgstatus(gp), "(", gp.waitreason.String(), ") m=")
|
|
|
|
|
if gp.m != nil {
|
|
|
|
|
print(gp.m.id)
|
|
|
|
|
} else {
|
|
|
|
|
print("nil")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2022-07-19 14:19:09 -04:00
|
|
|
print(" lockedm=")
|
|
|
|
|
if lockedm := gp.lockedm.ptr(); lockedm != nil {
|
|
|
|
|
print(lockedm.id)
|
|
|
|
|
} else {
|
|
|
|
|
print("nil")
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2022-07-19 14:19:09 -04:00
|
|
|
print("\n")
|
2020-12-23 15:05:37 -05:00
|
|
|
})
|
2015-10-18 17:04:05 -07:00
|
|
|
unlock(&sched.lock)
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-11 11:28:24 -04:00
|
|
|
// schedEnableUser enables or disables the scheduling of user
|
|
|
|
|
// goroutines.
|
|
|
|
|
//
|
|
|
|
|
// This does not stop already running user goroutines, so the caller
|
|
|
|
|
// should first stop the world when disabling user goroutines.
|
|
|
|
|
func schedEnableUser(enable bool) {
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
if sched.disable.user == !enable {
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
sched.disable.user = !enable
|
|
|
|
|
if enable {
|
|
|
|
|
n := sched.disable.n
|
|
|
|
|
sched.disable.n = 0
|
|
|
|
|
globrunqputbatch(&sched.disable.runnable, n)
|
|
|
|
|
unlock(&sched.lock)
|
2022-07-20 18:01:31 -04:00
|
|
|
for ; n != 0 && sched.npidle.Load() != 0; n-- {
|
2018-09-11 11:28:24 -04:00
|
|
|
startm(nil, false)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-22 11:46:44 +01:00
|
|
|
// schedEnabled reports whether gp should be scheduled. It returns
|
2018-09-11 11:28:24 -04:00
|
|
|
// false is scheduling of gp is disabled.
|
2020-08-21 11:51:25 -04:00
|
|
|
//
|
|
|
|
|
// sched.lock must be held.
|
2018-09-11 11:28:24 -04:00
|
|
|
func schedEnabled(gp *g) bool {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
2018-09-11 11:28:24 -04:00
|
|
|
if sched.disable.user {
|
|
|
|
|
return isSystemGoroutine(gp, true)
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Put mp on midle list.
|
2020-08-21 11:51:25 -04:00
|
|
|
// sched.lock must be held.
|
2015-10-18 17:04:05 -07:00
|
|
|
// May run during STW, so write barriers are not allowed.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2015-10-18 17:04:05 -07:00
|
|
|
func mput(mp *m) {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
mp.schedlink = sched.midle
|
|
|
|
|
sched.midle.set(mp)
|
|
|
|
|
sched.nmidle++
|
|
|
|
|
checkdead()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to get an m from midle list.
|
2020-08-21 11:51:25 -04:00
|
|
|
// sched.lock must be held.
|
2015-10-18 17:04:05 -07:00
|
|
|
// May run during STW, so write barriers are not allowed.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2015-10-18 17:04:05 -07:00
|
|
|
func mget() *m {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
mp := sched.midle.ptr()
|
|
|
|
|
if mp != nil {
|
|
|
|
|
sched.midle = mp.schedlink
|
|
|
|
|
sched.nmidle--
|
|
|
|
|
}
|
|
|
|
|
return mp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Put gp on the global runnable queue.
|
2020-08-21 11:51:25 -04:00
|
|
|
// sched.lock must be held.
|
2015-10-18 17:04:05 -07:00
|
|
|
// May run during STW, so write barriers are not allowed.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2015-10-18 17:04:05 -07:00
|
|
|
func globrunqput(gp *g) {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
2018-08-09 23:47:37 -04:00
|
|
|
sched.runq.pushBack(gp)
|
2015-10-18 17:04:05 -07:00
|
|
|
sched.runqsize++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Put gp at the head of the global runnable queue.
|
2020-08-21 11:51:25 -04:00
|
|
|
// sched.lock must be held.
|
2015-10-18 17:04:05 -07:00
|
|
|
// May run during STW, so write barriers are not allowed.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2015-10-18 17:04:05 -07:00
|
|
|
func globrunqputhead(gp *g) {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
2018-08-09 23:47:37 -04:00
|
|
|
sched.runq.push(gp)
|
2015-10-18 17:04:05 -07:00
|
|
|
sched.runqsize++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Put a batch of runnable goroutines on the global runnable queue.
|
2018-08-09 23:47:37 -04:00
|
|
|
// This clears *batch.
|
2020-08-21 11:51:25 -04:00
|
|
|
// sched.lock must be held.
|
2021-04-23 21:25:06 +08:00
|
|
|
// May run during STW, so write barriers are not allowed.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2021-04-23 21:25:06 +08:00
|
|
|
//go:nowritebarrierrec
|
2018-08-09 23:47:37 -04:00
|
|
|
func globrunqputbatch(batch *gQueue, n int32) {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
2018-08-09 23:47:37 -04:00
|
|
|
sched.runq.pushBackAll(*batch)
|
2015-10-18 17:04:05 -07:00
|
|
|
sched.runqsize += n
|
2018-08-09 23:47:37 -04:00
|
|
|
*batch = gQueue{}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try get a batch of G's from the global runnable queue.
|
2020-08-21 11:51:25 -04:00
|
|
|
// sched.lock must be held.
|
2021-02-09 15:48:41 -05:00
|
|
|
func globrunqget(pp *p, max int32) *g {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
if sched.runqsize == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n := sched.runqsize/gomaxprocs + 1
|
|
|
|
|
if n > sched.runqsize {
|
|
|
|
|
n = sched.runqsize
|
|
|
|
|
}
|
|
|
|
|
if max > 0 && n > max {
|
|
|
|
|
n = max
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if n > int32(len(pp.runq))/2 {
|
|
|
|
|
n = int32(len(pp.runq)) / 2
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sched.runqsize -= n
|
|
|
|
|
|
2018-08-09 23:47:37 -04:00
|
|
|
gp := sched.runq.pop()
|
2015-10-18 17:04:05 -07:00
|
|
|
n--
|
|
|
|
|
for ; n > 0; n-- {
|
2018-08-09 23:47:37 -04:00
|
|
|
gp1 := sched.runq.pop()
|
2021-02-09 15:48:41 -05:00
|
|
|
runqput(pp, gp1, false)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
return gp
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 18:12:35 -04:00
|
|
|
// pMask is an atomic bitstring with one bit per P.
|
|
|
|
|
type pMask []uint32
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
|
2020-10-05 18:12:35 -04:00
|
|
|
// read returns true if P id's bit is set.
|
|
|
|
|
func (p pMask) read(id uint32) bool {
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
word := id / 32
|
|
|
|
|
mask := uint32(1) << (id % 32)
|
|
|
|
|
return (atomic.Load(&p[word]) & mask) != 0
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 18:12:35 -04:00
|
|
|
// set sets P id's bit.
|
|
|
|
|
func (p pMask) set(id int32) {
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
word := id / 32
|
|
|
|
|
mask := uint32(1) << (id % 32)
|
|
|
|
|
atomic.Or(&p[word], mask)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 18:12:35 -04:00
|
|
|
// clear clears P id's bit.
|
|
|
|
|
func (p pMask) clear(id int32) {
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
word := id / 32
|
|
|
|
|
mask := uint32(1) << (id % 32)
|
|
|
|
|
atomic.And(&p[word], ^mask)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-05 18:12:35 -04:00
|
|
|
// updateTimerPMask clears pp's timer mask if it has no timers on its heap.
|
|
|
|
|
//
|
|
|
|
|
// Ideally, the timer mask would be kept immediately consistent on any timer
|
|
|
|
|
// operations. Unfortunately, updating a shared global data structure in the
|
|
|
|
|
// timer hot path adds too much overhead in applications frequently switching
|
|
|
|
|
// between no timers and some timers.
|
|
|
|
|
//
|
|
|
|
|
// As a compromise, the timer mask is updated only on pidleget / pidleput. A
|
|
|
|
|
// running P (returned by pidleget) may add a timer at any time, so its mask
|
|
|
|
|
// must be set. An idle P (passed to pidleput) cannot add new timers while
|
|
|
|
|
// idle, so if it has no timers at that time, its mask may be cleared.
|
|
|
|
|
//
|
|
|
|
|
// Thus, we get the following effects on timer-stealing in findrunnable:
|
|
|
|
|
//
|
2022-02-03 14:12:08 -05:00
|
|
|
// - Idle Ps with no timers when they go idle are never checked in findrunnable
|
|
|
|
|
// (for work- or timer-stealing; this is the ideal case).
|
|
|
|
|
// - Running Ps must always be checked.
|
|
|
|
|
// - Idle Ps whose timers are stolen must continue to be checked until they run
|
|
|
|
|
// again, even after timer expiration.
|
2020-10-05 18:12:35 -04:00
|
|
|
//
|
|
|
|
|
// When the P starts running again, the mask should be set, as a timer may be
|
|
|
|
|
// added at any time.
|
|
|
|
|
//
|
|
|
|
|
// TODO(prattmic): Additional targeted updates may improve the above cases.
|
|
|
|
|
// e.g., updating the mask when stealing a timer.
|
|
|
|
|
func updateTimerPMask(pp *p) {
|
2022-08-26 09:54:32 +08:00
|
|
|
if pp.numTimers.Load() > 0 {
|
2020-10-05 18:12:35 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Looks like there are no timers, however another P may transiently
|
|
|
|
|
// decrement numTimers when handling a timerModified timer in
|
|
|
|
|
// checkTimers. We must take timersLock to serialize with these changes.
|
|
|
|
|
lock(&pp.timersLock)
|
2022-08-26 09:54:32 +08:00
|
|
|
if pp.numTimers.Load() == 0 {
|
2020-10-05 18:12:35 -04:00
|
|
|
timerpMask.clear(pp.id)
|
|
|
|
|
}
|
|
|
|
|
unlock(&pp.timersLock)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-02 21:26:49 +00:00
|
|
|
// pidleput puts p on the _Pidle list. now must be a relatively recent call
|
|
|
|
|
// to nanotime or zero. Returns now or the current time if now was zero.
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
//
|
|
|
|
|
// This releases ownership of p. Once sched.lock is released it is no longer
|
|
|
|
|
// safe to use p.
|
|
|
|
|
//
|
2020-08-21 11:51:25 -04:00
|
|
|
// sched.lock must be held.
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
// May run during STW, so write barriers are not allowed.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2021-02-09 15:48:41 -05:00
|
|
|
func pidleput(pp *p, now int64) int64 {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
2021-02-09 15:48:41 -05:00
|
|
|
if !runqempty(pp) {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("pidleput: P has non-empty run queue")
|
|
|
|
|
}
|
2022-06-02 21:26:49 +00:00
|
|
|
if now == 0 {
|
|
|
|
|
now = nanotime()
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
updateTimerPMask(pp) // clear if there are no timers.
|
|
|
|
|
idlepMask.set(pp.id)
|
|
|
|
|
pp.link = sched.pidle
|
|
|
|
|
sched.pidle.set(pp)
|
2022-07-20 18:01:31 -04:00
|
|
|
sched.npidle.Add(1)
|
2021-02-09 15:48:41 -05:00
|
|
|
if !pp.limiterEvent.start(limiterEventIdle, now) {
|
2022-06-02 21:26:49 +00:00
|
|
|
throw("must be able to track idle limiter event")
|
|
|
|
|
}
|
|
|
|
|
return now
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
// pidleget tries to get a p from the _Pidle list, acquiring ownership.
|
|
|
|
|
//
|
2020-08-21 11:51:25 -04:00
|
|
|
// sched.lock must be held.
|
runtime: don't attempt to steal from idle Ps
Work stealing is a scalability bottleneck in the scheduler. Since each P
has a work queue, work stealing must look at every P to determine if
there is any work. The number of Ps scales linearly with GOMAXPROCS
(i.e., the number of Ps _is_ GOMAXPROCS), thus this work scales linearly
with GOMAXPROCS.
Work stealing is a later attempt by a P to find work before it goes
idle. Since the P has no work of its own, extra costs here tend not to
directly affect application-level benchmarks. Where they show up is
extra CPU usage by the process as a whole. These costs get particularly
expensive for applications that transition between blocked and running
frequently.
Long term, we need a more scalable approach in general, but for now we
can make a simple observation: idle Ps ([1]) cannot possibly have
anything in their runq, so we need not bother checking at all.
We track idle Ps via a new global bitmap, updated in pidleput/pidleget.
This is already a slow path (requires sched.lock), so we don't expect
high contention there.
Using a single bitmap avoids the need to touch every P to read p.status.
Currently, the bitmap approach is not significantly better than reading
p.status. However, in a future CL I'd like to apply a similiar
optimization to timers. Once done, findrunnable would not touch most Ps
at all (in mostly idle programs), which will avoid memory latency to
pull those Ps into cache.
When reading this bitmap, we are racing with Ps going in and out of
idle, so there are a few cases to consider:
1. _Prunning -> _Pidle: Running P goes idle after we check the bitmap.
In this case, we will try to steal (and find nothing) so there is no
harm.
2. _Pidle -> _Prunning while spinning: A P that starts running may queue
new work that we miss. This is OK: (a) that P cannot go back to sleep
without completing its work, and (b) more fundamentally, we will recheck
after we drop our P.
3. _Pidle -> _Prunning after spinning: After spinning, we really can
miss work from a newly woken P. (a) above still applies here as well,
but this is also the same delicate dance case described in findrunnable:
if nothing is spinning anymore, the other P will unpark a thread to run
the work it submits.
Benchmark results from WakeupParallel/syscall/pair/race/1ms (see
golang.org/cl/228577):
name old msec new msec delta
Perf-task-clock-8 250 ± 1% 247 ± 4% ~ (p=0.690 n=5+5)
Perf-task-clock-16 258 ± 2% 259 ± 2% ~ (p=0.841 n=5+5)
Perf-task-clock-32 284 ± 2% 270 ± 4% -4.94% (p=0.032 n=5+5)
Perf-task-clock-64 326 ± 3% 303 ± 2% -6.92% (p=0.008 n=5+5)
Perf-task-clock-128 407 ± 2% 363 ± 5% -10.69% (p=0.008 n=5+5)
Perf-task-clock-256 561 ± 1% 481 ± 1% -14.20% (p=0.016 n=4+5)
Perf-task-clock-512 840 ± 5% 683 ± 2% -18.70% (p=0.008 n=5+5)
Perf-task-clock-1024 1.38k ±14% 1.07k ± 2% -21.85% (p=0.008 n=5+5)
[1] "Idle Ps" here refers to _Pidle Ps in the sched.pidle list. In other
contexts, Ps may temporarily transition through _Pidle (e.g., in
handoffp); those Ps may have work.
Updates #28808
Updates #18237
Change-Id: Ieeb958bd72e7d8fb375b0b1f414e8d7378b14e29
Reviewed-on: https://go-review.googlesource.com/c/go/+/259578
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Trust: Michael Pratt <mpratt@google.com>
2020-10-01 15:21:37 -04:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
// May run during STW, so write barriers are not allowed.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2016-10-10 17:14:14 -04:00
|
|
|
//go:nowritebarrierrec
|
2022-06-02 21:26:49 +00:00
|
|
|
func pidleget(now int64) (*p, int64) {
|
2020-08-21 11:51:25 -04:00
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
2021-02-09 15:48:41 -05:00
|
|
|
pp := sched.pidle.ptr()
|
|
|
|
|
if pp != nil {
|
2020-10-05 18:12:35 -04:00
|
|
|
// Timer may get added at any time now.
|
2022-06-02 21:26:49 +00:00
|
|
|
if now == 0 {
|
|
|
|
|
now = nanotime()
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
timerpMask.set(pp.id)
|
|
|
|
|
idlepMask.clear(pp.id)
|
|
|
|
|
sched.pidle = pp.link
|
2022-07-20 18:01:31 -04:00
|
|
|
sched.npidle.Add(-1)
|
2021-02-09 15:48:41 -05:00
|
|
|
pp.limiterEvent.stop(limiterEventIdle, now)
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
return pp, now
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
2022-03-01 15:06:37 -05:00
|
|
|
// pidlegetSpinning tries to get a p from the _Pidle list, acquiring ownership.
|
|
|
|
|
// This is called by spinning Ms (or callers than need a spinning M) that have
|
|
|
|
|
// found work. If no P is available, this must synchronized with non-spinning
|
|
|
|
|
// Ms that may be preparing to drop their P without discovering this work.
|
|
|
|
|
//
|
|
|
|
|
// sched.lock must be held.
|
|
|
|
|
//
|
|
|
|
|
// May run during STW, so write barriers are not allowed.
|
|
|
|
|
//
|
|
|
|
|
//go:nowritebarrierrec
|
|
|
|
|
func pidlegetSpinning(now int64) (*p, int64) {
|
|
|
|
|
assertLockHeld(&sched.lock)
|
|
|
|
|
|
|
|
|
|
pp, now := pidleget(now)
|
|
|
|
|
if pp == nil {
|
|
|
|
|
// See "Delicate dance" comment in findrunnable. We found work
|
|
|
|
|
// that we cannot take, we must synchronize with non-spinning
|
|
|
|
|
// Ms that may be preparing to drop their P.
|
|
|
|
|
sched.needspinning.Store(1)
|
|
|
|
|
return nil, now
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pp, now
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-09 15:48:41 -05:00
|
|
|
// runqempty reports whether pp has no Gs on its local run queue.
|
2016-03-18 16:34:11 +01:00
|
|
|
// It never returns true spuriously.
|
2021-02-09 15:48:41 -05:00
|
|
|
func runqempty(pp *p) bool {
|
|
|
|
|
// Defend against a race where 1) pp has G1 in runqnext but runqhead == runqtail,
|
|
|
|
|
// 2) runqput on pp kicks G1 to the runq, 3) runqget on pp empties runqnext.
|
2016-03-18 16:34:11 +01:00
|
|
|
// Simply observing that runqhead == runqtail and then observing that runqnext == nil
|
|
|
|
|
// does not mean the queue is empty.
|
|
|
|
|
for {
|
2021-02-09 15:48:41 -05:00
|
|
|
head := atomic.Load(&pp.runqhead)
|
|
|
|
|
tail := atomic.Load(&pp.runqtail)
|
|
|
|
|
runnext := atomic.Loaduintptr((*uintptr)(unsafe.Pointer(&pp.runnext)))
|
|
|
|
|
if tail == atomic.Load(&pp.runqtail) {
|
2016-03-18 16:34:11 +01:00
|
|
|
return head == tail && runnext == 0
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// To shake out latent assumptions about scheduling order,
|
|
|
|
|
// we introduce some randomness into scheduling decisions
|
|
|
|
|
// when running with the race detector.
|
|
|
|
|
// The need for this was made obvious by changing the
|
|
|
|
|
// (deterministic) scheduling order in Go 1.5 and breaking
|
|
|
|
|
// many poorly-written tests.
|
|
|
|
|
// With the randomness here, as long as the tests pass
|
|
|
|
|
// consistently with -race, they shouldn't have latent scheduling
|
|
|
|
|
// assumptions.
|
|
|
|
|
const randomizeScheduler = raceenabled
|
|
|
|
|
|
|
|
|
|
// runqput tries to put g on the local runnable queue.
|
2018-06-07 10:08:48 +00:00
|
|
|
// If next is false, runqput adds g to the tail of the runnable queue.
|
2021-02-09 15:48:41 -05:00
|
|
|
// If next is true, runqput puts g in the pp.runnext slot.
|
2015-10-18 17:04:05 -07:00
|
|
|
// If the run queue is full, runnext puts g on the global queue.
|
|
|
|
|
// Executed only by the owner P.
|
2021-02-09 15:48:41 -05:00
|
|
|
func runqput(pp *p, gp *g, next bool) {
|
2021-09-30 22:46:09 +08:00
|
|
|
if randomizeScheduler && next && fastrandn(2) == 0 {
|
2015-10-18 17:04:05 -07:00
|
|
|
next = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if next {
|
|
|
|
|
retryNext:
|
2021-02-09 15:48:41 -05:00
|
|
|
oldnext := pp.runnext
|
|
|
|
|
if !pp.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) {
|
2015-10-18 17:04:05 -07:00
|
|
|
goto retryNext
|
|
|
|
|
}
|
|
|
|
|
if oldnext == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Kick the old runnext out to the regular run queue.
|
|
|
|
|
gp = oldnext.ptr()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retry:
|
2021-02-09 15:48:41 -05:00
|
|
|
h := atomic.LoadAcq(&pp.runqhead) // load-acquire, synchronize with consumers
|
|
|
|
|
t := pp.runqtail
|
|
|
|
|
if t-h < uint32(len(pp.runq)) {
|
|
|
|
|
pp.runq[t%uint32(len(pp.runq))].set(gp)
|
|
|
|
|
atomic.StoreRel(&pp.runqtail, t+1) // store-release, makes the item available for consumption
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if runqputslow(pp, gp, h, t) {
|
2015-10-18 17:04:05 -07:00
|
|
|
return
|
|
|
|
|
}
|
2016-02-24 11:55:20 +01:00
|
|
|
// the queue is not full, now the put above must succeed
|
2015-10-18 17:04:05 -07:00
|
|
|
goto retry
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Put g and a batch of work from local runnable queue on global queue.
|
|
|
|
|
// Executed only by the owner P.
|
2021-02-09 15:48:41 -05:00
|
|
|
func runqputslow(pp *p, gp *g, h, t uint32) bool {
|
|
|
|
|
var batch [len(pp.runq)/2 + 1]*g
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// First, grab a batch from local queue.
|
|
|
|
|
n := t - h
|
|
|
|
|
n = n / 2
|
2021-02-09 15:48:41 -05:00
|
|
|
if n != uint32(len(pp.runq)/2) {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("runqputslow: queue is not full")
|
|
|
|
|
}
|
|
|
|
|
for i := uint32(0); i < n; i++ {
|
2021-02-09 15:48:41 -05:00
|
|
|
batch[i] = pp.runq[(h+i)%uint32(len(pp.runq))].ptr()
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if !atomic.CasRel(&pp.runqhead, h, h+n) { // cas-release, commits consume
|
2015-10-18 17:04:05 -07:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
batch[n] = gp
|
|
|
|
|
|
|
|
|
|
if randomizeScheduler {
|
|
|
|
|
for i := uint32(1); i <= n; i++ {
|
2017-02-13 12:46:17 -08:00
|
|
|
j := fastrandn(i + 1)
|
2015-10-18 17:04:05 -07:00
|
|
|
batch[i], batch[j] = batch[j], batch[i]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Link the goroutines.
|
|
|
|
|
for i := uint32(0); i < n; i++ {
|
|
|
|
|
batch[i].schedlink.set(batch[i+1])
|
|
|
|
|
}
|
2018-08-09 23:47:37 -04:00
|
|
|
var q gQueue
|
|
|
|
|
q.head.set(batch[0])
|
|
|
|
|
q.tail.set(batch[n])
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
// Now put the batch on global queue.
|
|
|
|
|
lock(&sched.lock)
|
2018-08-09 23:47:37 -04:00
|
|
|
globrunqputbatch(&q, int32(n+1))
|
2015-10-18 17:04:05 -07:00
|
|
|
unlock(&sched.lock)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-23 20:38:20 -08:00
|
|
|
// runqputbatch tries to put all the G's on q on the local runnable queue.
|
|
|
|
|
// If the queue is full, they are put on the global queue; in that case
|
|
|
|
|
// this will temporarily acquire the scheduler lock.
|
|
|
|
|
// Executed only by the owner P.
|
|
|
|
|
func runqputbatch(pp *p, q *gQueue, qsize int) {
|
|
|
|
|
h := atomic.LoadAcq(&pp.runqhead)
|
|
|
|
|
t := pp.runqtail
|
|
|
|
|
n := uint32(0)
|
|
|
|
|
for !q.empty() && t-h < uint32(len(pp.runq)) {
|
|
|
|
|
gp := q.pop()
|
|
|
|
|
pp.runq[t%uint32(len(pp.runq))].set(gp)
|
|
|
|
|
t++
|
|
|
|
|
n++
|
|
|
|
|
}
|
|
|
|
|
qsize -= int(n)
|
|
|
|
|
|
|
|
|
|
if randomizeScheduler {
|
|
|
|
|
off := func(o uint32) uint32 {
|
|
|
|
|
return (pp.runqtail + o) % uint32(len(pp.runq))
|
|
|
|
|
}
|
|
|
|
|
for i := uint32(1); i < n; i++ {
|
|
|
|
|
j := fastrandn(i + 1)
|
|
|
|
|
pp.runq[off(i)], pp.runq[off(j)] = pp.runq[off(j)], pp.runq[off(i)]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
atomic.StoreRel(&pp.runqtail, t)
|
|
|
|
|
if !q.empty() {
|
2020-07-28 20:02:57 +00:00
|
|
|
lock(&sched.lock)
|
2020-01-23 20:38:20 -08:00
|
|
|
globrunqputbatch(q, int32(qsize))
|
2020-07-28 20:02:57 +00:00
|
|
|
unlock(&sched.lock)
|
2020-01-23 20:38:20 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
// Get g from local runnable queue.
|
|
|
|
|
// If inheritTime is true, gp should inherit the remaining time in the
|
|
|
|
|
// current time slice. Otherwise, it should start a new time slice.
|
|
|
|
|
// Executed only by the owner P.
|
2021-02-09 15:48:41 -05:00
|
|
|
func runqget(pp *p) (gp *g, inheritTime bool) {
|
2015-10-18 17:04:05 -07:00
|
|
|
// If there's a runnext, it's the next G to run.
|
2021-02-09 15:48:41 -05:00
|
|
|
next := pp.runnext
|
2021-05-06 09:04:03 +08:00
|
|
|
// If the runnext is non-0 and the CAS fails, it could only have been stolen by another P,
|
|
|
|
|
// because other Ps can race to set runnext to 0, but only the current P can set it to non-0.
|
2022-08-13 03:56:02 +08:00
|
|
|
// Hence, there's no need to retry this CAS if it fails.
|
2021-02-09 15:48:41 -05:00
|
|
|
if next != 0 && pp.runnext.cas(next, 0) {
|
2021-05-06 09:04:03 +08:00
|
|
|
return next.ptr(), true
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for {
|
2021-02-09 15:48:41 -05:00
|
|
|
h := atomic.LoadAcq(&pp.runqhead) // load-acquire, synchronize with other consumers
|
|
|
|
|
t := pp.runqtail
|
2015-10-18 17:04:05 -07:00
|
|
|
if t == h {
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
gp := pp.runq[h%uint32(len(pp.runq))].ptr()
|
|
|
|
|
if atomic.CasRel(&pp.runqhead, h, h+1) { // cas-release, commits consume
|
2015-10-18 17:04:05 -07:00
|
|
|
return gp, false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-09 15:48:41 -05:00
|
|
|
// runqdrain drains the local runnable queue of pp and returns all goroutines in it.
|
2021-04-23 21:25:06 +08:00
|
|
|
// Executed only by the owner P.
|
2021-02-09 15:48:41 -05:00
|
|
|
func runqdrain(pp *p) (drainQ gQueue, n uint32) {
|
|
|
|
|
oldNext := pp.runnext
|
|
|
|
|
if oldNext != 0 && pp.runnext.cas(oldNext, 0) {
|
2021-04-23 21:25:06 +08:00
|
|
|
drainQ.pushBack(oldNext.ptr())
|
|
|
|
|
n++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retry:
|
2021-02-09 15:48:41 -05:00
|
|
|
h := atomic.LoadAcq(&pp.runqhead) // load-acquire, synchronize with other consumers
|
|
|
|
|
t := pp.runqtail
|
2021-04-23 21:25:06 +08:00
|
|
|
qn := t - h
|
|
|
|
|
if qn == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if qn > uint32(len(pp.runq)) { // read inconsistent h and t
|
2021-04-23 21:25:06 +08:00
|
|
|
goto retry
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-09 15:48:41 -05:00
|
|
|
if !atomic.CasRel(&pp.runqhead, h, h+qn) { // cas-release, commits consume
|
2021-04-23 21:25:06 +08:00
|
|
|
goto retry
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We've inverted the order in which it gets G's from the local P's runnable queue
|
|
|
|
|
// and then advances the head pointer because we don't want to mess up the statuses of G's
|
|
|
|
|
// while runqdrain() and runqsteal() are running in parallel.
|
|
|
|
|
// Thus we should advance the head pointer before draining the local P into a gQueue,
|
|
|
|
|
// so that we can update any gp.schedlink only after we take the full ownership of G,
|
|
|
|
|
// meanwhile, other P's can't access to all G's in local P's runnable queue and steal them.
|
|
|
|
|
// See https://groups.google.com/g/golang-dev/c/0pTKxEKhHSc/m/6Q85QjdVBQAJ for more details.
|
|
|
|
|
for i := uint32(0); i < qn; i++ {
|
2021-02-09 15:48:41 -05:00
|
|
|
gp := pp.runq[(h+i)%uint32(len(pp.runq))].ptr()
|
2021-04-23 21:25:06 +08:00
|
|
|
drainQ.pushBack(gp)
|
|
|
|
|
n++
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-09 15:48:41 -05:00
|
|
|
// Grabs a batch of goroutines from pp's runnable queue into batch.
|
2015-10-18 17:04:05 -07:00
|
|
|
// Batch is a ring buffer starting at batchHead.
|
|
|
|
|
// Returns number of grabbed goroutines.
|
|
|
|
|
// Can be executed by any P.
|
2021-02-09 15:48:41 -05:00
|
|
|
func runqgrab(pp *p, batch *[256]guintptr, batchHead uint32, stealRunNextG bool) uint32 {
|
2015-10-18 17:04:05 -07:00
|
|
|
for {
|
2021-02-09 15:48:41 -05:00
|
|
|
h := atomic.LoadAcq(&pp.runqhead) // load-acquire, synchronize with other consumers
|
|
|
|
|
t := atomic.LoadAcq(&pp.runqtail) // load-acquire, synchronize with the producer
|
2015-10-18 17:04:05 -07:00
|
|
|
n := t - h
|
|
|
|
|
n = n - n/2
|
|
|
|
|
if n == 0 {
|
|
|
|
|
if stealRunNextG {
|
2021-02-09 15:48:41 -05:00
|
|
|
// Try to steal from pp.runnext.
|
|
|
|
|
if next := pp.runnext; next != 0 {
|
|
|
|
|
if pp.status == _Prunning {
|
|
|
|
|
// Sleep to ensure that pp isn't about to run the g
|
2017-11-15 12:47:22 -08:00
|
|
|
// we are about to steal.
|
|
|
|
|
// The important use case here is when the g running
|
2021-02-09 15:48:41 -05:00
|
|
|
// on pp ready()s another g and then almost
|
2017-11-15 12:47:22 -08:00
|
|
|
// immediately blocks. Instead of stealing runnext
|
2021-02-09 15:48:41 -05:00
|
|
|
// in this window, back off to give pp a chance to
|
2017-11-15 12:47:22 -08:00
|
|
|
// schedule runnext. This will avoid thrashing gs
|
|
|
|
|
// between different Ps.
|
|
|
|
|
// A sync chan send/recv takes ~50ns as of time of
|
|
|
|
|
// writing, so 3us gives ~50x overshoot.
|
2022-05-18 23:47:03 +00:00
|
|
|
if GOOS != "windows" && GOOS != "openbsd" && GOOS != "netbsd" {
|
2017-11-15 12:47:22 -08:00
|
|
|
usleep(3)
|
|
|
|
|
} else {
|
2022-04-21 22:18:31 +00:00
|
|
|
// On some platforms system timer granularity is
|
2017-11-15 12:47:22 -08:00
|
|
|
// 1-15ms, which is way too much for this
|
|
|
|
|
// optimization. So just yield.
|
|
|
|
|
osyield()
|
|
|
|
|
}
|
2016-03-18 11:00:03 +01:00
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if !pp.runnext.cas(next, 0) {
|
2015-10-18 17:04:05 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2015-11-02 16:59:39 -05:00
|
|
|
batch[batchHead%uint32(len(batch))] = next
|
2015-10-18 17:04:05 -07:00
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if n > uint32(len(pp.runq)/2) { // read inconsistent h and t
|
2015-10-18 17:04:05 -07:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
for i := uint32(0); i < n; i++ {
|
2021-02-09 15:48:41 -05:00
|
|
|
g := pp.runq[(h+i)%uint32(len(pp.runq))]
|
2015-10-18 17:04:05 -07:00
|
|
|
batch[(batchHead+i)%uint32(len(batch))] = g
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
if atomic.CasRel(&pp.runqhead, h, h+n) { // cas-release, commits consume
|
2015-10-18 17:04:05 -07:00
|
|
|
return n
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Steal half of elements from local runnable queue of p2
|
|
|
|
|
// and put onto local runnable queue of p.
|
|
|
|
|
// Returns one of the stolen elements (or nil if failed).
|
2021-02-09 15:48:41 -05:00
|
|
|
func runqsteal(pp, p2 *p, stealRunNextG bool) *g {
|
|
|
|
|
t := pp.runqtail
|
|
|
|
|
n := runqgrab(p2, &pp.runq, t, stealRunNextG)
|
2015-10-18 17:04:05 -07:00
|
|
|
if n == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
n--
|
2021-02-09 15:48:41 -05:00
|
|
|
gp := pp.runq[(t+n)%uint32(len(pp.runq))].ptr()
|
2015-10-18 17:04:05 -07:00
|
|
|
if n == 0 {
|
|
|
|
|
return gp
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
h := atomic.LoadAcq(&pp.runqhead) // load-acquire, synchronize with consumers
|
|
|
|
|
if t-h+n >= uint32(len(pp.runq)) {
|
2015-10-18 17:04:05 -07:00
|
|
|
throw("runqsteal: runq overflow")
|
|
|
|
|
}
|
2021-02-09 15:48:41 -05:00
|
|
|
atomic.StoreRel(&pp.runqtail, t+n) // store-release, makes the item available for consumption
|
2015-10-18 17:04:05 -07:00
|
|
|
return gp
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-09 23:47:37 -04:00
|
|
|
// A gQueue is a dequeue of Gs linked through g.schedlink. A G can only
|
|
|
|
|
// be on one gQueue or gList at a time.
|
|
|
|
|
type gQueue struct {
|
|
|
|
|
head guintptr
|
|
|
|
|
tail guintptr
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-02 15:18:43 +00:00
|
|
|
// empty reports whether q is empty.
|
2018-08-09 23:47:37 -04:00
|
|
|
func (q *gQueue) empty() bool {
|
|
|
|
|
return q.head == 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// push adds gp to the head of q.
|
|
|
|
|
func (q *gQueue) push(gp *g) {
|
|
|
|
|
gp.schedlink = q.head
|
|
|
|
|
q.head.set(gp)
|
|
|
|
|
if q.tail == 0 {
|
|
|
|
|
q.tail.set(gp)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pushBack adds gp to the tail of q.
|
|
|
|
|
func (q *gQueue) pushBack(gp *g) {
|
|
|
|
|
gp.schedlink = 0
|
|
|
|
|
if q.tail != 0 {
|
|
|
|
|
q.tail.ptr().schedlink.set(gp)
|
|
|
|
|
} else {
|
|
|
|
|
q.head.set(gp)
|
|
|
|
|
}
|
|
|
|
|
q.tail.set(gp)
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-22 00:34:33 +09:00
|
|
|
// pushBackAll adds all Gs in q2 to the tail of q. After this q2 must
|
2018-08-09 23:47:37 -04:00
|
|
|
// not be used.
|
|
|
|
|
func (q *gQueue) pushBackAll(q2 gQueue) {
|
|
|
|
|
if q2.tail == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
q2.tail.ptr().schedlink = 0
|
|
|
|
|
if q.tail != 0 {
|
|
|
|
|
q.tail.ptr().schedlink = q2.head
|
|
|
|
|
} else {
|
|
|
|
|
q.head = q2.head
|
|
|
|
|
}
|
|
|
|
|
q.tail = q2.tail
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pop removes and returns the head of queue q. It returns nil if
|
|
|
|
|
// q is empty.
|
|
|
|
|
func (q *gQueue) pop() *g {
|
|
|
|
|
gp := q.head.ptr()
|
|
|
|
|
if gp != nil {
|
|
|
|
|
q.head = gp.schedlink
|
|
|
|
|
if q.head == 0 {
|
|
|
|
|
q.tail = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return gp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// popList takes all Gs in q and returns them as a gList.
|
|
|
|
|
func (q *gQueue) popList() gList {
|
|
|
|
|
stack := gList{q.head}
|
|
|
|
|
*q = gQueue{}
|
|
|
|
|
return stack
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A gList is a list of Gs linked through g.schedlink. A G can only be
|
|
|
|
|
// on one gQueue or gList at a time.
|
|
|
|
|
type gList struct {
|
|
|
|
|
head guintptr
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-02 15:18:43 +00:00
|
|
|
// empty reports whether l is empty.
|
2018-08-09 23:47:37 -04:00
|
|
|
func (l *gList) empty() bool {
|
|
|
|
|
return l.head == 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// push adds gp to the head of l.
|
|
|
|
|
func (l *gList) push(gp *g) {
|
|
|
|
|
gp.schedlink = l.head
|
|
|
|
|
l.head.set(gp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pushAll prepends all Gs in q to l.
|
|
|
|
|
func (l *gList) pushAll(q gQueue) {
|
|
|
|
|
if !q.empty() {
|
|
|
|
|
q.tail.ptr().schedlink = l.head
|
|
|
|
|
l.head = q.head
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pop removes and returns the head of l. If l is empty, it returns nil.
|
|
|
|
|
func (l *gList) pop() *g {
|
|
|
|
|
gp := l.head.ptr()
|
|
|
|
|
if gp != nil {
|
|
|
|
|
l.head = gp.schedlink
|
|
|
|
|
}
|
|
|
|
|
return gp
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:linkname setMaxThreads runtime/debug.setMaxThreads
|
|
|
|
|
func setMaxThreads(in int) (out int) {
|
|
|
|
|
lock(&sched.lock)
|
|
|
|
|
out = int(sched.maxmcount)
|
2016-10-20 11:24:51 +02:00
|
|
|
if in > 0x7fffffff { // MaxInt32
|
|
|
|
|
sched.maxmcount = 0x7fffffff
|
|
|
|
|
} else {
|
|
|
|
|
sched.maxmcount = int32(in)
|
|
|
|
|
}
|
2015-10-18 17:04:05 -07:00
|
|
|
checkmcount()
|
|
|
|
|
unlock(&sched.lock)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func procPin() int {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
mp := gp.m
|
2015-10-18 17:04:05 -07:00
|
|
|
|
|
|
|
|
mp.locks++
|
|
|
|
|
return int(mp.p.ptr().id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func procUnpin() {
|
2021-02-11 11:15:53 -05:00
|
|
|
gp := getg()
|
|
|
|
|
gp.m.locks--
|
2015-10-18 17:04:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:linkname sync_runtime_procPin sync.runtime_procPin
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func sync_runtime_procPin() int {
|
|
|
|
|
return procPin()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:linkname sync_runtime_procUnpin sync.runtime_procUnpin
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func sync_runtime_procUnpin() {
|
|
|
|
|
procUnpin()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:linkname sync_atomic_runtime_procPin sync/atomic.runtime_procPin
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func sync_atomic_runtime_procPin() int {
|
|
|
|
|
return procPin()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:linkname sync_atomic_runtime_procUnpin sync/atomic.runtime_procUnpin
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func sync_atomic_runtime_procUnpin() {
|
|
|
|
|
procUnpin()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Active spinning for sync.Mutex.
|
2022-01-30 20:13:43 -05:00
|
|
|
//
|
2015-10-18 17:04:05 -07:00
|
|
|
//go:linkname sync_runtime_canSpin sync.runtime_canSpin
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func sync_runtime_canSpin(i int) bool {
|
|
|
|
|
// sync.Mutex is cooperative, so we are conservative with spinning.
|
|
|
|
|
// Spin only few times and only if running on a multicore machine and
|
|
|
|
|
// GOMAXPROCS>1 and there is at least one other running P and local runq is empty.
|
|
|
|
|
// As opposed to runtime mutex we don't do passive spinning here,
|
2018-05-07 07:34:53 +00:00
|
|
|
// because there can be work on global runq or on other Ps.
|
2022-07-25 15:20:22 -04:00
|
|
|
if i >= active_spin || ncpu <= 1 || gomaxprocs <= sched.npidle.Load()+sched.nmspinning.Load()+1 {
|
2015-10-18 17:04:05 -07:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if p := getg().m.p.ptr(); !runqempty(p) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:linkname sync_runtime_doSpin sync.runtime_doSpin
|
|
|
|
|
//go:nosplit
|
|
|
|
|
func sync_runtime_doSpin() {
|
|
|
|
|
procyield(active_spin_cnt)
|
|
|
|
|
}
|
2016-03-18 12:52:52 +01:00
|
|
|
|
|
|
|
|
var stealOrder randomOrder
|
|
|
|
|
|
|
|
|
|
// randomOrder/randomEnum are helper types for randomized work stealing.
|
|
|
|
|
// They allow to enumerate all Ps in different pseudo-random orders without repetitions.
|
|
|
|
|
// The algorithm is based on the fact that if we have X such that X and GOMAXPROCS
|
|
|
|
|
// are coprime, then a sequences of (i + X) % GOMAXPROCS gives the required enumeration.
|
|
|
|
|
type randomOrder struct {
|
|
|
|
|
count uint32
|
|
|
|
|
coprimes []uint32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type randomEnum struct {
|
|
|
|
|
i uint32
|
|
|
|
|
count uint32
|
|
|
|
|
pos uint32
|
|
|
|
|
inc uint32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ord *randomOrder) reset(count uint32) {
|
|
|
|
|
ord.count = count
|
|
|
|
|
ord.coprimes = ord.coprimes[:0]
|
|
|
|
|
for i := uint32(1); i <= count; i++ {
|
|
|
|
|
if gcd(i, count) == 1 {
|
|
|
|
|
ord.coprimes = append(ord.coprimes, i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ord *randomOrder) start(i uint32) randomEnum {
|
|
|
|
|
return randomEnum{
|
|
|
|
|
count: ord.count,
|
|
|
|
|
pos: i % ord.count,
|
2021-12-07 09:22:33 -08:00
|
|
|
inc: ord.coprimes[i/ord.count%uint32(len(ord.coprimes))],
|
2016-03-18 12:52:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enum *randomEnum) done() bool {
|
|
|
|
|
return enum.i == enum.count
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enum *randomEnum) next() {
|
|
|
|
|
enum.i++
|
|
|
|
|
enum.pos = (enum.pos + enum.inc) % enum.count
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enum *randomEnum) position() uint32 {
|
|
|
|
|
return enum.pos
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func gcd(a, b uint32) uint32 {
|
|
|
|
|
for b != 0 {
|
|
|
|
|
a, b = b, a%b
|
|
|
|
|
}
|
|
|
|
|
return a
|
|
|
|
|
}
|
2019-02-05 16:22:38 -08:00
|
|
|
|
|
|
|
|
// An initTask represents the set of initializations that need to be done for a package.
|
2023-01-12 20:25:39 -08:00
|
|
|
// Keep in sync with ../../test/noinit.go:initTask
|
2019-02-05 16:22:38 -08:00
|
|
|
type initTask struct {
|
2023-01-12 20:25:39 -08:00
|
|
|
state uint32 // 0 = uninitialized, 1 = in progress, 2 = done
|
|
|
|
|
nfns uint32
|
|
|
|
|
// followed by nfns pcs, uintptr sized, one per init function to run
|
2019-02-05 16:22:38 -08:00
|
|
|
}
|
|
|
|
|
|
runtime: implement GODEBUG=inittrace=1 support
Setting inittrace=1 causes the runtime to emit a single line to standard error for
each package with init work, summarizing the execution time and memory allocation.
The emitted debug information for init functions can be used to find bottlenecks
or regressions in Go startup performance.
Packages with no init function work (user defined or compiler generated) are omitted.
Tracing plugin inits is not supported as they can execute concurrently. This would
make the implementation of tracing more complex while adding support for a very rare
use case. Plugin inits can be traced separately by testing a main package importing
the plugins package imports explicitly.
$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...
Inspired by stapelberg@google.com who instrumented doInit
in a prototype to measure init times with GDB.
Fixes #41378
Change-Id: Ic37c6a0cfc95488de9e737f5e346b8dbb39174e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/254659
Trust: Martin Möhrmann <moehrmann@google.com>
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2020-09-14 16:55:34 +02:00
|
|
|
// inittrace stores statistics for init functions which are
|
|
|
|
|
// updated by malloc and newproc when active is true.
|
|
|
|
|
var inittrace tracestat
|
|
|
|
|
|
|
|
|
|
type tracestat struct {
|
|
|
|
|
active bool // init tracing activation status
|
2022-07-19 13:49:33 -04:00
|
|
|
id uint64 // init goroutine id
|
runtime: implement GODEBUG=inittrace=1 support
Setting inittrace=1 causes the runtime to emit a single line to standard error for
each package with init work, summarizing the execution time and memory allocation.
The emitted debug information for init functions can be used to find bottlenecks
or regressions in Go startup performance.
Packages with no init function work (user defined or compiler generated) are omitted.
Tracing plugin inits is not supported as they can execute concurrently. This would
make the implementation of tracing more complex while adding support for a very rare
use case. Plugin inits can be traced separately by testing a main package importing
the plugins package imports explicitly.
$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...
Inspired by stapelberg@google.com who instrumented doInit
in a prototype to measure init times with GDB.
Fixes #41378
Change-Id: Ic37c6a0cfc95488de9e737f5e346b8dbb39174e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/254659
Trust: Martin Möhrmann <moehrmann@google.com>
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2020-09-14 16:55:34 +02:00
|
|
|
allocs uint64 // heap allocations
|
|
|
|
|
bytes uint64 // heap allocated bytes
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 20:25:39 -08:00
|
|
|
func doInit(ts []*initTask) {
|
|
|
|
|
for _, t := range ts {
|
|
|
|
|
doInit1(t)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func doInit1(t *initTask) {
|
2019-02-05 16:22:38 -08:00
|
|
|
switch t.state {
|
|
|
|
|
case 2: // fully initialized
|
|
|
|
|
return
|
|
|
|
|
case 1: // initialization in progress
|
|
|
|
|
throw("recursive call during initialization - linker skew")
|
|
|
|
|
default: // not initialized yet
|
|
|
|
|
t.state = 1 // initialization in progress
|
runtime: implement GODEBUG=inittrace=1 support
Setting inittrace=1 causes the runtime to emit a single line to standard error for
each package with init work, summarizing the execution time and memory allocation.
The emitted debug information for init functions can be used to find bottlenecks
or regressions in Go startup performance.
Packages with no init function work (user defined or compiler generated) are omitted.
Tracing plugin inits is not supported as they can execute concurrently. This would
make the implementation of tracing more complex while adding support for a very rare
use case. Plugin inits can be traced separately by testing a main package importing
the plugins package imports explicitly.
$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...
Inspired by stapelberg@google.com who instrumented doInit
in a prototype to measure init times with GDB.
Fixes #41378
Change-Id: Ic37c6a0cfc95488de9e737f5e346b8dbb39174e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/254659
Trust: Martin Möhrmann <moehrmann@google.com>
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2020-09-14 16:55:34 +02:00
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
start int64
|
|
|
|
|
before tracestat
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if inittrace.active {
|
|
|
|
|
start = nanotime()
|
2021-03-28 12:17:35 +00:00
|
|
|
// Load stats non-atomically since tracinit is updated only by this init goroutine.
|
runtime: implement GODEBUG=inittrace=1 support
Setting inittrace=1 causes the runtime to emit a single line to standard error for
each package with init work, summarizing the execution time and memory allocation.
The emitted debug information for init functions can be used to find bottlenecks
or regressions in Go startup performance.
Packages with no init function work (user defined or compiler generated) are omitted.
Tracing plugin inits is not supported as they can execute concurrently. This would
make the implementation of tracing more complex while adding support for a very rare
use case. Plugin inits can be traced separately by testing a main package importing
the plugins package imports explicitly.
$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...
Inspired by stapelberg@google.com who instrumented doInit
in a prototype to measure init times with GDB.
Fixes #41378
Change-Id: Ic37c6a0cfc95488de9e737f5e346b8dbb39174e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/254659
Trust: Martin Möhrmann <moehrmann@google.com>
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2020-09-14 16:55:34 +02:00
|
|
|
before = inittrace
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-12 20:25:39 -08:00
|
|
|
if t.nfns == 0 {
|
|
|
|
|
// We should have pruned all of these in the linker.
|
|
|
|
|
throw("inittask with no functions")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
firstFunc := add(unsafe.Pointer(t), 8)
|
|
|
|
|
for i := uint32(0); i < t.nfns; i++ {
|
|
|
|
|
p := add(firstFunc, uintptr(i)*goarch.PtrSize)
|
2019-02-05 16:22:38 -08:00
|
|
|
f := *(*func())(unsafe.Pointer(&p))
|
|
|
|
|
f()
|
|
|
|
|
}
|
runtime: implement GODEBUG=inittrace=1 support
Setting inittrace=1 causes the runtime to emit a single line to standard error for
each package with init work, summarizing the execution time and memory allocation.
The emitted debug information for init functions can be used to find bottlenecks
or regressions in Go startup performance.
Packages with no init function work (user defined or compiler generated) are omitted.
Tracing plugin inits is not supported as they can execute concurrently. This would
make the implementation of tracing more complex while adding support for a very rare
use case. Plugin inits can be traced separately by testing a main package importing
the plugins package imports explicitly.
$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...
Inspired by stapelberg@google.com who instrumented doInit
in a prototype to measure init times with GDB.
Fixes #41378
Change-Id: Ic37c6a0cfc95488de9e737f5e346b8dbb39174e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/254659
Trust: Martin Möhrmann <moehrmann@google.com>
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2020-09-14 16:55:34 +02:00
|
|
|
|
|
|
|
|
if inittrace.active {
|
|
|
|
|
end := nanotime()
|
2021-03-29 18:22:15 +00:00
|
|
|
// Load stats non-atomically since tracinit is updated only by this init goroutine.
|
runtime: implement GODEBUG=inittrace=1 support
Setting inittrace=1 causes the runtime to emit a single line to standard error for
each package with init work, summarizing the execution time and memory allocation.
The emitted debug information for init functions can be used to find bottlenecks
or regressions in Go startup performance.
Packages with no init function work (user defined or compiler generated) are omitted.
Tracing plugin inits is not supported as they can execute concurrently. This would
make the implementation of tracing more complex while adding support for a very rare
use case. Plugin inits can be traced separately by testing a main package importing
the plugins package imports explicitly.
$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...
Inspired by stapelberg@google.com who instrumented doInit
in a prototype to measure init times with GDB.
Fixes #41378
Change-Id: Ic37c6a0cfc95488de9e737f5e346b8dbb39174e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/254659
Trust: Martin Möhrmann <moehrmann@google.com>
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2020-09-14 16:55:34 +02:00
|
|
|
after := inittrace
|
|
|
|
|
|
2021-05-20 21:40:32 -04:00
|
|
|
f := *(*func())(unsafe.Pointer(&firstFunc))
|
2021-05-21 13:37:19 -04:00
|
|
|
pkg := funcpkgpath(findfunc(abi.FuncPCABIInternal(f)))
|
runtime: implement GODEBUG=inittrace=1 support
Setting inittrace=1 causes the runtime to emit a single line to standard error for
each package with init work, summarizing the execution time and memory allocation.
The emitted debug information for init functions can be used to find bottlenecks
or regressions in Go startup performance.
Packages with no init function work (user defined or compiler generated) are omitted.
Tracing plugin inits is not supported as they can execute concurrently. This would
make the implementation of tracing more complex while adding support for a very rare
use case. Plugin inits can be traced separately by testing a main package importing
the plugins package imports explicitly.
$ GODEBUG=inittrace=1 go test
init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs
init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs
init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs
init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs
init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs
init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs
init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs
...
Inspired by stapelberg@google.com who instrumented doInit
in a prototype to measure init times with GDB.
Fixes #41378
Change-Id: Ic37c6a0cfc95488de9e737f5e346b8dbb39174e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/254659
Trust: Martin Möhrmann <moehrmann@google.com>
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2020-09-14 16:55:34 +02:00
|
|
|
|
|
|
|
|
var sbuf [24]byte
|
|
|
|
|
print("init ", pkg, " @")
|
|
|
|
|
print(string(fmtNSAsMS(sbuf[:], uint64(start-runtimeInitTime))), " ms, ")
|
|
|
|
|
print(string(fmtNSAsMS(sbuf[:], uint64(end-start))), " ms clock, ")
|
|
|
|
|
print(string(itoa(sbuf[:], after.bytes-before.bytes)), " bytes, ")
|
|
|
|
|
print(string(itoa(sbuf[:], after.allocs-before.allocs)), " allocs")
|
|
|
|
|
print("\n")
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-05 16:22:38 -08:00
|
|
|
t.state = 2 // initialization done
|
|
|
|
|
}
|
|
|
|
|
}
|