Skip to content

Commit ca1d2ea

Browse files
mknyszekgopherbot
authored andcommitted
runtime: skip tracing events that would cause reentrancy
Some of the new experimental events added have a problem in that they might be emitted during stack growth. This is, to my knowledge, the only restriction on the tracer, because the tracer otherwise prevents preemption, avoids allocation, and avoids write barriers. However, the stack can grow from within the tracer. This leads to tracing-during-tracing which can result in lost buffers and broken event streams. (There's a debug mode to get a nice error message, but it's disabled by default.) This change resolves the problem by skipping writing out these new events. This results in the new events sometimes being broken (alloc without a free, free without an alloc) but for now that's OK. Before the freeze begins we just want to fix broken tests; tools interpreting these events will be totally in-house to begin with, and if they have to be a little bit smarter about missing information, that's OK. In the future we'll have a more robust fix for this, but it appears that it's going to require making the tracer fully reentrant. (This is not too hard; either we force flushing all buffers when going reentrant (which is actually somewhat subtle with respect to event ordering) or we isolate down just the actual event writing to be atomic with respect to stack growth. Both are just bigger changes on shared codepaths that are scary to land this late in the release cycle.) Fixes #67379. Change-Id: I46bb7e470e61c64ff54ac5aec5554b828c1ca4be Reviewed-on: https://go-review.googlesource.com/c/go/+/587597 Reviewed-by: Carlos Amedee <[email protected]> Auto-Submit: Michael Knyszek <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 587c384 commit ca1d2ea

File tree

3 files changed

+41
-5
lines changed

3 files changed

+41
-5
lines changed

src/runtime/mheap.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,7 +1368,7 @@ HaveSpan:
13681368

13691369
// Trace the span alloc.
13701370
if traceAllocFreeEnabled() {
1371-
trace := traceAcquire()
1371+
trace := traceTryAcquire()
13721372
if trace.ok() {
13731373
trace.SpanAlloc(s)
13741374
traceRelease(trace)
@@ -1556,7 +1556,7 @@ func (h *mheap) freeSpan(s *mspan) {
15561556
systemstack(func() {
15571557
// Trace the span free.
15581558
if traceAllocFreeEnabled() {
1559-
trace := traceAcquire()
1559+
trace := traceTryAcquire()
15601560
if trace.ok() {
15611561
trace.SpanFree(s)
15621562
traceRelease(trace)
@@ -1595,7 +1595,7 @@ func (h *mheap) freeSpan(s *mspan) {
15951595
func (h *mheap) freeManual(s *mspan, typ spanAllocType) {
15961596
// Trace the span free.
15971597
if traceAllocFreeEnabled() {
1598-
trace := traceAcquire()
1598+
trace := traceTryAcquire()
15991599
if trace.ok() {
16001600
trace.SpanFree(s)
16011601
traceRelease(trace)

src/runtime/stack.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ func stackalloc(n uint32) stack {
416416
}
417417

418418
if traceAllocFreeEnabled() {
419-
trace := traceAcquire()
419+
trace := traceTryAcquire()
420420
if trace.ok() {
421421
trace.GoroutineStackAlloc(uintptr(v), uintptr(n))
422422
traceRelease(trace)
@@ -466,7 +466,7 @@ func stackfree(stk stack) {
466466
return
467467
}
468468
if traceAllocFreeEnabled() {
469-
trace := traceAcquire()
469+
trace := traceTryAcquire()
470470
if trace.ok() {
471471
trace.GoroutineStackFree(uintptr(v))
472472
traceRelease(trace)

src/runtime/traceruntime.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,22 @@ func traceAcquire() traceLocker {
184184
return traceAcquireEnabled()
185185
}
186186

187+
// traceTryAcquire is like traceAcquire, but may return an invalid traceLocker even
188+
// if tracing is enabled. For example, it will return !ok if traceAcquire is being
189+
// called with an active traceAcquire on the M (reentrant locking). This exists for
190+
// optimistically emitting events in the few contexts where tracing is now allowed.
191+
//
192+
// nosplit for alignment with traceTryAcquire, so it can be used in the
193+
// same contexts.
194+
//
195+
//go:nosplit
196+
func traceTryAcquire() traceLocker {
197+
if !traceEnabled() {
198+
return traceLocker{}
199+
}
200+
return traceTryAcquireEnabled()
201+
}
202+
187203
// traceAcquireEnabled is the traceEnabled path for traceAcquire. It's explicitly
188204
// broken out to make traceAcquire inlineable to keep the overhead of the tracer
189205
// when it's disabled low.
@@ -228,6 +244,26 @@ func traceAcquireEnabled() traceLocker {
228244
return traceLocker{mp, gen}
229245
}
230246

247+
// traceTryAcquireEnabled is like traceAcquireEnabled but may return an invalid
248+
// traceLocker under some conditions. See traceTryAcquire for more details.
249+
//
250+
// nosplit for alignment with traceAcquireEnabled, so it can be used in the
251+
// same contexts.
252+
//
253+
//go:nosplit
254+
func traceTryAcquireEnabled() traceLocker {
255+
// Any time we acquire a traceLocker, we may flush a trace buffer. But
256+
// buffer flushes are rare. Record the lock edge even if it doesn't happen
257+
// this time.
258+
lockRankMayTraceFlush()
259+
260+
// Check if we're already locked. If so, return an invalid traceLocker.
261+
if getg().m.trace.seqlock.Load()%2 == 1 {
262+
return traceLocker{}
263+
}
264+
return traceAcquireEnabled()
265+
}
266+
231267
// ok returns true if the traceLocker is valid (i.e. tracing is enabled).
232268
//
233269
// nosplit because it's called on the syscall path when stack movement is forbidden.

0 commit comments

Comments
 (0)