aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/runtime1.go
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2023-01-12 09:30:38 -0500
committerGopher Robot <gobot@golang.org>2023-01-19 22:21:50 +0000
commitaa51c40b1cc62d53603d7b7aea3232969aa40afe (patch)
tree7a148203657f031a08e5351b3d317c942ce22207 /src/runtime/runtime1.go
parent8d71ae8c775129161fda64e77b2de2276e7362e2 (diff)
downloadgo-aa51c40b1cc62d53603d7b7aea3232969aa40afe.tar.xz
runtime: replace panic(nil) with panic(new(runtime.PanicNilError))
Long ago we decided that panic(nil) was too unlikely to bother making a special case for purposes of recover. Unfortunately, it has turned out not to be a special case. There are many examples of code in the Go ecosystem where an author has written panic(nil) because they want to panic and don't care about the panic value. Using panic(nil) in this case has the unfortunate behavior of making recover behave as though the goroutine isn't panicking. As a result, code like: func f() { defer func() { if err := recover(); err != nil { log.Fatalf("panicked! %v", err) } }() call1() call2() } looks like it guarantees that call2 has been run any time f returns, but that turns out not to be strictly true. If call1 does panic(nil), then f returns "successfully", having recovered the panic, but without calling call2. Instead you have to write something like: func f() { done := false defer func() { if err := recover(); !done { log.Fatalf("panicked! %v", err) } }() call1() call2() done = true } which defeats nearly the whole point of recover. No one does this, with the result that almost all uses of recover are subtly broken. One specific broken use along these lines is in net/http, which recovers from panics in handlers and sends back an HTTP error. Users discovered in the early days of Go that panic(nil) was a convenient way to jump out of a handler up to the serving loop without sending back an HTTP error. This was a bug, not a feature. Go 1.8 added panic(http.ErrAbortHandler) as a better way to access the feature. Any lingering code that uses panic(nil) to abort an HTTP handler without a failure message should be changed to use http.ErrAbortHandler. Programs that need the old, unintended behavior from net/http or other packages can set GODEBUG=panicnil=1 to stop the run-time error. Uses of recover that want to detect panic(nil) in new programs can check for recover returning a value of type *runtime.PanicNilError. Because the new GODEBUG is used inside the runtime, we can't import internal/godebug, so there is some new machinery to cross-connect those in this CL, to allow a mutable GODEBUG setting. That won't be necessary if we add any other mutable GODEBUG settings in the future. The CL also corrects the handling of defaulted GODEBUG values in the runtime, for #56986. Fixes #25448. Change-Id: I2b39c7e83e4f7aa308777dabf2edae54773e03f5 Reviewed-on: https://go-review.googlesource.com/c/go/+/461956 Reviewed-by: Robert Griesemer <gri@google.com> Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Auto-Submit: Russ Cox <rsc@golang.org>
Diffstat (limited to 'src/runtime/runtime1.go')
-rw-r--r--src/runtime/runtime1.go159
1 files changed, 118 insertions, 41 deletions
diff --git a/src/runtime/runtime1.go b/src/runtime/runtime1.go
index 277f18a5a6..5f9555e404 100644
--- a/src/runtime/runtime1.go
+++ b/src/runtime/runtime1.go
@@ -296,8 +296,10 @@ func check() {
}
type dbgVar struct {
- name string
- value *int32
+ name string
+ value *int32 // for variables that can only be set at startup
+ atomic *atomic.Int32 // for variables that can be changed during execution
+ def int32 // default value (ideally zero)
}
// Holds variables parsed from GODEBUG env var,
@@ -330,32 +332,33 @@ var debug struct {
allocfreetrace int32
inittrace int32
sbrk int32
-}
-var dbgvars = []dbgVar{
- {"allocfreetrace", &debug.allocfreetrace},
- {"clobberfree", &debug.clobberfree},
- {"cgocheck", &debug.cgocheck},
- {"efence", &debug.efence},
- {"gccheckmark", &debug.gccheckmark},
- {"gcpacertrace", &debug.gcpacertrace},
- {"gcshrinkstackoff", &debug.gcshrinkstackoff},
- {"gcstoptheworld", &debug.gcstoptheworld},
- {"gctrace", &debug.gctrace},
- {"invalidptr", &debug.invalidptr},
- {"madvdontneed", &debug.madvdontneed},
- {"sbrk", &debug.sbrk},
- {"scavtrace", &debug.scavtrace},
- {"scheddetail", &debug.scheddetail},
- {"schedtrace", &debug.schedtrace},
- {"tracebackancestors", &debug.tracebackancestors},
- {"asyncpreemptoff", &debug.asyncpreemptoff},
- {"inittrace", &debug.inittrace},
- {"harddecommit", &debug.harddecommit},
- {"adaptivestackstart", &debug.adaptivestackstart},
+ panicnil atomic.Int32
}
-var globalGODEBUG string
+var dbgvars = []*dbgVar{
+ {name: "allocfreetrace", value: &debug.allocfreetrace},
+ {name: "clobberfree", value: &debug.clobberfree},
+ {name: "cgocheck", value: &debug.cgocheck},
+ {name: "efence", value: &debug.efence},
+ {name: "gccheckmark", value: &debug.gccheckmark},
+ {name: "gcpacertrace", value: &debug.gcpacertrace},
+ {name: "gcshrinkstackoff", value: &debug.gcshrinkstackoff},
+ {name: "gcstoptheworld", value: &debug.gcstoptheworld},
+ {name: "gctrace", value: &debug.gctrace},
+ {name: "invalidptr", value: &debug.invalidptr},
+ {name: "madvdontneed", value: &debug.madvdontneed},
+ {name: "sbrk", value: &debug.sbrk},
+ {name: "scavtrace", value: &debug.scavtrace},
+ {name: "scheddetail", value: &debug.scheddetail},
+ {name: "schedtrace", value: &debug.schedtrace},
+ {name: "tracebackancestors", value: &debug.tracebackancestors},
+ {name: "asyncpreemptoff", value: &debug.asyncpreemptoff},
+ {name: "inittrace", value: &debug.inittrace},
+ {name: "harddecommit", value: &debug.harddecommit},
+ {name: "adaptivestackstart", value: &debug.adaptivestackstart},
+ {name: "panicnil", atomic: &debug.panicnil},
+}
func parsedebugvars() {
// defaults
@@ -374,26 +377,101 @@ func parsedebugvars() {
debug.madvdontneed = 1
}
- globalGODEBUG = gogetenv("GODEBUG")
- godebugEnv.StoreNoWB(&globalGODEBUG)
- for p := globalGODEBUG; p != ""; {
- field := ""
- i := bytealg.IndexByteString(p, ',')
- if i < 0 {
- field, p = p, ""
+ godebug := gogetenv("GODEBUG")
+
+ p := new(string)
+ *p = godebug
+ godebugEnv.Store(p)
+
+ // apply runtime defaults, if any
+ for _, v := range dbgvars {
+ if v.def != 0 {
+ // Every var should have either v.value or v.atomic set.
+ if v.value != nil {
+ *v.value = v.def
+ } else if v.atomic != nil {
+ v.atomic.Store(v.def)
+ }
+ }
+ }
+
+ // apply compile-time GODEBUG settings
+ parsegodebug(godebugDefault, nil)
+
+ // apply environment settings
+ parsegodebug(godebug, nil)
+
+ debug.malloc = (debug.allocfreetrace | debug.inittrace | debug.sbrk) != 0
+
+ setTraceback(gogetenv("GOTRACEBACK"))
+ traceback_env = traceback_cache
+}
+
+// reparsedebugvars reparses the runtime's debug variables
+// because the environment variable has been changed to env.
+func reparsedebugvars(env string) {
+ seen := make(map[string]bool)
+ // apply environment settings
+ parsegodebug(env, seen)
+ // apply compile-time GODEBUG settings for as-yet-unseen variables
+ parsegodebug(godebugDefault, seen)
+ // apply defaults for as-yet-unseen variables
+ for _, v := range dbgvars {
+ if v.atomic != nil && !seen[v.name] {
+ v.atomic.Store(0)
+ }
+ }
+}
+
+// parsegodebug parses the godebug string, updating variables listed in dbgvars.
+// If seen == nil, this is startup time and we process the string left to right
+// overwriting older settings with newer ones.
+// If seen != nil, $GODEBUG has changed and we are doing an
+// incremental update. To avoid flapping in the case where a value is
+// set multiple times (perhaps in the default and the environment,
+// or perhaps twice in the environment), we process the string right-to-left
+// and only change values not already seen. After doing this for both
+// the environment and the default settings, the caller must also call
+// cleargodebug(seen) to reset any now-unset values back to their defaults.
+func parsegodebug(godebug string, seen map[string]bool) {
+ for p := godebug; p != ""; {
+ var field string
+ if seen == nil {
+ // startup: process left to right, overwriting older settings with newer
+ i := bytealg.IndexByteString(p, ',')
+ if i < 0 {
+ field, p = p, ""
+ } else {
+ field, p = p[:i], p[i+1:]
+ }
} else {
- field, p = p[:i], p[i+1:]
+ // incremental update: process right to left, updating and skipping seen
+ i := len(p) - 1
+ for i >= 0 && p[i] != ',' {
+ i--
+ }
+ if i < 0 {
+ p, field = "", p
+ } else {
+ p, field = p[:i], p[i+1:]
+ }
}
- i = bytealg.IndexByteString(field, '=')
+ i := bytealg.IndexByteString(field, '=')
if i < 0 {
continue
}
key, value := field[:i], field[i+1:]
+ if seen[key] {
+ continue
+ }
+ if seen != nil {
+ seen[key] = true
+ }
// Update MemProfileRate directly here since it
// is int, not int32, and should only be updated
// if specified in GODEBUG.
- if key == "memprofilerate" {
+ if seen == nil && key == "memprofilerate" {
if n, ok := atoi(value); ok {
MemProfileRate = n
}
@@ -401,17 +479,16 @@ func parsedebugvars() {
for _, v := range dbgvars {
if v.name == key {
if n, ok := atoi32(value); ok {
- *v.value = n
+ if seen == nil && v.value != nil {
+ *v.value = n
+ } else if v.atomic != nil {
+ v.atomic.Store(n)
+ }
}
}
}
}
}
-
- debug.malloc = (debug.allocfreetrace | debug.inittrace | debug.sbrk) != 0
-
- setTraceback(gogetenv("GOTRACEBACK"))
- traceback_env = traceback_cache
}
//go:linkname setTraceback runtime/debug.SetTraceback