diff options
| author | Cherry Mui <cherryyz@google.com> | 2025-11-24 11:03:06 -0500 |
|---|---|---|
| committer | Cherry Mui <cherryyz@google.com> | 2025-11-24 11:03:06 -0500 |
| commit | 220d73cc44a0c580dc8ee10df6395c8e517e118f (patch) | |
| tree | d072aab20229f5489f43a0ce13ab52ff867ba38c /src/runtime | |
| parent | 0c69e7734308f36de7acbeda4cabced8018c04e3 (diff) | |
| parent | 8dd5b13abcb64993959ea02c3f664654af6367a9 (diff) | |
| download | go-220d73cc44a0c580dc8ee10df6395c8e517e118f.tar.xz | |
[dev.simd] all: merge master (8dd5b13) into dev.simd
Merge List:
+ 2025-11-24 8dd5b13abc cmd/compile: relax stmtline_test on amd64
+ 2025-11-23 feae743bdb cmd/compile: use 32x32->64 multiplies on loong64
+ 2025-11-23 e88be8a128 runtime: fix stale comment for mheap/malloc
+ 2025-11-23 a318843a2a cmd/internal/obj/loong64: optimize duplicate optab entries
+ 2025-11-23 a18294bb6a cmd/internal/obj/arm64, image/gif, runtime, sort: use math/bits to calculate log2
+ 2025-11-23 437323ef7b slices: fix incorrect comment in slices.Insert function documentation
+ 2025-11-23 1993dca400 doc/next: pre-announce end of support for macOS 12 in Go 1.27
+ 2025-11-22 337f7b1f5d cmd/go: update default go directive in mod or work init
+ 2025-11-21 3c26aef8fb cmd/internal/obj/riscv: improve large branch/call/jump tests
+ 2025-11-21 31aa9f800b crypto/tls: use inner hello for earlyData when using QUIC and ECH
+ 2025-11-21 d68aec8db1 runtime: replace trace seqlock with write flag
+ 2025-11-21 8d9906cd34 runtime/trace: add Log benchmark
+ 2025-11-21 6aeacdff38 cmd/go: support sha1 repos when git default is sha256
+ 2025-11-21 9570036ca5 crypto/sha3: make the zero value of SHAKE useable
+ 2025-11-21 155efbbeeb crypto/sha3: make the zero value of SHA3 useable
+ 2025-11-21 6f16669e34 database/sql: don't ignore ColumnConverter for unknown input count
+ 2025-11-21 121bc3e464 runtime/pprof: remove hard-coded sleep in CPU profile reader
+ 2025-11-21 b604148c4e runtime: fix double wakeup in CPU profile buffer
+ 2025-11-21 22f24f90b5 cmd/compile: change testing.B.Loop keep alive semantic
+ 2025-11-21 cfb9d2eb73 net: remove unused linknames
+ 2025-11-21 65ef314f89 net/http: remove unused linknames
+ 2025-11-21 0f32fbc631 net/http: populate Response.Request when using NewFileTransport
+ 2025-11-21 3e0a8e7867 net/http: preserve original path encoding in redirects
+ 2025-11-21 831af61120 net/http: use HTTP 307 redirects in ServeMux
+ 2025-11-21 87269224cb net/http: update Response.Request.URL after redirects on GOOS=js
+ 2025-11-21 7aa9ca729f net/http/cookiejar: treat localhost as secure origin
+ 2025-11-21 f870a1d398 net/url: warn that JoinPath arguments should be escaped
+ 2025-11-21 9962d95fed crypto/internal/fips140/mldsa: unroll NTT and inverseNTT
+ 2025-11-21 f821fc46c5 crypto/internal/fisp140test: update acvptool, test data
+ 2025-11-21 b59efc38a0 crypto/internal/fips140/mldsa: new package
+ 2025-11-21 62741480b8 runtime: remove linkname for gopanic
+ 2025-11-21 7db2f0bb9a crypto/internal/hpke: separate KEM and PublicKey/PrivateKey interfaces
+ 2025-11-21 e15800c0ec crypto/internal/hpke: add ML-KEM and hybrid KEMs, and SHAKE KDFs
+ 2025-11-21 7c985a2df4 crypto/internal/hpke: modularize API and support more ciphersuites
+ 2025-11-21 e7d47ac33d cmd/compile: simplify negative on multiplication
+ 2025-11-21 35d2712b32 net/http: fix typo in Transport docs
+ 2025-11-21 90c970cd0f net: remove unnecessary loop variable copies in tests
+ 2025-11-21 9772d3a690 cmd/cgo: strip top-level const qualifier from argument frame struct
+ 2025-11-21 1903782ade errors: add examples for custom Is/As matching
+ 2025-11-21 ec92bc6d63 cmd/compile: rewrite Rsh to RshU if arguments are proved positive
+ 2025-11-21 3820f94c1d cmd/compile: propagate unsigned relations for Rsh if arguments are positive
+ 2025-11-21 d474f1fd21 cmd/compile: make dse track multiple shadowed ranges
+ 2025-11-21 d0d0a72980 cmd/compile/internal/ssa: correct type of ARM64 conditional instructions
+ 2025-11-21 a9704f89ea internal/runtime/gc/scan: add AVX512 impl of filterNil.
+ 2025-11-21 ccd389036a cmd/internal/objabi: remove -V=goexperiment internal special case
+ 2025-11-21 e7787b9eca runtime: go fmt
+ 2025-11-21 17b3b98796 internal/strconv: go fmt
+ 2025-11-21 c851827c68 internal/trace: go fmt
+ 2025-11-21 f87aaec53d cmd/compile: fix integer overflow in prove pass
+ 2025-11-21 dbd2ab9992 cmd/compile/internal: fix typos
+ 2025-11-21 b9d86baae3 cmd/compile/internal/devirtualize: fix typos
+ 2025-11-20 4b0e3cc1d6 cmd/link: support loading R_LARCH_PCREL20_S2 and R_LARCH_CALL36 relocs
+ 2025-11-20 cdba82c7d6 cmd/internal/obj/loong64: add {,X}VSLT.{B/H/W/V}{,U} instructions support
+ 2025-11-20 bd2b117c2c crypto/tls: add QUICErrorEvent
+ 2025-11-20 3ad2e113fc net/http/httputil: wrap ReverseProxy's outbound request body so Close is a noop
+ 2025-11-20 d58b733646 runtime: track goroutine location until actual STW
+ 2025-11-20 1bc54868d4 cmd/vendor: update to x/tools@68724af
+ 2025-11-20 8c3195973b runtime: disable stack allocation tests on sanitizers
+ 2025-11-20 ff654ea100 net/url: permit colons in the host of postgresql:// URLs
+ 2025-11-20 a662badab9 encoding/json: remove linknames
+ 2025-11-20 5afe237d65 mime: add missing path for mime types in godoc
+ 2025-11-20 c1b7112af8 os/signal: make NotifyContext cancel the context with a cause
Change-Id: Ib93ef643be610dfbdd83ff45095a7b1ca2537b8b
Diffstat (limited to 'src/runtime')
| -rw-r--r-- | src/runtime/cgocall.go | 1 | ||||
| -rw-r--r-- | src/runtime/list.go | 8 | ||||
| -rw-r--r-- | src/runtime/list_manual.go | 8 | ||||
| -rw-r--r-- | src/runtime/malloc.go | 2 | ||||
| -rw-r--r-- | src/runtime/mgcmark_nogreenteagc.go | 2 | ||||
| -rw-r--r-- | src/runtime/mheap.go | 2 | ||||
| -rw-r--r-- | src/runtime/panic.go | 5 | ||||
| -rw-r--r-- | src/runtime/pprof/pprof.go | 5 | ||||
| -rw-r--r-- | src/runtime/proc_test.go | 47 | ||||
| -rw-r--r-- | src/runtime/profbuf.go | 30 | ||||
| -rw-r--r-- | src/runtime/profbuf_test.go | 92 | ||||
| -rw-r--r-- | src/runtime/runtime2.go | 14 | ||||
| -rw-r--r-- | src/runtime/slice_test.go | 11 | ||||
| -rw-r--r-- | src/runtime/stack.go | 9 | ||||
| -rw-r--r-- | src/runtime/trace.go | 14 | ||||
| -rw-r--r-- | src/runtime/trace/annotation_test.go | 13 | ||||
| -rw-r--r-- | src/runtime/tracecpu.go | 14 | ||||
| -rw-r--r-- | src/runtime/traceruntime.go | 37 |
18 files changed, 225 insertions, 89 deletions
diff --git a/src/runtime/cgocall.go b/src/runtime/cgocall.go index a53fd6da34..f01353ffa6 100644 --- a/src/runtime/cgocall.go +++ b/src/runtime/cgocall.go @@ -592,6 +592,7 @@ func cgoCheckPointer(ptr any, arg any) { } type cgoErrorMsg int + const ( cgoCheckPointerFail cgoErrorMsg = iota cgoResultFail diff --git a/src/runtime/list.go b/src/runtime/list.go index c900ad7ff3..43b51996e4 100644 --- a/src/runtime/list.go +++ b/src/runtime/list.go @@ -34,11 +34,11 @@ func (head *listHead) init(off uintptr) { // // For example: // -// type foo struct { -// val int +// type foo struct { +// val int // -// node listNode -// } +// node listNode +// } // // var fooHead listHead // fooHead.init(unsafe.Offsetof(foo{}.node)) diff --git a/src/runtime/list_manual.go b/src/runtime/list_manual.go index af0ae6b2d6..f0ce9ad4da 100644 --- a/src/runtime/list_manual.go +++ b/src/runtime/list_manual.go @@ -41,11 +41,11 @@ func (head *listHeadManual) init(off uintptr) { // // For example: // -// type foo struct { -// val int +// type foo struct { +// val int // -// node listNodeManual -// } +// node listNodeManual +// } // // var fooHead listHeadManual // fooHead.init(unsafe.Offsetof(foo{}.node)) diff --git a/src/runtime/malloc.go b/src/runtime/malloc.go index d49dacaf68..4971e16c6a 100644 --- a/src/runtime/malloc.go +++ b/src/runtime/malloc.go @@ -1169,7 +1169,7 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { var x unsafe.Pointer var elemsize uintptr if sizeSpecializedMallocEnabled { - // we know that heapBitsInSpan is true. + // we know that heapBitsInSpan is false. if size <= maxSmallSize-gc.MallocHeaderSize { if typ == nil || !typ.Pointers() { x, elemsize = mallocgcSmallNoscan(size, typ, needzero) diff --git a/src/runtime/mgcmark_nogreenteagc.go b/src/runtime/mgcmark_nogreenteagc.go index a0470c6e32..ce0d8d80f3 100644 --- a/src/runtime/mgcmark_nogreenteagc.go +++ b/src/runtime/mgcmark_nogreenteagc.go @@ -67,7 +67,7 @@ func (q *spanQueue) destroy() { } type spanSPMC struct { - _ sys.NotInHeap + _ sys.NotInHeap allnode listNodeManual } diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go index d2ff063b00..0ccaadc891 100644 --- a/src/runtime/mheap.go +++ b/src/runtime/mheap.go @@ -56,7 +56,7 @@ const ( ) // Main malloc heap. -// The heap itself is the "free" and "scav" treaps, +// The heap use pageAlloc to manage free and scavenged pages, // but all the other global data is here too. // // mheap must not be heap-allocated because it contains mSpanLists, diff --git a/src/runtime/panic.go b/src/runtime/panic.go index ff2dec386f..d467e9305d 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -800,10 +800,7 @@ var panicnil = &godebugInc{name: "panicnil"} // The compiler emits calls to this function. // // gopanic should be an internal detail, -// but widely used packages access it using linkname. -// Notable members of the hall of shame include: -// - go.undefinedlabs.com/scopeagent -// - github.com/goplus/igop +// but historically, widely used packages access it using linkname. // // Do not remove or change the type signature. // See go.dev/issue/67401. diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go index 78d00af6ca..c617a8b26a 100644 --- a/src/runtime/pprof/pprof.go +++ b/src/runtime/pprof/pprof.go @@ -924,7 +924,10 @@ func profileWriter(w io.Writer) { b := newProfileBuilder(w) var err error for { - time.Sleep(100 * time.Millisecond) + if runtime.GOOS == "darwin" || runtime.GOOS == "ios" { + // see runtime_pprof_readProfile + time.Sleep(100 * time.Millisecond) + } data, tags, eof := readProfile() if e := b.addCPUData(data, tags); e != nil && err == nil { err = e diff --git a/src/runtime/proc_test.go b/src/runtime/proc_test.go index 35a1aeab1f..a2c8b55342 100644 --- a/src/runtime/proc_test.go +++ b/src/runtime/proc_test.go @@ -1175,9 +1175,9 @@ func TestBigGOMAXPROCS(t *testing.T) { } type goroutineState struct { - G trace.GoID // This goroutine. - P trace.ProcID // Most recent P this goroutine ran on. - M trace.ThreadID // Most recent M this goroutine ran on. + G trace.GoID // This goroutine. + P trace.ProcID // Most recent P this goroutine ran on. + M trace.ThreadID // Most recent M this goroutine ran on. } func newGoroutineState(g trace.GoID) *goroutineState { @@ -1228,7 +1228,7 @@ func TestTraceSTW(t *testing.T) { } } - pct := float64(errors)/float64(runs) + pct := float64(errors) / float64(runs) t.Logf("Errors: %d/%d = %f%%", errors, runs, 100*pct) if pct > 0.25 { t.Errorf("Error rate too high") @@ -1264,7 +1264,7 @@ func TestTraceGCSTW(t *testing.T) { } } - pct := float64(errors)/float64(runs) + pct := float64(errors) / float64(runs) t.Logf("Errors: %d/%d = %f%%", errors, runs, 100*pct) if pct > 0.25 { t.Errorf("Error rate too high") @@ -1321,11 +1321,13 @@ func runTestTracesSTW(t *testing.T, run int, name, stwType string) (err error) { // // 2. Once found, track which M and P the target goroutines run on until... // - // 3. Look for the "TraceSTW" "start" log message, where we commit the - // target goroutines' "before" M and P. + // 3. Look for the first STW after the "TraceSTW" "start" log message, + // where we commit the target goroutines' "before" M and P. // // N.B. We must do (1) and (2) together because the first target // goroutine may start running before the second is created. + var startLogSeen bool + var stwSeen bool findStart: for { ev, err := br.ReadEvent() @@ -1384,10 +1386,26 @@ findStart: // Found start point, move on to next stage. t.Logf("Found start message") - break findStart + startLogSeen = true + case trace.EventRangeBegin: + if !startLogSeen { + // Ignore spurious STW before we expect. + continue + } + + r := ev.Range() + if r.Name == stwType { + t.Logf("Found STW") + stwSeen = true + break findStart + } } } + if !stwSeen { + t.Fatal("Can't find STW in the test trace") + } + t.Log("Target goroutines:") for _, gs := range targetGoroutines { t.Logf("%+v", gs) @@ -1440,7 +1458,6 @@ findStart: // [1] This is slightly fragile because there is a small window between // the "start" log and actual STW during which the target goroutines // could legitimately migrate. - var stwSeen bool var pRunning []trace.ProcID var gRunning []trace.GoID findEnd: @@ -1543,21 +1560,9 @@ findEnd: // Found end point. t.Logf("Found end message") break findEnd - case trace.EventRangeBegin: - r := ev.Range() - if r.Name == stwType { - // Note when we see the STW begin. This is not - // load bearing; it's purpose is simply to fail - // the test if we accidentally remove the STW. - stwSeen = true - } } } - if !stwSeen { - t.Fatal("No STW in the test trace") - } - return nil } diff --git a/src/runtime/profbuf.go b/src/runtime/profbuf.go index 8ae626b1b0..147212206f 100644 --- a/src/runtime/profbuf.go +++ b/src/runtime/profbuf.go @@ -38,6 +38,10 @@ import ( // be returned by read. By definition, r ≤ rNext ≤ w (before wraparound), // and rNext is only used by the reader, so it can be accessed without atomics. // +// If the reader is blocked waiting for more data, the writer will wake it up if +// either the buffer is more than half full, or when the writer sets the eof +// marker or writes overflow entries (described below.) +// // If the writer gets ahead of the reader, so that the buffer fills, // future writes are discarded and replaced in the output stream by an // overflow entry, which has size 2+hdrsize+1, time set to the time of @@ -378,11 +382,28 @@ func (b *profBuf) write(tagPtr *unsafe.Pointer, now int64, hdr []uint64, stk []u // Racing with reader setting flag bits in b.w, to avoid lost wakeups. old := b.w.load() new := old.addCountsAndClearFlags(skip+2+len(stk)+int(b.hdrsize), 1) + // We re-load b.r here to reduce the likelihood of early wakeups + // if the reader already consumed some data between the last + // time we read b.r and now. This isn't strictly necessary. + unread := countSub(new.dataCount(), b.r.load().dataCount()) + if unread < 0 { + // The new count overflowed and wrapped around. + unread += len(b.data) + } + wakeupThreshold := len(b.data) / 2 + if unread < wakeupThreshold { + // Carry over the sleeping flag since we're not planning + // to wake the reader yet + new |= old & profReaderSleeping + } if !b.w.cas(old, new) { continue } - // If there was a reader, wake it up. - if old&profReaderSleeping != 0 { + // If we've hit our high watermark for data in the buffer, + // and there is a reader, wake it up. + if unread >= wakeupThreshold && old&profReaderSleeping != 0 { + // NB: if we reach this point, then the sleeping bit is + // cleared in the new b.w value notewakeup(&b.wait) } break @@ -406,6 +427,11 @@ func (b *profBuf) wakeupExtra() { for { old := b.w.load() new := old | profWriteExtra + // Clear profReaderSleeping. We're going to wake up the reader + // if it was sleeping and we don't want double wakeups in case + // we, for example, attempt to write into a full buffer multiple + // times before the reader wakes up. + new &^= profReaderSleeping if !b.w.cas(old, new) { continue } diff --git a/src/runtime/profbuf_test.go b/src/runtime/profbuf_test.go index 9050d1fa25..2f068ac386 100644 --- a/src/runtime/profbuf_test.go +++ b/src/runtime/profbuf_test.go @@ -5,8 +5,12 @@ package runtime_test import ( + "fmt" + "regexp" + "runtime" . "runtime" "slices" + "sync" "testing" "time" "unsafe" @@ -174,3 +178,91 @@ func TestProfBuf(t *testing.T) { } }) } + +func TestProfBufDoubleWakeup(t *testing.T) { + b := NewProfBuf(2, 16, 2) + go func() { + for range 1000 { + b.Write(nil, 1, []uint64{5, 6}, []uintptr{7, 8}) + } + b.Close() + }() + for { + _, _, eof := b.Read(ProfBufBlocking) + if eof { + return + } + } +} + +func TestProfBufWakeup(t *testing.T) { + b := NewProfBuf(2, 16, 2) + var wg sync.WaitGroup + wg.Go(func() { + read := 0 + for { + rdata, _, eof := b.Read(ProfBufBlocking) + if read == 0 && len(rdata) < 8 { + t.Errorf("first wake up at less than half full, got %x", rdata) + } + read += len(rdata) + if eof { + return + } + } + }) + + // Under the hood profBuf uses notetsleepg when the reader blocks. + // Different platforms have different implementations, leading to + // different statuses we need to look for to determine whether the + // reader is blocked. + var waitStatus string + switch runtime.GOOS { + case "js": + waitStatus = "waiting" + case "wasip1": + waitStatus = "runnable" + default: + waitStatus = "syscall" + } + + // Ensure that the reader is blocked + awaitBlockedGoroutine(waitStatus, "TestProfBufWakeup.func1") + // NB: this writes 6 words not 4. Fine for the test. + // The reader shouldn't wake up for this + b.Write(nil, 1, []uint64{1, 2}, []uintptr{3, 4}) + + // The reader should still be blocked + // + // TODO(nick): this is racy. We could Gosched and still have the reader + // blocked in a buggy implementation because it just didn't get a chance + // to run + awaitBlockedGoroutine(waitStatus, "TestProfBufWakeup.func1") + b.Write(nil, 1, []uint64{5, 6}, []uintptr{7, 8}) + b.Close() + + // Wait here so we can be sure the reader got the data + wg.Wait() +} + +// see also runtime/pprof tests +func awaitBlockedGoroutine(state, fName string) { + re := fmt.Sprintf(`(?m)^goroutine \d+ \[%s\]:\n(?:.+\n\t.+\n)*runtime_test\.%s`, regexp.QuoteMeta(state), fName) + r := regexp.MustCompile(re) + + buf := make([]byte, 64<<10) + for { + Gosched() + n := Stack(buf, true) + if n == len(buf) { + // Buffer wasn't large enough for a full goroutine dump. + // Resize it and try again. + buf = make([]byte, 2*len(buf)) + continue + } + const count = 1 + if len(r.FindAll(buf[:n], -1)) >= count { + return + } + } +} diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index 56082bf7f5..3175ee55f5 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -838,7 +838,7 @@ type p struct { palloc persistentAlloc // per-P to avoid mutex // Per-P GC state - gcAssistTime int64 // Nanoseconds in assistAlloc + gcAssistTime int64 // Nanoseconds in assistAlloc gcFractionalMarkTime atomic.Int64 // Nanoseconds in fractional mark worker // limiterEvent tracks events for the GC CPU limiter. @@ -934,12 +934,12 @@ type schedt struct { // sure to call checkdead(). midle listHeadManual // idle m's waiting for work - nmidle int32 // number of idle m's waiting for work - nmidlelocked int32 // number of locked m's waiting for work - mnext int64 // number of m's that have been created and next M ID - maxmcount int32 // maximum number of m's allowed (or die) - nmsys int32 // number of system m's not counted for deadlock - nmfreed int64 // cumulative number of freed m's + nmidle int32 // number of idle m's waiting for work + nmidlelocked int32 // number of locked m's waiting for work + mnext int64 // number of m's that have been created and next M ID + maxmcount int32 // maximum number of m's allowed (or die) + nmsys int32 // number of system m's not counted for deadlock + nmfreed int64 // cumulative number of freed m's ngsys atomic.Int32 // number of system goroutines nGsyscallNoP atomic.Int32 // number of goroutines in syscalls without a P diff --git a/src/runtime/slice_test.go b/src/runtime/slice_test.go index 5463b6c02f..376b4a58f2 100644 --- a/src/runtime/slice_test.go +++ b/src/runtime/slice_test.go @@ -6,6 +6,8 @@ package runtime_test import ( "fmt" + "internal/asan" + "internal/msan" "internal/race" "internal/testenv" "runtime" @@ -516,6 +518,9 @@ func TestAppendByteInLoop(t *testing.T) { if race.Enabled { t.Skip("skipping in -race mode") } + if asan.Enabled || msan.Enabled { + t.Skip("skipping in sanitizer mode") + } for _, test := range [][3]int{ {0, 0, 0}, {1, 1, 8}, @@ -562,6 +567,9 @@ func TestAppendPtrInLoop(t *testing.T) { if race.Enabled { t.Skip("skipping in -race mode") } + if asan.Enabled || msan.Enabled { + t.Skip("skipping in sanitizer mode") + } var tests [][3]int if runtime.PtrSize == 8 { tests = [][3]int{ @@ -628,6 +636,9 @@ func TestAppendByteCapInLoop(t *testing.T) { if race.Enabled { t.Skip("skipping in -race mode") } + if asan.Enabled || msan.Enabled { + t.Skip("skipping in sanitizer mode") + } for _, test := range [][3]int{ {0, 0, 0}, {1, 1, 8}, diff --git a/src/runtime/stack.go b/src/runtime/stack.go index 55e97e77af..c92accf188 100644 --- a/src/runtime/stack.go +++ b/src/runtime/stack.go @@ -12,6 +12,7 @@ import ( "internal/runtime/atomic" "internal/runtime/gc" "internal/runtime/sys" + "math/bits" "unsafe" ) @@ -181,12 +182,10 @@ func stackinit() { // stacklog2 returns ⌊log_2(n)⌋. func stacklog2(n uintptr) int { - log2 := 0 - for n > 1 { - n >>= 1 - log2++ + if n == 0 { + return 0 } - return log2 + return bits.Len64(uint64(n)) } // Allocates a stack from the free pool. Must be called with diff --git a/src/runtime/trace.go b/src/runtime/trace.go index 7130e2c136..0fdc829f71 100644 --- a/src/runtime/trace.go +++ b/src/runtime/trace.go @@ -13,17 +13,17 @@ // ## Design // // The basic idea behind the the execution tracer is to have per-M buffers that -// trace data may be written into. Each M maintains a seqlock indicating whether +// trace data may be written into. Each M maintains a write flag indicating whether // its trace buffer is currently in use. // // Tracing is initiated by StartTrace, and proceeds in "generations," with each // generation being marked by a call to traceAdvance, to advance to the next // generation. Generations are a global synchronization point for trace data, // and we proceed to a new generation by moving forward trace.gen. Each M reads -// trace.gen under its own seqlock to determine which generation it is writing +// trace.gen under its own write flag to determine which generation it is writing // trace data for. To this end, each M has 2 slots for buffers: one slot for the // previous generation, one slot for the current one. It uses tl.gen to select -// which buffer slot to write to. Simultaneously, traceAdvance uses the seqlock +// which buffer slot to write to. Simultaneously, traceAdvance uses the write flag // to determine whether every thread is guaranteed to observe an updated // trace.gen. Once it is sure, it may then flush any buffers that are left over // from the previous generation safely, since it knows the Ms will not mutate @@ -43,7 +43,7 @@ // appear in pairs: one for the previous generation, and one for the current one. // Like the per-M buffers, which of the two is written to is selected using trace.gen, // and anything managed this way must similarly be mutated only in traceAdvance or -// under the M's seqlock. +// under the M's write flag. // // Trace events themselves are simple. They consist of a single byte for the event type, // followed by zero or more LEB128-encoded unsigned varints. They are decoded using @@ -629,7 +629,7 @@ func traceAdvance(stopTrace bool) { // while they're still on that list. Removal from sched.freem is serialized with // this snapshot, so either we'll capture an m on sched.freem and race with // the removal to flush its buffers (resolved by traceThreadDestroy acquiring - // the thread's seqlock, which one of us must win, so at least its old gen buffer + // the thread's write flag, which one of us must win, so at least its old gen buffer // will be flushed in time for the new generation) or it will have flushed its // buffers before we snapshotted it to begin with. lock(&sched.lock) @@ -645,7 +645,7 @@ func traceAdvance(stopTrace bool) { // Iterate over our snapshot, flushing every buffer until we're done. // - // Because trace writers read the generation while the seqlock is + // Because trace writers read the generation while the write flag is // held, we can be certain that when there are no writers there are // also no stale generation values left. Therefore, it's safe to flush // any buffers that remain in that generation's slot. @@ -658,7 +658,7 @@ func traceAdvance(stopTrace bool) { for mToFlush != nil { prev := &mToFlush for mp := *prev; mp != nil; { - if mp.trace.seqlock.Load()%2 != 0 { + if mp.trace.writing.Load() { // The M is writing. Come back to it later. prev = &mp.trace.link mp = mp.trace.link diff --git a/src/runtime/trace/annotation_test.go b/src/runtime/trace/annotation_test.go index 3bd767bfbe..ea10843230 100644 --- a/src/runtime/trace/annotation_test.go +++ b/src/runtime/trace/annotation_test.go @@ -6,6 +6,7 @@ package trace_test import ( "context" + "io" . "runtime/trace" "testing" ) @@ -34,3 +35,15 @@ func BenchmarkNewTask(b *testing.B) { } }) } + +func BenchmarkLog(b *testing.B) { + b.ReportAllocs() + + Start(io.Discard) + defer Stop() + + ctx := context.Background() + for b.Loop() { + Log(ctx, "", "") + } +} diff --git a/src/runtime/tracecpu.go b/src/runtime/tracecpu.go index e64ca32cdf..c9c3a1511f 100644 --- a/src/runtime/tracecpu.go +++ b/src/runtime/tracecpu.go @@ -224,20 +224,20 @@ func traceCPUSample(gp *g, mp *m, pp *p, stk []uintptr) { // We're going to conditionally write to one of two buffers based on the // generation. To make sure we write to the correct one, we need to make - // sure this thread's trace seqlock is held. If it already is, then we're + // sure this thread's trace write flag is set. If it already is, then we're // in the tracer and we can just take advantage of that. If it isn't, then // we need to acquire it and read the generation. locked := false - if mp.trace.seqlock.Load()%2 == 0 { - mp.trace.seqlock.Add(1) + if !mp.trace.writing.Load() { + mp.trace.writing.Store(true) locked = true } gen := trace.gen.Load() if gen == 0 { - // Tracing is disabled, as it turns out. Release the seqlock if necessary + // Tracing is disabled, as it turns out. Clear the write flag if necessary // and exit. if locked { - mp.trace.seqlock.Add(1) + mp.trace.writing.Store(false) } return } @@ -275,8 +275,8 @@ func traceCPUSample(gp *g, mp *m, pp *p, stk []uintptr) { trace.signalLock.Store(0) - // Release the seqlock if we acquired it earlier. + // Clear the write flag if we set it earlier. if locked { - mp.trace.seqlock.Add(1) + mp.trace.writing.Store(false) } } diff --git a/src/runtime/traceruntime.go b/src/runtime/traceruntime.go index ad91d9c836..92d07c6063 100644 --- a/src/runtime/traceruntime.go +++ b/src/runtime/traceruntime.go @@ -25,7 +25,7 @@ func (s *gTraceState) reset() { // mTraceState is per-M state for the tracer. type mTraceState struct { - seqlock atomic.Uintptr // seqlock indicating that this M is writing to a trace buffer. + writing atomic.Bool // flag indicating that this M is writing to a trace buffer. buf [2][tracev2.NumExperiments]*traceBuf // Per-M traceBuf for writing. Indexed by trace.gen%2. link *m // Snapshot of alllink or freelink. reentered uint32 // Whether we've reentered tracing from within tracing. @@ -211,21 +211,18 @@ func traceAcquireEnabled() traceLocker { // Check if we're already tracing. It's safe to be reentrant in general, // because this function (and the invariants of traceLocker.writer) ensure // that it is. - if mp.trace.seqlock.Load()%2 == 1 { + if mp.trace.writing.Load() { mp.trace.reentered++ return traceLocker{mp, mp.trace.entryGen} } - // Acquire the trace seqlock. This prevents traceAdvance from moving forward - // until all Ms are observed to be outside of their seqlock critical section. + // Set the write flag. This prevents traceAdvance from moving forward + // until all Ms are observed to be outside of a write critical section. // - // Note: The seqlock is mutated here and also in traceCPUSample. If you update - // usage of the seqlock here, make sure to also look at what traceCPUSample is + // Note: The write flag is mutated here and also in traceCPUSample. If you update + // usage of the write flag here, make sure to also look at what traceCPUSample is // doing. - seq := mp.trace.seqlock.Add(1) - if debugTraceReentrancy && seq%2 != 1 { - throw("bad use of trace.seqlock") - } + mp.trace.writing.Store(true) // N.B. This load of gen appears redundant with the one in traceEnabled. // However, it's very important that the gen we use for writing to the trace @@ -237,7 +234,7 @@ func traceAcquireEnabled() traceLocker { // what we did and bail. gen := trace.gen.Load() if gen == 0 { - mp.trace.seqlock.Add(1) + mp.trace.writing.Store(false) releasem(mp) return traceLocker{} } @@ -263,11 +260,7 @@ func traceRelease(tl traceLocker) { if tl.mp.trace.reentered > 0 { tl.mp.trace.reentered-- } else { - seq := tl.mp.trace.seqlock.Add(1) - if debugTraceReentrancy && seq%2 != 0 { - print("runtime: seq=", seq, "\n") - throw("bad use of trace.seqlock") - } + tl.mp.trace.writing.Store(false) } releasem(tl.mp) } @@ -699,10 +692,10 @@ func traceThreadDestroy(mp *m) { // Perform a traceAcquire/traceRelease on behalf of mp to // synchronize with the tracer trying to flush our buffer // as well. - seq := mp.trace.seqlock.Add(1) - if debugTraceReentrancy && seq%2 != 1 { - throw("bad use of trace.seqlock") + if debugTraceReentrancy && mp.trace.writing.Load() { + throw("bad use of trace.writing") } + mp.trace.writing.Store(true) systemstack(func() { lock(&trace.lock) for i := range mp.trace.buf { @@ -717,9 +710,5 @@ func traceThreadDestroy(mp *m) { } unlock(&trace.lock) }) - seq1 := mp.trace.seqlock.Add(1) - if seq1 != seq+1 { - print("runtime: seq1=", seq1, "\n") - throw("bad use of trace.seqlock") - } + mp.trace.writing.Store(false) } |
