2012-05-30 15:10:54 +10:00
|
|
|
// Copyright 2012 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_test
|
|
|
|
|
|
|
|
|
|
import (
|
2016-06-14 14:38:22 -07:00
|
|
|
"bytes"
|
2024-02-08 08:56:33 -08:00
|
|
|
"context"
|
2021-11-16 22:18:02 -08:00
|
|
|
"errors"
|
2016-07-06 15:02:49 -07:00
|
|
|
"flag"
|
2015-01-05 13:14:08 -08:00
|
|
|
"fmt"
|
2015-06-05 11:01:53 -04:00
|
|
|
"internal/testenv"
|
2024-05-09 10:45:01 -04:00
|
|
|
traceparse "internal/trace"
|
2024-02-08 08:56:33 -08:00
|
|
|
"io"
|
2024-03-14 15:32:00 -04:00
|
|
|
"log"
|
2012-05-30 15:10:54 +10:00
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"path/filepath"
|
2015-02-07 15:31:18 +03:00
|
|
|
"regexp"
|
2014-05-20 12:10:19 -04:00
|
|
|
"runtime"
|
2024-02-08 08:56:33 -08:00
|
|
|
"runtime/trace"
|
2013-02-20 12:15:02 +04:00
|
|
|
"strings"
|
2015-01-05 13:14:08 -08:00
|
|
|
"sync"
|
2012-05-30 15:10:54 +10:00
|
|
|
"testing"
|
2022-10-26 11:44:34 -04:00
|
|
|
"time"
|
2012-05-30 15:10:54 +10:00
|
|
|
)
|
|
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
var toRemove []string
|
|
|
|
|
|
2024-03-14 15:32:00 -04:00
|
|
|
const entrypointVar = "RUNTIME_TEST_ENTRYPOINT"
|
|
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
func TestMain(m *testing.M) {
|
2024-03-14 15:32:00 -04:00
|
|
|
switch entrypoint := os.Getenv(entrypointVar); entrypoint {
|
2024-12-19 20:58:19 -05:00
|
|
|
case "panic":
|
|
|
|
|
crashViaPanic()
|
|
|
|
|
panic("unreachable")
|
|
|
|
|
case "trap":
|
|
|
|
|
crashViaTrap()
|
2024-03-14 15:32:00 -04:00
|
|
|
panic("unreachable")
|
|
|
|
|
default:
|
|
|
|
|
log.Fatalf("invalid %s: %q", entrypointVar, entrypoint)
|
|
|
|
|
case "":
|
|
|
|
|
// fall through to normal behavior
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 12:52:48 -07:00
|
|
|
_, coreErrBefore := os.Stat("core")
|
|
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
status := m.Run()
|
|
|
|
|
for _, file := range toRemove {
|
|
|
|
|
os.RemoveAll(file)
|
|
|
|
|
}
|
2023-09-01 12:52:48 -07:00
|
|
|
|
|
|
|
|
_, coreErrAfter := os.Stat("core")
|
|
|
|
|
if coreErrBefore != nil && coreErrAfter == nil {
|
|
|
|
|
fmt.Fprintln(os.Stderr, "runtime.test: some test left a core file behind")
|
|
|
|
|
if status == 0 {
|
|
|
|
|
status = 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
os.Exit(status)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var testprog struct {
|
|
|
|
|
sync.Mutex
|
|
|
|
|
dir string
|
2021-11-16 22:18:02 -08:00
|
|
|
target map[string]*buildexe
|
2015-12-21 10:29:21 -05:00
|
|
|
}
|
2014-05-20 12:10:19 -04:00
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
type buildexe struct {
|
2021-11-16 22:18:02 -08:00
|
|
|
once sync.Once
|
|
|
|
|
exe string
|
|
|
|
|
err error
|
2015-12-21 10:29:21 -05:00
|
|
|
}
|
2012-05-30 15:10:54 +10:00
|
|
|
|
2017-06-16 16:21:12 -04:00
|
|
|
func runTestProg(t *testing.T, binary, name string, env ...string) string {
|
2017-10-27 13:30:09 -04:00
|
|
|
if *flagQuick {
|
|
|
|
|
t.Skip("-quick")
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
testenv.MustHaveGoBuild(t)
|
2023-02-02 10:09:39 -05:00
|
|
|
t.Helper()
|
2012-05-30 15:10:54 +10:00
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
exe, err := buildTestProg(t, binary)
|
2012-05-30 15:10:54 +10:00
|
|
|
if err != nil {
|
2015-12-21 10:29:21 -05:00
|
|
|
t.Fatal(err)
|
2012-05-30 15:10:54 +10:00
|
|
|
}
|
2016-06-14 14:38:22 -07:00
|
|
|
|
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
|
|
|
return runBuiltTestProg(t, exe, name, env...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runBuiltTestProg(t *testing.T, exe, name string, env ...string) string {
|
2022-10-26 11:44:34 -04:00
|
|
|
t.Helper()
|
|
|
|
|
|
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
|
|
|
if *flagQuick {
|
|
|
|
|
t.Skip("-quick")
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-03 09:08:33 -04:00
|
|
|
start := time.Now()
|
|
|
|
|
|
|
|
|
|
cmd := testenv.CleanCmdEnv(testenv.Command(t, exe, name))
|
2017-06-16 16:21:12 -04:00
|
|
|
cmd.Env = append(cmd.Env, env...)
|
2017-10-27 13:30:09 -04:00
|
|
|
if testing.Short() {
|
|
|
|
|
cmd.Env = append(cmd.Env, "RUNTIME_TEST_SHORT=1")
|
|
|
|
|
}
|
2022-10-26 11:44:34 -04:00
|
|
|
out, err := cmd.CombinedOutput()
|
2022-11-03 09:08:33 -04:00
|
|
|
if err == nil {
|
|
|
|
|
t.Logf("%v (%v): ok", cmd, time.Since(start))
|
|
|
|
|
} else {
|
2022-10-26 11:44:34 -04:00
|
|
|
if _, ok := err.(*exec.ExitError); ok {
|
|
|
|
|
t.Logf("%v: %v", cmd, err)
|
2022-11-01 09:24:54 -04:00
|
|
|
} else if errors.Is(err, exec.ErrWaitDelay) {
|
|
|
|
|
t.Fatalf("%v: %v", cmd, err)
|
2022-10-26 11:44:34 -04:00
|
|
|
} else {
|
|
|
|
|
t.Fatalf("%v failed to start: %v", cmd, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-09 12:25:04 -05:00
|
|
|
return string(out)
|
2015-12-21 10:29:21 -05:00
|
|
|
}
|
2012-05-30 15:10:54 +10:00
|
|
|
|
2021-11-16 22:18:02 -08:00
|
|
|
var serializeBuild = make(chan bool, 2)
|
|
|
|
|
|
2016-05-27 16:03:44 -07:00
|
|
|
func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) {
|
2017-10-27 13:30:09 -04:00
|
|
|
if *flagQuick {
|
|
|
|
|
t.Skip("-quick")
|
|
|
|
|
}
|
2021-11-16 22:18:02 -08:00
|
|
|
testenv.MustHaveGoBuild(t)
|
2017-10-27 13:30:09 -04:00
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
testprog.Lock()
|
|
|
|
|
if testprog.dir == "" {
|
2020-10-29 14:17:47 -04:00
|
|
|
dir, err := os.MkdirTemp("", "go-build")
|
2015-12-21 10:29:21 -05:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create temp directory: %v", err)
|
2014-10-28 21:53:09 -04:00
|
|
|
}
|
2015-12-21 10:29:21 -05:00
|
|
|
testprog.dir = dir
|
|
|
|
|
toRemove = append(toRemove, dir)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if testprog.target == nil {
|
2021-11-16 22:18:02 -08:00
|
|
|
testprog.target = make(map[string]*buildexe)
|
2015-12-21 10:29:21 -05:00
|
|
|
}
|
2016-05-27 16:03:44 -07:00
|
|
|
name := binary
|
|
|
|
|
if len(flags) > 0 {
|
|
|
|
|
name += "_" + strings.Join(flags, "_")
|
|
|
|
|
}
|
|
|
|
|
target, ok := testprog.target[name]
|
2021-11-16 22:18:02 -08:00
|
|
|
if !ok {
|
|
|
|
|
target = &buildexe{}
|
2016-05-27 16:03:44 -07:00
|
|
|
testprog.target[name] = target
|
2014-10-28 21:53:09 -04:00
|
|
|
}
|
2021-11-16 22:18:02 -08:00
|
|
|
|
|
|
|
|
dir := testprog.dir
|
|
|
|
|
|
|
|
|
|
// Unlock testprog while actually building, so that other
|
|
|
|
|
// tests can look up executables that were already built.
|
|
|
|
|
testprog.Unlock()
|
|
|
|
|
|
|
|
|
|
target.once.Do(func() {
|
|
|
|
|
// Only do two "go build"'s at a time,
|
|
|
|
|
// to keep load from getting too high.
|
|
|
|
|
serializeBuild <- true
|
|
|
|
|
defer func() { <-serializeBuild }()
|
|
|
|
|
|
|
|
|
|
// Don't get confused if testenv.GoToolPath calls t.Skip.
|
|
|
|
|
target.err = errors.New("building test called t.Skip")
|
|
|
|
|
|
|
|
|
|
exe := filepath.Join(dir, name+".exe")
|
|
|
|
|
|
2023-02-02 10:09:39 -05:00
|
|
|
start := time.Now()
|
2021-11-16 22:18:02 -08:00
|
|
|
cmd := exec.Command(testenv.GoToolPath(t), append([]string{"build", "-o", exe}, flags...)...)
|
2023-02-02 10:09:39 -05:00
|
|
|
t.Logf("running %v", cmd)
|
2021-11-16 22:18:02 -08:00
|
|
|
cmd.Dir = "testdata/" + binary
|
runtime: fix coro interactions with thread-locked goroutines
This change fixes problems with thread-locked goroutines using
newcoro/coroswitch/etc. Currently, the coro paths do not consider
thread-locked goroutines at all and can quickly result in broken
scheduler state or lost/leaked goroutines.
One possible fix to these issues is to fall back on goroutine+channel
semantics, but that turns out to be fairly complicated to implement and
results in significant performance cliffs. More complex thread-lock
state donation tricks also result in some fairly complicated state
tracking that doesn't seem worth it given the use-cases of iter.Pull
(and even then, there will be performance cliffs).
This change implements a much simpler, but more restrictive semantics.
In particular, thread-lock state is tied to the coro at the first call
to newcoro (i.e. iter.Pull). From then on, the invariant is that if the
coro has any thread-lock state *or* a goroutine calling into coroswitch
has any thread-lock state, that the full gamut of thread-lock state must
remain the same as it was when newcoro was called (the full gamut
meaning internal and external lock counts as well as the identity of the
thread that was locked to).
This semantics allows the common cases to be always fast, but comes with
a non-orthogonality caveat. Specifically, when iter.Pull is used in
conjunction with thread-locked goroutines, complex cases (passing next
between goroutines or passing yield between goroutines) are likely to
fail. Simple cases, where any number of iter.Pull iterators are used in
a straightforward way (nested, in series, etc.) from the same
goroutine, will work and will be guaranteed to be fast regardless of
thread-lock state.
This is a compromise for the near-term and we may consider lifting the
restrictions imposed by this CL in the future.
Fixes #65889.
Fixes #65946.
Change-Id: I3fb5791e36a61f5ded50226a229a79d28739b24e
Reviewed-on: https://go-review.googlesource.com/c/go/+/583675
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Austin Clements <austin@google.com>
2024-05-06 15:52:09 +00:00
|
|
|
cmd = testenv.CleanCmdEnv(cmd)
|
|
|
|
|
|
|
|
|
|
// Add the rangefunc GOEXPERIMENT unconditionally since some tests depend on it.
|
|
|
|
|
// TODO(61405): Remove this once it's enabled by default.
|
|
|
|
|
edited := false
|
|
|
|
|
for i := range cmd.Env {
|
|
|
|
|
e := cmd.Env[i]
|
|
|
|
|
if _, vars, ok := strings.Cut(e, "GOEXPERIMENT="); ok {
|
|
|
|
|
cmd.Env[i] = "GOEXPERIMENT=" + vars + ",rangefunc"
|
|
|
|
|
edited = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !edited {
|
|
|
|
|
cmd.Env = append(cmd.Env, "GOEXPERIMENT=rangefunc")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out, err := cmd.CombinedOutput()
|
2021-11-16 22:18:02 -08:00
|
|
|
if err != nil {
|
|
|
|
|
target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out)
|
|
|
|
|
} else {
|
2023-02-02 10:09:39 -05:00
|
|
|
t.Logf("built %v in %v", name, time.Since(start))
|
2021-11-16 22:18:02 -08:00
|
|
|
target.exe = exe
|
|
|
|
|
target.err = nil
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return target.exe, target.err
|
2013-02-20 12:15:02 +04:00
|
|
|
}
|
|
|
|
|
|
2019-09-11 02:26:02 +00:00
|
|
|
func TestVDSO(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
output := runTestProg(t, "testprog", "SignalInVDSO")
|
|
|
|
|
want := "success\n"
|
|
|
|
|
if output != want {
|
2019-09-25 08:48:26 +02:00
|
|
|
t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
|
2019-09-11 02:26:02 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-20 12:15:02 +04:00
|
|
|
func testCrashHandler(t *testing.T, cgo bool) {
|
|
|
|
|
type crashTest struct {
|
|
|
|
|
Cgo bool
|
|
|
|
|
}
|
2015-12-21 10:29:21 -05:00
|
|
|
var output string
|
|
|
|
|
if cgo {
|
|
|
|
|
output = runTestProg(t, "testprogcgo", "Crash")
|
|
|
|
|
} else {
|
|
|
|
|
output = runTestProg(t, "testprog", "Crash")
|
|
|
|
|
}
|
2012-05-30 15:10:54 +10:00
|
|
|
want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
|
2013-08-15 22:34:06 -04:00
|
|
|
if output != want {
|
|
|
|
|
t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
|
2012-05-30 15:10:54 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCrashHandler(t *testing.T) {
|
2013-02-20 12:15:02 +04:00
|
|
|
testCrashHandler(t, false)
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
func testDeadlock(t *testing.T, name string) {
|
2020-10-07 18:29:51 -04:00
|
|
|
// External linking brings in cgo, causing deadlock detection not working.
|
2023-03-01 16:11:07 +00:00
|
|
|
testenv.MustInternalLink(t, false)
|
2020-10-07 18:29:51 -04:00
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", name)
|
2013-02-20 12:15:02 +04:00
|
|
|
want := "fatal error: all goroutines are asleep - deadlock!\n"
|
2013-08-15 22:34:06 -04:00
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
2013-02-20 12:15:02 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSimpleDeadlock(t *testing.T) {
|
2015-12-21 10:29:21 -05:00
|
|
|
testDeadlock(t, "SimpleDeadlock")
|
2013-02-20 12:15:02 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestInitDeadlock(t *testing.T) {
|
2015-12-21 10:29:21 -05:00
|
|
|
testDeadlock(t, "InitDeadlock")
|
2013-02-20 12:15:02 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLockedDeadlock(t *testing.T) {
|
2015-12-21 10:29:21 -05:00
|
|
|
testDeadlock(t, "LockedDeadlock")
|
2013-02-20 12:15:02 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLockedDeadlock2(t *testing.T) {
|
2015-12-21 10:29:21 -05:00
|
|
|
testDeadlock(t, "LockedDeadlock2")
|
2012-05-30 15:10:54 +10:00
|
|
|
}
|
|
|
|
|
|
2013-03-05 09:40:17 +02:00
|
|
|
func TestGoexitDeadlock(t *testing.T) {
|
2020-10-07 18:29:51 -04:00
|
|
|
// External linking brings in cgo, causing deadlock detection not working.
|
2023-03-01 16:11:07 +00:00
|
|
|
testenv.MustInternalLink(t, false)
|
2020-10-07 18:29:51 -04:00
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", "GoexitDeadlock")
|
runtime: crash when func main calls Goexit and all other goroutines exit
This has typically crashed in the past, although usually with
an 'all goroutines are asleep - deadlock!' message that shows
no goroutines (because there aren't any).
Previous discussion at:
https://groups.google.com/d/msg/golang-nuts/uCT_7WxxopQ/BoSBlLFzUTkJ
https://groups.google.com/d/msg/golang-dev/KUojayEr20I/u4fp_Ej5PdUJ
http://golang.org/issue/7711
There is general agreement that runtime.Goexit terminates the
main goroutine, so that main cannot return, so the program does
not exit.
The interpretation that all other goroutines exiting causes an
exit(0) is relatively new and was not part of those discussions.
That is what this CL changes.
Thankfully, even though the exit(0) has been there for a while,
some other accounting bugs made it very difficult to trigger,
so it is reasonable to replace. In particular, see golang.org/issue/7711#c10
for an examination of the behavior across past releases.
Fixes #7711.
LGTM=iant, r
R=golang-codereviews, iant, dvyukov, r
CC=golang-codereviews
https://golang.org/cl/88210044
2014-04-16 13:12:18 -04:00
|
|
|
want := "no goroutines (main called runtime.Goexit) - deadlock!"
|
|
|
|
|
if !strings.Contains(output, want) {
|
|
|
|
|
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
2013-08-15 22:34:06 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestStackOverflow(t *testing.T) {
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", "StackOverflow")
|
2019-11-14 21:34:35 -05:00
|
|
|
want := []string{
|
|
|
|
|
"runtime: goroutine stack exceeds 1474560-byte limit\n",
|
|
|
|
|
"fatal error: stack overflow",
|
|
|
|
|
// information about the current SP and stack bounds
|
|
|
|
|
"runtime: sp=",
|
|
|
|
|
"stack=[",
|
|
|
|
|
}
|
|
|
|
|
if !strings.HasPrefix(output, want[0]) {
|
|
|
|
|
t.Errorf("output does not start with %q", want[0])
|
|
|
|
|
}
|
|
|
|
|
for _, s := range want[1:] {
|
|
|
|
|
if !strings.Contains(output, s) {
|
|
|
|
|
t.Errorf("output does not contain %q", s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if t.Failed() {
|
|
|
|
|
t.Logf("output:\n%s", output)
|
2013-03-05 09:40:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-16 22:25:26 -04:00
|
|
|
func TestThreadExhaustion(t *testing.T) {
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", "ThreadExhaustion")
|
2013-08-16 22:25:26 -04:00
|
|
|
want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-07 20:50:30 +04:00
|
|
|
func TestRecursivePanic(t *testing.T) {
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", "RecursivePanic")
|
2014-03-07 20:50:30 +04:00
|
|
|
want := `wrap: bad
|
|
|
|
|
panic: again
|
|
|
|
|
|
|
|
|
|
`
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-23 17:46:38 -07:00
|
|
|
func TestRecursivePanic2(t *testing.T) {
|
|
|
|
|
output := runTestProg(t, "testprog", "RecursivePanic2")
|
|
|
|
|
want := `first panic
|
|
|
|
|
second panic
|
|
|
|
|
panic: third panic
|
|
|
|
|
|
|
|
|
|
`
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRecursivePanic3(t *testing.T) {
|
|
|
|
|
output := runTestProg(t, "testprog", "RecursivePanic3")
|
|
|
|
|
want := `panic: first panic
|
|
|
|
|
|
|
|
|
|
`
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
runtime: ensure that Goexit cannot be aborted by a recursive panic/recover
When we do a successful recover of a panic, we resume normal execution by
returning from the frame that had the deferred call that did the recover (after
executing any remaining deferred calls in that frame).
However, suppose we have called runtime.Goexit and there is a panic during one of the
deferred calls run by the Goexit. Further assume that there is a deferred call in
the frame of the Goexit or a parent frame that does a recover. Then the recovery
process will actually resume normal execution above the Goexit frame and hence
abort the Goexit. We will not terminate the thread as expected, but continue
running in the frame above the Goexit.
To fix this, we explicitly create a _panic object for a Goexit call. We then
change the "abort" behavior for Goexits, but not panics. After a recovery, if the
top-level panic is actually a Goexit that is marked to be aborted, then we return
to the Goexit defer-processing loop, so that the Goexit is not actually aborted.
Actual code changes are just panic.go, runtime2.go, and funcid.go. Adjusted the
test related to the new Goexit behavior (TestRecoverBeforePanicAfterGoexit) and
added several new tests of aborted panics (whose behavior has not changed).
Fixes #29226
Change-Id: Ib13cb0074f5acc2567a28db7ca6912cfc47eecb5
Reviewed-on: https://go-review.googlesource.com/c/go/+/200081
Run-TryBot: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
2019-10-09 12:18:26 -07:00
|
|
|
func TestRecursivePanic4(t *testing.T) {
|
|
|
|
|
output := runTestProg(t, "testprog", "RecursivePanic4")
|
|
|
|
|
want := `panic: first panic [recovered]
|
|
|
|
|
panic: second panic
|
|
|
|
|
`
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-25 17:51:03 -08:00
|
|
|
func TestRecursivePanic5(t *testing.T) {
|
|
|
|
|
output := runTestProg(t, "testprog", "RecursivePanic5")
|
|
|
|
|
want := `first panic
|
|
|
|
|
second panic
|
|
|
|
|
panic: third panic
|
|
|
|
|
`
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
runtime: crash when func main calls Goexit and all other goroutines exit
This has typically crashed in the past, although usually with
an 'all goroutines are asleep - deadlock!' message that shows
no goroutines (because there aren't any).
Previous discussion at:
https://groups.google.com/d/msg/golang-nuts/uCT_7WxxopQ/BoSBlLFzUTkJ
https://groups.google.com/d/msg/golang-dev/KUojayEr20I/u4fp_Ej5PdUJ
http://golang.org/issue/7711
There is general agreement that runtime.Goexit terminates the
main goroutine, so that main cannot return, so the program does
not exit.
The interpretation that all other goroutines exiting causes an
exit(0) is relatively new and was not part of those discussions.
That is what this CL changes.
Thankfully, even though the exit(0) has been there for a while,
some other accounting bugs made it very difficult to trigger,
so it is reasonable to replace. In particular, see golang.org/issue/7711#c10
for an examination of the behavior across past releases.
Fixes #7711.
LGTM=iant, r
R=golang-codereviews, iant, dvyukov, r
CC=golang-codereviews
https://golang.org/cl/88210044
2014-04-16 13:12:18 -04:00
|
|
|
func TestGoexitCrash(t *testing.T) {
|
2020-10-07 18:29:51 -04:00
|
|
|
// External linking brings in cgo, causing deadlock detection not working.
|
2023-03-01 16:11:07 +00:00
|
|
|
testenv.MustInternalLink(t, false)
|
2020-10-07 18:29:51 -04:00
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", "GoexitExit")
|
runtime: crash when func main calls Goexit and all other goroutines exit
This has typically crashed in the past, although usually with
an 'all goroutines are asleep - deadlock!' message that shows
no goroutines (because there aren't any).
Previous discussion at:
https://groups.google.com/d/msg/golang-nuts/uCT_7WxxopQ/BoSBlLFzUTkJ
https://groups.google.com/d/msg/golang-dev/KUojayEr20I/u4fp_Ej5PdUJ
http://golang.org/issue/7711
There is general agreement that runtime.Goexit terminates the
main goroutine, so that main cannot return, so the program does
not exit.
The interpretation that all other goroutines exiting causes an
exit(0) is relatively new and was not part of those discussions.
That is what this CL changes.
Thankfully, even though the exit(0) has been there for a while,
some other accounting bugs made it very difficult to trigger,
so it is reasonable to replace. In particular, see golang.org/issue/7711#c10
for an examination of the behavior across past releases.
Fixes #7711.
LGTM=iant, r
R=golang-codereviews, iant, dvyukov, r
CC=golang-codereviews
https://golang.org/cl/88210044
2014-04-16 13:12:18 -04:00
|
|
|
want := "no goroutines (main called runtime.Goexit) - deadlock!"
|
|
|
|
|
if !strings.Contains(output, want) {
|
|
|
|
|
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
2014-04-15 19:48:17 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-15 15:09:17 -07:00
|
|
|
func TestGoexitDefer(t *testing.T) {
|
|
|
|
|
c := make(chan struct{})
|
|
|
|
|
go func() {
|
|
|
|
|
defer func() {
|
|
|
|
|
r := recover()
|
|
|
|
|
if r != nil {
|
|
|
|
|
t.Errorf("non-nil recover during Goexit")
|
|
|
|
|
}
|
|
|
|
|
c <- struct{}{}
|
|
|
|
|
}()
|
|
|
|
|
runtime.Goexit()
|
|
|
|
|
}()
|
|
|
|
|
// Note: if the defer fails to run, we will get a deadlock here
|
|
|
|
|
<-c
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-28 00:00:01 -04:00
|
|
|
func TestGoNil(t *testing.T) {
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", "GoNil")
|
2014-05-28 00:00:01 -04:00
|
|
|
want := "go of nil func value"
|
|
|
|
|
if !strings.Contains(output, want) {
|
|
|
|
|
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
func TestMainGoroutineID(t *testing.T) {
|
|
|
|
|
output := runTestProg(t, "testprog", "MainGoroutineID")
|
2014-07-16 12:19:33 +04:00
|
|
|
want := "panic: test\n\ngoroutine 1 [running]:\n"
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-07 15:31:18 +03:00
|
|
|
func TestNoHelperGoroutines(t *testing.T) {
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", "NoHelperGoroutines")
|
2015-02-07 15:31:18 +03:00
|
|
|
matches := regexp.MustCompile(`goroutine [0-9]+ \[`).FindAllStringSubmatch(output, -1)
|
|
|
|
|
if len(matches) != 1 || matches[0][0] != "goroutine 1 [" {
|
|
|
|
|
t.Fatalf("want to see only goroutine 1, see:\n%s", output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-11 12:08:30 -04:00
|
|
|
func TestBreakpoint(t *testing.T) {
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", "Breakpoint")
|
2017-04-19 12:04:18 -04:00
|
|
|
// If runtime.Breakpoint() is inlined, then the stack trace prints
|
|
|
|
|
// "runtime.Breakpoint(...)" instead of "runtime.Breakpoint()".
|
|
|
|
|
want := "runtime.Breakpoint("
|
2014-09-11 12:08:30 -04:00
|
|
|
if !strings.Contains(output, want) {
|
|
|
|
|
t.Fatalf("output:\n%s\n\nwant output containing: %s", output, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-19 16:33:14 -07:00
|
|
|
func TestGoexitInPanic(t *testing.T) {
|
2020-10-07 18:29:51 -04:00
|
|
|
// External linking brings in cgo, causing deadlock detection not working.
|
2023-03-01 16:11:07 +00:00
|
|
|
testenv.MustInternalLink(t, false)
|
2020-10-07 18:29:51 -04:00
|
|
|
|
2014-09-19 16:33:14 -07:00
|
|
|
// see issue 8774: this code used to trigger an infinite recursion
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", "GoexitInPanic")
|
2014-09-19 16:33:14 -07:00
|
|
|
want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-27 17:29:53 -07:00
|
|
|
// Issue 14965: Runtime panics should be of type runtime.Error
|
|
|
|
|
func TestRuntimePanicWithRuntimeError(t *testing.T) {
|
|
|
|
|
testCases := [...]func(){
|
|
|
|
|
0: func() {
|
|
|
|
|
var m map[uint64]bool
|
|
|
|
|
m[1234] = true
|
|
|
|
|
},
|
|
|
|
|
1: func() {
|
|
|
|
|
ch := make(chan struct{})
|
|
|
|
|
close(ch)
|
|
|
|
|
close(ch)
|
|
|
|
|
},
|
|
|
|
|
2: func() {
|
|
|
|
|
var ch = make(chan struct{})
|
|
|
|
|
close(ch)
|
|
|
|
|
ch <- struct{}{}
|
|
|
|
|
},
|
|
|
|
|
3: func() {
|
|
|
|
|
var s = make([]int, 2)
|
|
|
|
|
_ = s[2]
|
|
|
|
|
},
|
|
|
|
|
4: func() {
|
|
|
|
|
n := -1
|
|
|
|
|
_ = make(chan bool, n)
|
|
|
|
|
},
|
|
|
|
|
5: func() {
|
|
|
|
|
close((chan bool)(nil))
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i, fn := range testCases {
|
|
|
|
|
got := panicValue(fn)
|
|
|
|
|
if _, ok := got.(runtime.Error); !ok {
|
|
|
|
|
t.Errorf("test #%d: recovered value %v(type %T) does not implement runtime.Error", i, got, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-01 12:15:45 -05:00
|
|
|
func panicValue(fn func()) (recovered any) {
|
2016-03-27 17:29:53 -07:00
|
|
|
defer func() {
|
|
|
|
|
recovered = recover()
|
|
|
|
|
}()
|
|
|
|
|
fn()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-19 16:33:14 -07:00
|
|
|
func TestPanicAfterGoexit(t *testing.T) {
|
|
|
|
|
// an uncaught panic should still work after goexit
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", "PanicAfterGoexit")
|
2014-09-19 16:33:14 -07:00
|
|
|
want := "panic: hello"
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRecoveredPanicAfterGoexit(t *testing.T) {
|
2020-10-07 18:29:51 -04:00
|
|
|
// External linking brings in cgo, causing deadlock detection not working.
|
2023-03-01 16:11:07 +00:00
|
|
|
testenv.MustInternalLink(t, false)
|
2020-10-07 18:29:51 -04:00
|
|
|
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprog", "RecoveredPanicAfterGoexit")
|
2014-09-19 16:33:14 -07:00
|
|
|
want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRecoverBeforePanicAfterGoexit(t *testing.T) {
|
2020-10-07 18:29:51 -04:00
|
|
|
// External linking brings in cgo, causing deadlock detection not working.
|
2023-03-01 16:11:07 +00:00
|
|
|
testenv.MustInternalLink(t, false)
|
2020-10-07 18:29:51 -04:00
|
|
|
|
runtime: ensure that Goexit cannot be aborted by a recursive panic/recover
When we do a successful recover of a panic, we resume normal execution by
returning from the frame that had the deferred call that did the recover (after
executing any remaining deferred calls in that frame).
However, suppose we have called runtime.Goexit and there is a panic during one of the
deferred calls run by the Goexit. Further assume that there is a deferred call in
the frame of the Goexit or a parent frame that does a recover. Then the recovery
process will actually resume normal execution above the Goexit frame and hence
abort the Goexit. We will not terminate the thread as expected, but continue
running in the frame above the Goexit.
To fix this, we explicitly create a _panic object for a Goexit call. We then
change the "abort" behavior for Goexits, but not panics. After a recovery, if the
top-level panic is actually a Goexit that is marked to be aborted, then we return
to the Goexit defer-processing loop, so that the Goexit is not actually aborted.
Actual code changes are just panic.go, runtime2.go, and funcid.go. Adjusted the
test related to the new Goexit behavior (TestRecoverBeforePanicAfterGoexit) and
added several new tests of aborted panics (whose behavior has not changed).
Fixes #29226
Change-Id: Ib13cb0074f5acc2567a28db7ca6912cfc47eecb5
Reviewed-on: https://go-review.googlesource.com/c/go/+/200081
Run-TryBot: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
2019-10-09 12:18:26 -07:00
|
|
|
t.Parallel()
|
|
|
|
|
output := runTestProg(t, "testprog", "RecoverBeforePanicAfterGoexit")
|
|
|
|
|
want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRecoverBeforePanicAfterGoexit2(t *testing.T) {
|
2020-10-07 18:29:51 -04:00
|
|
|
// External linking brings in cgo, causing deadlock detection not working.
|
2023-03-01 16:11:07 +00:00
|
|
|
testenv.MustInternalLink(t, false)
|
2020-10-07 18:29:51 -04:00
|
|
|
|
runtime: ensure that Goexit cannot be aborted by a recursive panic/recover
When we do a successful recover of a panic, we resume normal execution by
returning from the frame that had the deferred call that did the recover (after
executing any remaining deferred calls in that frame).
However, suppose we have called runtime.Goexit and there is a panic during one of the
deferred calls run by the Goexit. Further assume that there is a deferred call in
the frame of the Goexit or a parent frame that does a recover. Then the recovery
process will actually resume normal execution above the Goexit frame and hence
abort the Goexit. We will not terminate the thread as expected, but continue
running in the frame above the Goexit.
To fix this, we explicitly create a _panic object for a Goexit call. We then
change the "abort" behavior for Goexits, but not panics. After a recovery, if the
top-level panic is actually a Goexit that is marked to be aborted, then we return
to the Goexit defer-processing loop, so that the Goexit is not actually aborted.
Actual code changes are just panic.go, runtime2.go, and funcid.go. Adjusted the
test related to the new Goexit behavior (TestRecoverBeforePanicAfterGoexit) and
added several new tests of aborted panics (whose behavior has not changed).
Fixes #29226
Change-Id: Ib13cb0074f5acc2567a28db7ca6912cfc47eecb5
Reviewed-on: https://go-review.googlesource.com/c/go/+/200081
Run-TryBot: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
2019-10-09 12:18:26 -07:00
|
|
|
t.Parallel()
|
|
|
|
|
output := runTestProg(t, "testprog", "RecoverBeforePanicAfterGoexit2")
|
|
|
|
|
want := "fatal error: no goroutines (main called runtime.Goexit) - deadlock!"
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
2014-09-19 16:33:14 -07:00
|
|
|
}
|
2015-01-13 20:12:50 +03:00
|
|
|
|
|
|
|
|
func TestNetpollDeadlock(t *testing.T) {
|
2016-11-04 05:28:01 +00:00
|
|
|
t.Parallel()
|
2015-12-21 10:29:21 -05:00
|
|
|
output := runTestProg(t, "testprognet", "NetpollDeadlock")
|
2015-01-13 20:12:50 +03:00
|
|
|
want := "done\n"
|
|
|
|
|
if !strings.HasSuffix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-02-12 10:33:51 -05:00
|
|
|
|
|
|
|
|
func TestPanicTraceback(t *testing.T) {
|
2016-11-04 05:28:01 +00:00
|
|
|
t.Parallel()
|
2016-02-12 10:33:51 -05:00
|
|
|
output := runTestProg(t, "testprog", "PanicTraceback")
|
2019-09-23 17:46:38 -07:00
|
|
|
want := "panic: hello\n\tpanic: panic pt2\n\tpanic: panic pt1\n"
|
2016-02-12 10:33:51 -05:00
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check functions in the traceback.
|
runtime: do not print runtime panic frame at top of user stack
The expected default behavior (no explicit GOTRACEBACK setting)
is for the stack trace to start in user code, eliding unnecessary runtime
frames that led up to the actual trace printing code. The idea was that
the first line number printed was the one that crashed.
For #5832 we added code to show 'panic' frames so that if code panics
and then starts running defers and then we trace from there, the panic
frame can help explain why the code seems to have made a call not
present in the code. But that's only needed for panics between two different
call frames, not the panic at the very top of the stack trace.
Fix the fix to again elide the runtime code at the very top of the stack trace.
Simple panic:
package main
func main() {
var x []int
println(x[1])
}
Before this CL:
panic: runtime error: index out of range
goroutine 1 [running]:
panic(0x1056980, 0x1091bf0)
/Users/rsc/go/src/runtime/panic.go:531 +0x1cf
main.main()
/tmp/x.go:5 +0x5
After this CL:
panic: runtime error: index out of range
goroutine 1 [running]:
main.main()
/tmp/x.go:5 +0x5
Panic inside defer triggered by panic:
package main
func main() {
var x []int
defer func() {
println(x[1])
}()
println(x[2])
}
Before this CL:
panic: runtime error: index out of range
panic: runtime error: index out of range
goroutine 1 [running]:
panic(0x1056aa0, 0x1091bf0)
/Users/rsc/go/src/runtime/panic.go:531 +0x1cf
main.main.func1(0x0, 0x0, 0x0)
/tmp/y.go:6 +0x62
panic(0x1056aa0, 0x1091bf0)
/Users/rsc/go/src/runtime/panic.go:489 +0x2cf
main.main()
/tmp/y.go:8 +0x59
The middle panic is important: it explains why main.main ended up calling main.main.func1 on a line that looks like a call to println. The top panic is noise.
After this CL:
panic: runtime error: index out of range
panic: runtime error: index out of range
goroutine 1 [running]:
main.main.func1(0x0, 0x0, 0x0)
/tmp/y.go:6 +0x62
panic(0x1056ac0, 0x1091bf0)
/Users/rsc/go/src/runtime/panic.go:489 +0x2cf
main.main()
/tmp/y.go:8 +0x59
Fixes #17901.
Change-Id: Id6d7c76373f7a658a537a39ca32b7dc23e1e76aa
Reviewed-on: https://go-review.googlesource.com/33165
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2016-11-12 23:01:37 -05:00
|
|
|
fns := []string{"main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"}
|
2016-02-12 10:33:51 -05:00
|
|
|
for _, fn := range fns {
|
|
|
|
|
re := regexp.MustCompile(`(?m)^` + regexp.QuoteMeta(fn) + `\(.*\n`)
|
|
|
|
|
idx := re.FindStringIndex(output)
|
|
|
|
|
if idx == nil {
|
|
|
|
|
t.Fatalf("expected %q function in traceback:\n%s", fn, output)
|
|
|
|
|
}
|
|
|
|
|
output = output[idx[1]:]
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-02-21 13:56:08 -05:00
|
|
|
|
|
|
|
|
func testPanicDeadlock(t *testing.T, name string, want string) {
|
|
|
|
|
// test issue 14432
|
|
|
|
|
output := runTestProg(t, "testprog", name)
|
|
|
|
|
if !strings.HasPrefix(output, want) {
|
|
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPanicDeadlockGosched(t *testing.T) {
|
|
|
|
|
testPanicDeadlock(t, "GoschedInPanic", "panic: errorThatGosched\n\n")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPanicDeadlockSyscall(t *testing.T) {
|
|
|
|
|
testPanicDeadlock(t, "SyscallInPanic", "1\n2\npanic: 3\n\n")
|
|
|
|
|
}
|
2016-06-20 14:00:58 -07:00
|
|
|
|
2016-10-04 21:15:42 -07:00
|
|
|
func TestPanicLoop(t *testing.T) {
|
|
|
|
|
output := runTestProg(t, "testprog", "PanicLoop")
|
|
|
|
|
if want := "panic while printing panic value"; !strings.Contains(output, want) {
|
|
|
|
|
t.Errorf("output does not contain %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-20 14:00:58 -07:00
|
|
|
func TestMemPprof(t *testing.T) {
|
|
|
|
|
testenv.MustHaveGoRun(t)
|
|
|
|
|
|
|
|
|
|
exe, err := buildTestProg(t, "testprog")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-13 15:04:16 +02:00
|
|
|
got, err := testenv.CleanCmdEnv(exec.Command(exe, "MemProf")).CombinedOutput()
|
2016-06-20 14:00:58 -07:00
|
|
|
if err != nil {
|
2023-07-05 16:03:55 -04:00
|
|
|
t.Fatalf("testprog failed: %s, output:\n%s", err, got)
|
2016-06-20 14:00:58 -07:00
|
|
|
}
|
|
|
|
|
fn := strings.TrimSpace(string(got))
|
|
|
|
|
defer os.Remove(fn)
|
|
|
|
|
|
2017-02-17 15:27:12 -05:00
|
|
|
for try := 0; try < 2; try++ {
|
2017-09-13 15:04:16 +02:00
|
|
|
cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-alloc_space", "-top"))
|
2017-02-17 15:27:12 -05:00
|
|
|
// Check that pprof works both with and without explicit executable on command line.
|
|
|
|
|
if try == 0 {
|
|
|
|
|
cmd.Args = append(cmd.Args, exe, fn)
|
|
|
|
|
} else {
|
|
|
|
|
cmd.Args = append(cmd.Args, fn)
|
|
|
|
|
}
|
|
|
|
|
found := false
|
|
|
|
|
for i, e := range cmd.Env {
|
|
|
|
|
if strings.HasPrefix(e, "PPROF_TMPDIR=") {
|
|
|
|
|
cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir()
|
|
|
|
|
found = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !found {
|
|
|
|
|
cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
|
2016-06-20 14:00:58 -07:00
|
|
|
}
|
|
|
|
|
|
2017-02-17 15:27:12 -05:00
|
|
|
top, err := cmd.CombinedOutput()
|
|
|
|
|
t.Logf("%s:\n%s", cmd.Args, top)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
} else if !bytes.Contains(top, []byte("MemProf")) {
|
|
|
|
|
t.Error("missing MemProf in pprof output")
|
|
|
|
|
}
|
2016-06-20 14:00:58 -07:00
|
|
|
}
|
|
|
|
|
}
|
2016-07-06 15:02:49 -07:00
|
|
|
|
|
|
|
|
var concurrentMapTest = flag.Bool("run_concurrent_map_tests", false, "also run flaky concurrent map tests")
|
|
|
|
|
|
|
|
|
|
func TestConcurrentMapWrites(t *testing.T) {
|
|
|
|
|
if !*concurrentMapTest {
|
|
|
|
|
t.Skip("skipping without -run_concurrent_map_tests")
|
|
|
|
|
}
|
|
|
|
|
testenv.MustHaveGoRun(t)
|
|
|
|
|
output := runTestProg(t, "testprog", "concurrentMapWrites")
|
2024-09-24 15:11:54 -07:00
|
|
|
want := "fatal error: concurrent map writes\n"
|
2024-09-12 10:44:38 -04:00
|
|
|
// Concurrent writes can corrupt the map in a way that we
|
|
|
|
|
// detect with a separate throw.
|
|
|
|
|
want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
|
|
|
|
|
if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
|
2016-07-06 15:02:49 -07:00
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
func TestConcurrentMapReadWrite(t *testing.T) {
|
|
|
|
|
if !*concurrentMapTest {
|
|
|
|
|
t.Skip("skipping without -run_concurrent_map_tests")
|
|
|
|
|
}
|
|
|
|
|
testenv.MustHaveGoRun(t)
|
|
|
|
|
output := runTestProg(t, "testprog", "concurrentMapReadWrite")
|
2024-09-24 15:11:54 -07:00
|
|
|
want := "fatal error: concurrent map read and map write\n"
|
2024-09-12 10:44:38 -04:00
|
|
|
// Concurrent writes can corrupt the map in a way that we
|
|
|
|
|
// detect with a separate throw.
|
|
|
|
|
want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
|
|
|
|
|
if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
|
2016-07-06 15:02:49 -07:00
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
func TestConcurrentMapIterateWrite(t *testing.T) {
|
|
|
|
|
if !*concurrentMapTest {
|
|
|
|
|
t.Skip("skipping without -run_concurrent_map_tests")
|
|
|
|
|
}
|
|
|
|
|
testenv.MustHaveGoRun(t)
|
|
|
|
|
output := runTestProg(t, "testprog", "concurrentMapIterateWrite")
|
2024-09-24 15:11:54 -07:00
|
|
|
want := "fatal error: concurrent map iteration and map write\n"
|
2024-09-12 10:44:38 -04:00
|
|
|
// Concurrent writes can corrupt the map in a way that we
|
|
|
|
|
// detect with a separate throw.
|
|
|
|
|
want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
|
|
|
|
|
if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
|
2016-07-06 15:02:49 -07:00
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-17 16:08:36 -05:00
|
|
|
|
2024-09-24 15:11:54 -07:00
|
|
|
func TestConcurrentMapWritesIssue69447(t *testing.T) {
|
|
|
|
|
testenv.MustHaveGoRun(t)
|
|
|
|
|
exe, err := buildTestProg(t, "testprog")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
for i := 0; i < 200; i++ {
|
|
|
|
|
output := runBuiltTestProg(t, exe, "concurrentMapWrites")
|
|
|
|
|
if output == "" {
|
|
|
|
|
// If we didn't detect an error, that's ok.
|
|
|
|
|
// This case makes this test not flaky like
|
|
|
|
|
// the other ones above.
|
|
|
|
|
// (More correctly, this case makes this test flaky
|
|
|
|
|
// in the other direction, in that it might not
|
|
|
|
|
// detect a problem even if there is one.)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
want := "fatal error: concurrent map writes\n"
|
2024-09-12 10:44:38 -04:00
|
|
|
// Concurrent writes can corrupt the map in a way that we
|
|
|
|
|
// detect with a separate throw.
|
|
|
|
|
want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
|
|
|
|
|
if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
|
2024-09-24 15:11:54 -07:00
|
|
|
t.Fatalf("output does not start with %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-17 16:08:36 -05:00
|
|
|
type point struct {
|
|
|
|
|
x, y *int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *point) negate() {
|
|
|
|
|
*p.x = *p.x * -1
|
|
|
|
|
*p.y = *p.y * -1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test for issue #10152.
|
|
|
|
|
func TestPanicInlined(t *testing.T) {
|
|
|
|
|
defer func() {
|
|
|
|
|
r := recover()
|
|
|
|
|
if r == nil {
|
|
|
|
|
t.Fatalf("recover failed")
|
|
|
|
|
}
|
|
|
|
|
buf := make([]byte, 2048)
|
|
|
|
|
n := runtime.Stack(buf, false)
|
|
|
|
|
buf = buf[:n]
|
|
|
|
|
if !bytes.Contains(buf, []byte("(*point).negate(")) {
|
|
|
|
|
t.Fatalf("expecting stack trace to contain call to (*point).negate()")
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
pt := new(point)
|
|
|
|
|
pt.negate()
|
|
|
|
|
}
|
2017-04-19 07:32:34 -07:00
|
|
|
|
|
|
|
|
// Test for issues #3934 and #20018.
|
|
|
|
|
// We want to delay exiting until a panic print is complete.
|
|
|
|
|
func TestPanicRace(t *testing.T) {
|
|
|
|
|
testenv.MustHaveGoRun(t)
|
|
|
|
|
|
|
|
|
|
exe, err := buildTestProg(t, "testprog")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-06 17:31:57 -07:00
|
|
|
// The test is intentionally racy, and in my testing does not
|
|
|
|
|
// produce the expected output about 0.05% of the time.
|
|
|
|
|
// So run the program in a loop and only fail the test if we
|
|
|
|
|
// get the wrong output ten times in a row.
|
|
|
|
|
const tries = 10
|
|
|
|
|
retry:
|
|
|
|
|
for i := 0; i < tries; i++ {
|
2017-09-13 15:04:16 +02:00
|
|
|
got, err := testenv.CleanCmdEnv(exec.Command(exe, "PanicRace")).CombinedOutput()
|
2017-06-06 17:31:57 -07:00
|
|
|
if err == nil {
|
|
|
|
|
t.Logf("try %d: program exited successfully, should have failed", i+1)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2017-04-19 07:32:34 -07:00
|
|
|
|
2017-06-06 17:31:57 -07:00
|
|
|
if i > 0 {
|
|
|
|
|
t.Logf("try %d:\n", i+1)
|
|
|
|
|
}
|
|
|
|
|
t.Logf("%s\n", got)
|
2017-04-19 07:32:34 -07:00
|
|
|
|
2017-06-06 17:31:57 -07:00
|
|
|
wants := []string{
|
|
|
|
|
"panic: crash",
|
|
|
|
|
"PanicRace",
|
|
|
|
|
"created by ",
|
|
|
|
|
}
|
|
|
|
|
for _, want := range wants {
|
|
|
|
|
if !bytes.Contains(got, []byte(want)) {
|
|
|
|
|
t.Logf("did not find expected string %q", want)
|
|
|
|
|
continue retry
|
|
|
|
|
}
|
2017-04-19 07:32:34 -07:00
|
|
|
}
|
2017-06-06 17:31:57 -07:00
|
|
|
|
|
|
|
|
// Test generated expected output.
|
|
|
|
|
return
|
2017-04-19 07:32:34 -07:00
|
|
|
}
|
2017-06-06 17:31:57 -07:00
|
|
|
t.Errorf("test ran %d times without producing expected output", tries)
|
2017-04-19 07:32:34 -07:00
|
|
|
}
|
2018-01-22 14:53:36 -05:00
|
|
|
|
|
|
|
|
func TestBadTraceback(t *testing.T) {
|
|
|
|
|
output := runTestProg(t, "testprog", "BadTraceback")
|
|
|
|
|
for _, want := range []string{
|
2022-03-04 11:17:43 -05:00
|
|
|
"unexpected return pc",
|
2018-01-22 14:53:36 -05:00
|
|
|
"called from 0xbad",
|
|
|
|
|
"00000bad", // Smashed LR in hex dump
|
|
|
|
|
"<main.badLR", // Symbolization in hex dump (badLR1 or badLR2)
|
|
|
|
|
} {
|
|
|
|
|
if !strings.Contains(output, want) {
|
|
|
|
|
t.Errorf("output does not contain %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-02-26 14:03:47 -08:00
|
|
|
|
|
|
|
|
func TestTimePprof(t *testing.T) {
|
2021-11-23 16:30:17 -08:00
|
|
|
// This test is unreliable on any system in which nanotime
|
|
|
|
|
// calls into libc.
|
|
|
|
|
switch runtime.GOOS {
|
2021-11-24 15:37:21 +01:00
|
|
|
case "aix", "darwin", "illumos", "openbsd", "solaris":
|
2021-11-23 16:30:17 -08:00
|
|
|
t.Skipf("skipping on %s because nanotime calls libc", runtime.GOOS)
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-01 16:06:03 -04:00
|
|
|
// Pass GOTRACEBACK for issue #41120 to try to get more
|
|
|
|
|
// information on timeout.
|
|
|
|
|
fn := runTestProg(t, "testprog", "TimeProf", "GOTRACEBACK=crash")
|
2018-02-26 14:03:47 -08:00
|
|
|
fn = strings.TrimSpace(fn)
|
|
|
|
|
defer os.Remove(fn)
|
|
|
|
|
|
|
|
|
|
cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-top", "-nodecount=1", fn))
|
|
|
|
|
cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
|
|
|
|
|
top, err := cmd.CombinedOutput()
|
|
|
|
|
t.Logf("%s", top)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Error(err)
|
|
|
|
|
} else if bytes.Contains(top, []byte("ExternalCode")) {
|
|
|
|
|
t.Error("profiler refers to ExternalCode")
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-18 17:33:04 -05:00
|
|
|
|
|
|
|
|
// Test that runtime.abort does so.
|
|
|
|
|
func TestAbort(t *testing.T) {
|
2018-07-06 09:50:05 -04:00
|
|
|
// Pass GOTRACEBACK to ensure we get runtime frames.
|
|
|
|
|
output := runTestProg(t, "testprog", "Abort", "GOTRACEBACK=system")
|
2018-01-18 17:33:04 -05:00
|
|
|
if want := "runtime.abort"; !strings.Contains(output, want) {
|
|
|
|
|
t.Errorf("output does not contain %q:\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
if strings.Contains(output, "BAD") {
|
|
|
|
|
t.Errorf("output contains BAD:\n%s", output)
|
|
|
|
|
}
|
2018-07-08 00:21:27 -04:00
|
|
|
// Check that it's a signal traceback.
|
|
|
|
|
want := "PC="
|
|
|
|
|
// For systems that use a breakpoint, check specifically for that.
|
|
|
|
|
switch runtime.GOARCH {
|
|
|
|
|
case "386", "amd64":
|
|
|
|
|
switch runtime.GOOS {
|
|
|
|
|
case "plan9":
|
|
|
|
|
want = "sys: breakpoint"
|
|
|
|
|
case "windows":
|
|
|
|
|
want = "Exception 0x80000003"
|
|
|
|
|
default:
|
|
|
|
|
want = "SIGTRAP"
|
|
|
|
|
}
|
2018-07-06 09:50:05 -04:00
|
|
|
}
|
|
|
|
|
if !strings.Contains(output, want) {
|
|
|
|
|
t.Errorf("output does not contain %q:\n%s", want, output)
|
2018-01-18 17:33:04 -05:00
|
|
|
}
|
|
|
|
|
}
|
2018-06-28 16:45:28 -07:00
|
|
|
|
|
|
|
|
// For TestRuntimePanic: test a panic in the runtime package without
|
|
|
|
|
// involving the testing harness.
|
|
|
|
|
func init() {
|
|
|
|
|
if os.Getenv("GO_TEST_RUNTIME_PANIC") == "1" {
|
|
|
|
|
defer func() {
|
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
|
// We expect to crash, so exit 0
|
|
|
|
|
// to indicate failure.
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
runtime.PanicForTesting(nil, 1)
|
|
|
|
|
// We expect to crash, so exit 0 to indicate failure.
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
2023-07-03 13:49:26 -07:00
|
|
|
if os.Getenv("GO_TEST_RUNTIME_NPE_READMEMSTATS") == "1" {
|
|
|
|
|
runtime.ReadMemStats(nil)
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
|
|
|
|
if os.Getenv("GO_TEST_RUNTIME_NPE_FUNCMETHOD") == "1" {
|
|
|
|
|
var f *runtime.Func
|
|
|
|
|
_ = f.Entry()
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-28 16:45:28 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRuntimePanic(t *testing.T) {
|
|
|
|
|
testenv.MustHaveExec(t)
|
2023-09-03 14:23:02 -04:00
|
|
|
cmd := testenv.CleanCmdEnv(exec.Command(os.Args[0], "-test.run=^TestRuntimePanic$"))
|
2018-06-28 16:45:28 -07:00
|
|
|
cmd.Env = append(cmd.Env, "GO_TEST_RUNTIME_PANIC=1")
|
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
|
t.Logf("%s", out)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Error("child process did not fail")
|
|
|
|
|
} else if want := "runtime.unexportedPanicForTesting"; !bytes.Contains(out, []byte(want)) {
|
|
|
|
|
t.Errorf("output did not contain expected string %q", want)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-25 18:00:43 -04:00
|
|
|
|
2023-07-03 13:49:26 -07:00
|
|
|
func TestTracebackRuntimeFunction(t *testing.T) {
|
|
|
|
|
testenv.MustHaveExec(t)
|
|
|
|
|
cmd := testenv.CleanCmdEnv(exec.Command(os.Args[0], "-test.run=TestTracebackRuntimeFunction"))
|
|
|
|
|
cmd.Env = append(cmd.Env, "GO_TEST_RUNTIME_NPE_READMEMSTATS=1")
|
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
|
t.Logf("%s", out)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Error("child process did not fail")
|
|
|
|
|
} else if want := "runtime.ReadMemStats"; !bytes.Contains(out, []byte(want)) {
|
|
|
|
|
t.Errorf("output did not contain expected string %q", want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTracebackRuntimeMethod(t *testing.T) {
|
|
|
|
|
testenv.MustHaveExec(t)
|
|
|
|
|
cmd := testenv.CleanCmdEnv(exec.Command(os.Args[0], "-test.run=TestTracebackRuntimeMethod"))
|
|
|
|
|
cmd.Env = append(cmd.Env, "GO_TEST_RUNTIME_NPE_FUNCMETHOD=1")
|
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
|
t.Logf("%s", out)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Error("child process did not fail")
|
|
|
|
|
} else if want := "runtime.(*Func).Entry"; !bytes.Contains(out, []byte(want)) {
|
|
|
|
|
t.Errorf("output did not contain expected string %q", want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-25 18:00:43 -04:00
|
|
|
// Test that g0 stack overflows are handled gracefully.
|
|
|
|
|
func TestG0StackOverflow(t *testing.T) {
|
|
|
|
|
testenv.MustHaveExec(t)
|
|
|
|
|
|
2023-09-15 19:57:36 -04:00
|
|
|
if runtime.GOOS == "ios" {
|
|
|
|
|
testenv.SkipFlaky(t, 62671)
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-25 18:00:43 -04:00
|
|
|
if os.Getenv("TEST_G0_STACK_OVERFLOW") != "1" {
|
2023-10-31 16:26:52 -04:00
|
|
|
cmd := testenv.CleanCmdEnv(testenv.Command(t, os.Args[0], "-test.run=^TestG0StackOverflow$", "-test.v"))
|
2018-06-25 18:00:43 -04:00
|
|
|
cmd.Env = append(cmd.Env, "TEST_G0_STACK_OVERFLOW=1")
|
|
|
|
|
out, err := cmd.CombinedOutput()
|
2023-11-20 16:33:29 -05:00
|
|
|
t.Logf("output:\n%s", out)
|
2018-06-25 18:00:43 -04:00
|
|
|
// Don't check err since it's expected to crash.
|
|
|
|
|
if n := strings.Count(string(out), "morestack on g0\n"); n != 1 {
|
|
|
|
|
t.Fatalf("%s\n(exit status %v)", out, err)
|
|
|
|
|
}
|
2023-11-01 01:34:33 +11:00
|
|
|
if runtime.CrashStackImplemented {
|
2023-08-28 14:57:29 -04:00
|
|
|
// check for a stack trace
|
|
|
|
|
want := "runtime.stackOverflow"
|
|
|
|
|
if n := strings.Count(string(out), want); n < 5 {
|
|
|
|
|
t.Errorf("output does not contain %q at least 5 times:\n%s", want, out)
|
|
|
|
|
}
|
|
|
|
|
return // it's not a signal-style traceback
|
|
|
|
|
}
|
2018-06-25 18:00:43 -04:00
|
|
|
// Check that it's a signal-style traceback.
|
|
|
|
|
if runtime.GOOS != "windows" {
|
|
|
|
|
if want := "PC="; !strings.Contains(string(out), want) {
|
|
|
|
|
t.Errorf("output does not contain %q:\n%s", want, out)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
runtime.G0StackOverflow()
|
|
|
|
|
}
|
2019-02-09 23:31:59 -05:00
|
|
|
|
2024-02-08 08:56:33 -08:00
|
|
|
// For TestCrashWhileTracing: test a panic without involving the testing
|
|
|
|
|
// harness, as we rely on stdout only containing trace output.
|
|
|
|
|
func init() {
|
|
|
|
|
if os.Getenv("TEST_CRASH_WHILE_TRACING") == "1" {
|
|
|
|
|
trace.Start(os.Stdout)
|
|
|
|
|
trace.Log(context.Background(), "xyzzy-cat", "xyzzy-msg")
|
|
|
|
|
panic("yzzyx")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCrashWhileTracing(t *testing.T) {
|
|
|
|
|
testenv.MustHaveExec(t)
|
|
|
|
|
|
|
|
|
|
cmd := testenv.CleanCmdEnv(testenv.Command(t, os.Args[0]))
|
|
|
|
|
cmd.Env = append(cmd.Env, "TEST_CRASH_WHILE_TRACING=1")
|
|
|
|
|
stdOut, err := cmd.StdoutPipe()
|
|
|
|
|
var errOut bytes.Buffer
|
|
|
|
|
cmd.Stderr = &errOut
|
|
|
|
|
|
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
|
t.Fatalf("could not start subprocess: %v", err)
|
|
|
|
|
}
|
2024-05-09 10:45:01 -04:00
|
|
|
r, err := traceparse.NewReader(stdOut)
|
2024-02-08 08:56:33 -08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("could not create trace.NewReader: %v", err)
|
|
|
|
|
}
|
2024-03-15 22:15:37 +00:00
|
|
|
var seen, seenSync bool
|
2024-02-08 08:56:33 -08:00
|
|
|
i := 1
|
|
|
|
|
loop:
|
|
|
|
|
for ; ; i++ {
|
|
|
|
|
ev, err := r.ReadEvent()
|
|
|
|
|
if err != nil {
|
2024-03-15 22:15:37 +00:00
|
|
|
// We may have a broken tail to the trace -- that's OK.
|
|
|
|
|
// We'll make sure we saw at least one complete generation.
|
2024-02-08 08:56:33 -08:00
|
|
|
if err != io.EOF {
|
2024-03-15 22:15:37 +00:00
|
|
|
t.Logf("error at event %d: %v", i, err)
|
2024-02-08 08:56:33 -08:00
|
|
|
}
|
|
|
|
|
break loop
|
|
|
|
|
}
|
|
|
|
|
switch ev.Kind() {
|
2024-05-09 10:45:01 -04:00
|
|
|
case traceparse.EventSync:
|
2024-03-15 22:15:37 +00:00
|
|
|
seenSync = true
|
2024-05-09 10:45:01 -04:00
|
|
|
case traceparse.EventLog:
|
2024-02-08 08:56:33 -08:00
|
|
|
v := ev.Log()
|
|
|
|
|
if v.Category == "xyzzy-cat" && v.Message == "xyzzy-msg" {
|
|
|
|
|
// Should we already stop reading here? More events may come, but
|
|
|
|
|
// we're not guaranteeing a fully unbroken trace until the last
|
|
|
|
|
// byte...
|
|
|
|
|
seen = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err := cmd.Wait(); err == nil {
|
|
|
|
|
t.Error("the process should have panicked")
|
|
|
|
|
}
|
2024-03-15 22:15:37 +00:00
|
|
|
if !seenSync {
|
|
|
|
|
t.Errorf("expected at least one full generation to have been emitted before the trace was considered broken")
|
|
|
|
|
}
|
2024-02-08 08:56:33 -08:00
|
|
|
if !seen {
|
|
|
|
|
t.Errorf("expected one matching log event matching, but none of the %d received trace events match", i)
|
|
|
|
|
}
|
|
|
|
|
t.Logf("stderr output:\n%s", errOut.String())
|
|
|
|
|
needle := "yzzyx\n"
|
|
|
|
|
if n := strings.Count(errOut.String(), needle); n != 1 {
|
|
|
|
|
t.Fatalf("did not find expected panic message %q\n(exit status %v)", needle, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-09 23:31:59 -05:00
|
|
|
// Test that panic message is not clobbered.
|
|
|
|
|
// See issue 30150.
|
|
|
|
|
func TestDoublePanic(t *testing.T) {
|
|
|
|
|
output := runTestProg(t, "testprog", "DoublePanic", "GODEBUG=clobberfree=1")
|
|
|
|
|
wants := []string{"panic: XXX", "panic: YYY"}
|
|
|
|
|
for _, want := range wants {
|
|
|
|
|
if !strings.Contains(output, want) {
|
|
|
|
|
t.Errorf("output:\n%s\n\nwant output containing: %s", output, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-12 17:46:36 +08:00
|
|
|
|
|
|
|
|
// Test that panic while panicking discards error message
|
|
|
|
|
// See issue 52257
|
|
|
|
|
func TestPanicWhilePanicking(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
Want string
|
|
|
|
|
Func string
|
|
|
|
|
}{
|
|
|
|
|
{
|
2024-04-23 12:44:54 -04:00
|
|
|
"panic while printing panic value: important multi-line\n\terror message",
|
2022-04-12 17:46:36 +08:00
|
|
|
"ErrorPanic",
|
|
|
|
|
},
|
|
|
|
|
{
|
2024-04-23 12:44:54 -04:00
|
|
|
"panic while printing panic value: important multi-line\n\tstringer message",
|
2022-04-12 17:46:36 +08:00
|
|
|
"StringerPanic",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"panic while printing panic value: type",
|
|
|
|
|
"DoubleErrorPanic",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"panic while printing panic value: type",
|
|
|
|
|
"DoubleStringerPanic",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"panic while printing panic value: type",
|
|
|
|
|
"CircularPanic",
|
|
|
|
|
},
|
|
|
|
|
{
|
2024-04-23 12:44:54 -04:00
|
|
|
"important multi-line\n\tstring message",
|
2022-04-12 17:46:36 +08:00
|
|
|
"StringPanic",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"nil",
|
|
|
|
|
"NilPanic",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, x := range tests {
|
|
|
|
|
output := runTestProg(t, "testprog", x.Func)
|
|
|
|
|
if !strings.Contains(output, x.Want) {
|
|
|
|
|
t.Errorf("output does not contain %q:\n%s", x.Want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-08 16:07:05 +00:00
|
|
|
|
|
|
|
|
func TestPanicOnUnsafeSlice(t *testing.T) {
|
|
|
|
|
output := runTestProg(t, "testprog", "panicOnNilAndEleSizeIsZero")
|
|
|
|
|
want := "panic: runtime error: unsafe.Slice: ptr is nil and len is not zero"
|
|
|
|
|
if !strings.Contains(output, want) {
|
|
|
|
|
t.Errorf("output does not contain %q:\n%s", want, output)
|
|
|
|
|
}
|
2022-08-09 17:10:08 -04:00
|
|
|
}
|
2023-06-15 17:41:19 -07:00
|
|
|
|
|
|
|
|
func TestNetpollWaiters(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
output := runTestProg(t, "testprognet", "NetpollWaiters")
|
|
|
|
|
want := "OK\n"
|
|
|
|
|
if output != want {
|
|
|
|
|
t.Fatalf("output is not %q\n%s", want, output)
|
|
|
|
|
}
|
|
|
|
|
}
|