runtime: allow the tracer to be reentrant

This change allows the tracer to be reentrant by restructuring the
internals such that writing an event is atomic with respect to stack
growth. Essentially, a core set of functions that are involved in
acquiring a trace buffer and writing to it are all marked nosplit.

Stack growth is currently the only hidden place where the tracer may be
accidentally reentrant, preventing the tracer from being used
everywhere. It already lacks write barriers, lacks allocations, and is
non-preemptible. This change thus makes the tracer fully reentrant,
since the only reentry case it needs to handle is stack growth.

Since the invariants needed to attain this are subtle, this change also
extends the debugTraceReentrancy debug mode to check these invariants as
well. Specifically, the invariants are checked by setting the throwsplit
flag.

A side benefit of this change is it simplifies the trace event writing
API a good bit: there's no need to actually thread the event writer
through things, and most callsites look a bit simpler.

Change-Id: I7c329fb7a6cb936bd363c44cf882ea0a925132f3
Reviewed-on: https://go-review.googlesource.com/c/go/+/587599
Reviewed-by: Austin Clements <austin@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Michael Anthony Knyszek 2024-05-22 21:46:29 +00:00 committed by Michael Knyszek
parent bd6f911f85
commit e76353d5a9
9 changed files with 227 additions and 158 deletions

View file

@ -416,7 +416,7 @@ func stackalloc(n uint32) stack {
}
if traceAllocFreeEnabled() {
trace := traceTryAcquire()
trace := traceAcquire()
if trace.ok() {
trace.GoroutineStackAlloc(uintptr(v), uintptr(n))
traceRelease(trace)
@ -466,7 +466,7 @@ func stackfree(stk stack) {
return
}
if traceAllocFreeEnabled() {
trace := traceTryAcquire()
trace := traceAcquire()
if trace.ok() {
trace.GoroutineStackFree(uintptr(v))
traceRelease(trace)