Skip to content

Commit 20f4b6d

Browse files
aktaugopherbot
authored andcommitted
runtime: call traceAdvance before exiting
This ensures the trace buffers are as up-to-date as possible right before crashing. It increases the chance of finding the culprit for the crash when looking at core dumps, e.g. if slowness is the cause for the crash (monitor kills process). Fixes #65319. Change-Id: Iaf5551911b3b3b01ba65cb8749cf62a411e02d9c Reviewed-on: https://go-review.googlesource.com/c/go/+/562616 Auto-Submit: Michael Knyszek <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
1 parent 2dfc5ea commit 20f4b6d

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed

src/runtime/crash_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@ package runtime_test
66

77
import (
88
"bytes"
9+
"context"
910
"errors"
1011
"flag"
1112
"fmt"
13+
"internal/goexperiment"
1214
"internal/testenv"
15+
tracev2 "internal/trace/v2"
16+
"io"
1317
"log"
1418
"os"
1519
"os/exec"
1620
"path/filepath"
1721
"regexp"
1822
"runtime"
23+
"runtime/trace"
1924
"strings"
2025
"sync"
2126
"testing"
@@ -874,6 +879,71 @@ func TestG0StackOverflow(t *testing.T) {
874879
runtime.G0StackOverflow()
875880
}
876881

882+
// For TestCrashWhileTracing: test a panic without involving the testing
883+
// harness, as we rely on stdout only containing trace output.
884+
func init() {
885+
if os.Getenv("TEST_CRASH_WHILE_TRACING") == "1" {
886+
trace.Start(os.Stdout)
887+
trace.Log(context.Background(), "xyzzy-cat", "xyzzy-msg")
888+
panic("yzzyx")
889+
}
890+
}
891+
892+
func TestCrashWhileTracing(t *testing.T) {
893+
if !goexperiment.ExecTracer2 {
894+
t.Skip("skipping because this test is incompatible with the legacy tracer")
895+
}
896+
897+
testenv.MustHaveExec(t)
898+
899+
cmd := testenv.CleanCmdEnv(testenv.Command(t, os.Args[0]))
900+
cmd.Env = append(cmd.Env, "TEST_CRASH_WHILE_TRACING=1")
901+
stdOut, err := cmd.StdoutPipe()
902+
var errOut bytes.Buffer
903+
cmd.Stderr = &errOut
904+
905+
if err := cmd.Start(); err != nil {
906+
t.Fatalf("could not start subprocess: %v", err)
907+
}
908+
r, err := tracev2.NewReader(stdOut)
909+
if err != nil {
910+
t.Fatalf("could not create trace.NewReader: %v", err)
911+
}
912+
var seen bool
913+
i := 1
914+
loop:
915+
for ; ; i++ {
916+
ev, err := r.ReadEvent()
917+
if err != nil {
918+
if err != io.EOF {
919+
t.Errorf("error at event %d: %v", i, err)
920+
}
921+
break loop
922+
}
923+
switch ev.Kind() {
924+
case tracev2.EventLog:
925+
v := ev.Log()
926+
if v.Category == "xyzzy-cat" && v.Message == "xyzzy-msg" {
927+
// Should we already stop reading here? More events may come, but
928+
// we're not guaranteeing a fully unbroken trace until the last
929+
// byte...
930+
seen = true
931+
}
932+
}
933+
}
934+
if err := cmd.Wait(); err == nil {
935+
t.Error("the process should have panicked")
936+
}
937+
if !seen {
938+
t.Errorf("expected one matching log event matching, but none of the %d received trace events match", i)
939+
}
940+
t.Logf("stderr output:\n%s", errOut.String())
941+
needle := "yzzyx\n"
942+
if n := strings.Count(errOut.String(), needle); n != 1 {
943+
t.Fatalf("did not find expected panic message %q\n(exit status %v)", needle, err)
944+
}
945+
}
946+
877947
// Test that panic message is not clobbered.
878948
// See issue 30150.
879949
func TestDoublePanic(t *testing.T) {

src/runtime/panic.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,16 @@ func gopanic(e any) {
759759
fn()
760760
}
761761

762+
// If we're tracing, flush the current generation to make the trace more
763+
// readable.
764+
//
765+
// TODO(aktau): Handle a panic from within traceAdvance more gracefully.
766+
// Currently it would hang. Not handled now because it is very unlikely, and
767+
// already unrecoverable.
768+
if traceEnabled() {
769+
traceAdvance(false)
770+
}
771+
762772
// ran out of deferred calls - old-school panic now
763773
// Because it is unsafe to call arbitrary user code after freezing
764774
// the world, we call preprintpanics to invoke all necessary Error

src/runtime/trace.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,9 @@ func StopTrace() {
578578
})
579579
}
580580

581+
// traceAdvance is called from panic, it does nothing for the legacy tracer.
582+
func traceAdvance(stopTrace bool) {}
583+
581584
// ReadTrace returns the next chunk of binary tracing data, blocking until data
582585
// is available. If tracing is turned off and all the data accumulated while it
583586
// was on has been returned, ReadTrace returns nil. The caller must copy the

0 commit comments

Comments
 (0)