]> Cypherpunks.ru repositories - gostls13.git/commitdiff
internal/godebug: define more efficient API
authorRuss Cox <rsc@golang.org>
Fri, 11 Nov 2022 17:36:31 +0000 (12:36 -0500)
committerGopher Robot <gobot@golang.org>
Mon, 14 Nov 2022 15:19:57 +0000 (15:19 +0000)
We have been expanding our use of GODEBUG for compatibility,
and the current implementation forces a tradeoff between
freshness and efficiency. It parses the environment variable
in full each time it is called, which is expensive. But if clients
cache the result, they won't respond to run-time GODEBUG
changes, as happened with x509sha1 (#56436).

This CL changes the GODEBUG API to provide efficient,
up-to-date results. Instead of a single Get function,
New returns a *godebug.Setting that itself has a Get method.
Clients can save the result of New, which is no more expensive
than errors.New, in a global variable, and then call that
variable's Get method to get the value. Get costs only two
atomic loads in the case where the variable hasn't changed
since the last call.

Unfortunately, these changes do require importing sync
from godebug, which will mean that sync itself will never
be able to use a GODEBUG setting. That doesn't seem like
such a hardship. If it was really necessary, the runtime could
pass a setting to package sync itself at startup, with the
caveat that that setting, like the ones used by runtime itself,
would not respond to run-time GODEBUG changes.

Change-Id: I99a3acfa24fb2a692610af26a5d14bbc62c966ac
Reviewed-on: https://go-review.googlesource.com/c/go/+/449504
Run-TryBot: Russ Cox <rsc@golang.org>
Auto-Submit: Russ Cox <rsc@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

23 files changed:
src/cmd/go/go_test.go
src/cmd/go/internal/fsys/fsys.go
src/cmd/go/internal/modindex/read.go
src/crypto/x509/x509.go
src/go/build/build.go
src/go/build/deps_test.go
src/internal/cpu/cpu_test.go
src/internal/cpu/cpu_x86_test.go
src/internal/fuzz/fuzz.go
src/internal/godebug/export_test.go [deleted file]
src/internal/godebug/godebug.go
src/internal/godebug/godebug_test.go
src/internal/intern/intern.go
src/math/rand/rand.go
src/net/conf.go
src/net/http/server.go
src/net/http/transport.go
src/os/exec/exec.go
src/os/exec/lp_plan9.go
src/os/exec/lp_unix.go
src/os/exec/lp_windows.go
src/runtime/runtime.go
src/runtime/runtime1.go

index eebc3266c89cc816077fc13e3c344bba0d92b775..a852fea80521d834967fa72018ac03a4964b944b 100644 (file)
@@ -2348,9 +2348,11 @@ func TestUpxCompression(t *testing.T) {
        }
 }
 
+var gocacheverify = godebug.New("gocacheverify")
+
 func TestCacheListStale(t *testing.T) {
        tooSlow(t)
-       if godebug.Get("gocacheverify") == "1" {
+       if gocacheverify.Value() == "1" {
                t.Skip("GODEBUG gocacheverify")
        }
        tg := testgo(t)
@@ -2373,7 +2375,7 @@ func TestCacheListStale(t *testing.T) {
 func TestCacheCoverage(t *testing.T) {
        tooSlow(t)
 
-       if godebug.Get("gocacheverify") == "1" {
+       if gocacheverify.Value() == "1" {
                t.Skip("GODEBUG gocacheverify")
        }
 
@@ -2407,7 +2409,7 @@ func TestIssue22588(t *testing.T) {
 
 func TestIssue22531(t *testing.T) {
        tooSlow(t)
-       if godebug.Get("gocacheverify") == "1" {
+       if gocacheverify.Value() == "1" {
                t.Skip("GODEBUG gocacheverify")
        }
        tg := testgo(t)
@@ -2436,7 +2438,7 @@ func TestIssue22531(t *testing.T) {
 
 func TestIssue22596(t *testing.T) {
        tooSlow(t)
-       if godebug.Get("gocacheverify") == "1" {
+       if gocacheverify.Value() == "1" {
                t.Skip("GODEBUG gocacheverify")
        }
        tg := testgo(t)
@@ -2466,7 +2468,7 @@ func TestIssue22596(t *testing.T) {
 func TestTestCache(t *testing.T) {
        tooSlow(t)
 
-       if godebug.Get("gocacheverify") == "1" {
+       if gocacheverify.Value() == "1" {
                t.Skip("GODEBUG gocacheverify")
        }
        tg := testgo(t)
index 311e0339303e97ddcd5b29fed77ff3d6378cb420..07bdc16aba2a2a2451c76f241d82f102ce1bd3f6 100644 (file)
@@ -36,27 +36,29 @@ func Trace(op, path string) {
        traceMu.Lock()
        defer traceMu.Unlock()
        fmt.Fprintf(traceFile, "%d gofsystrace %s %s\n", os.Getpid(), op, path)
-       if traceStack != "" {
-               if match, _ := pathpkg.Match(traceStack, path); match {
+       if pattern := gofsystracestack.Value(); pattern != "" {
+               if match, _ := pathpkg.Match(pattern, path); match {
                        traceFile.Write(debug.Stack())
                }
        }
 }
 
 var (
-       doTrace    bool
-       traceStack string
-       traceFile  *os.File
-       traceMu    sync.Mutex
+       doTrace   bool
+       traceFile *os.File
+       traceMu   sync.Mutex
+
+       gofsystrace      = godebug.New("gofsystrace")
+       gofsystracelog   = godebug.New("gofsystracelog")
+       gofsystracestack = godebug.New("gofsystracestack")
 )
 
 func init() {
-       if godebug.Get("gofsystrace") != "1" {
+       if gofsystrace.Value() != "1" {
                return
        }
        doTrace = true
-       traceStack = godebug.Get("gofsystracestack")
-       if f := godebug.Get("gofsystracelog"); f != "" {
+       if f := gofsystracelog.Value(); f != "" {
                // Note: No buffering on writes to this file, so no need to worry about closing it at exit.
                var err error
                traceFile, err = os.OpenFile(f, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
index 3e068d56002bfbfd97fde17c237eb312957def87..eaf921b6dfd7e5d8d5def4daebb3168d51a41cd5 100644 (file)
@@ -37,7 +37,7 @@ import (
 // It will be removed before the release.
 // TODO(matloob): Remove enabled once we have more confidence on the
 // module index.
-var enabled bool = godebug.Get("goindex") != "0"
+var enabled = godebug.New("goindex").Value() != "0"
 
 // Module represents and encoded module index file. It is used to
 // do the equivalent of build.Import of packages in the module and answer other
@@ -368,6 +368,8 @@ func relPath(path, modroot string) string {
        return str.TrimFilePathPrefix(filepath.Clean(path), filepath.Clean(modroot))
 }
 
+var installgorootAll = godebug.New("installgoroot").Value() == "all"
+
 // Import is the equivalent of build.Import given the information in Module.
 func (rp *IndexPackage) Import(bctxt build.Context, mode build.ImportMode) (p *build.Package, err error) {
        defer unprotect(protect(), &err)
@@ -436,7 +438,7 @@ func (rp *IndexPackage) Import(bctxt build.Context, mode build.ImportMode) (p *b
                                p.PkgTargetRoot = ctxt.joinPath(p.Root, pkgtargetroot)
 
                                // Set the install target if applicable.
-                               if !p.Goroot || (strings.EqualFold(godebug.Get("installgoroot"), "all") && p.ImportPath != "unsafe" && p.ImportPath != "builtin") {
+                               if !p.Goroot || (installgorootAll && p.ImportPath != "unsafe" && p.ImportPath != "builtin") {
                                        p.PkgObj = ctxt.joinPath(p.Root, pkga)
                                }
                        }
index df86c65939ad9af17c9055a5bab638c32a4976e1..9ebc25bf000ffde54f97a654e635f7e81ca55feb 100644 (file)
@@ -813,6 +813,8 @@ func signaturePublicKeyAlgoMismatchError(expectedPubKeyAlgo PublicKeyAlgorithm,
        return fmt.Errorf("x509: signature algorithm specifies an %s public key, but have public key of type %T", expectedPubKeyAlgo.String(), pubKey)
 }
 
+var x509sha1 = godebug.New("x509sha1")
+
 // checkSignature verifies that signature is a valid signature over signed from
 // a crypto.PublicKey.
 func checkSignature(algo SignatureAlgorithm, signed, signature []byte, publicKey crypto.PublicKey, allowSHA1 bool) (err error) {
@@ -835,7 +837,7 @@ func checkSignature(algo SignatureAlgorithm, signed, signature []byte, publicKey
                return InsecureAlgorithmError(algo)
        case crypto.SHA1:
                // SHA-1 signatures are mostly disabled. See go.dev/issue/41682.
-               if !allowSHA1 && godebug.Get("x509sha1") != "1" {
+               if !allowSHA1 && x509sha1.Value() != "1" {
                        return InsecureAlgorithmError(algo)
                }
                fallthrough
index 6925154da18f4774b5c73ba72489212ba514bab3..53d4b27e10dbce550248216c558567efbc47e1e0 100644 (file)
@@ -521,6 +521,8 @@ func nameExt(name string) string {
        return name[i:]
 }
 
+var installgoroot = godebug.New("installgoroot")
+
 // Import returns details about the Go package named by the import path,
 // interpreting local import paths relative to the srcDir directory.
 // If the path is a local import path naming a package that can be imported
@@ -783,7 +785,7 @@ Found:
                        p.PkgTargetRoot = ctxt.joinPath(p.Root, pkgtargetroot)
 
                        // Set the install target if applicable.
-                       if !p.Goroot || (strings.EqualFold(godebug.Get("installgoroot"), "all") && p.ImportPath != "unsafe" && p.ImportPath != "builtin") {
+                       if !p.Goroot || (installgoroot.Value() == "all" && p.ImportPath != "unsafe" && p.ImportPath != "builtin") {
                                p.PkgObj = ctxt.joinPath(p.Root, pkga)
                        }
                }
index dea9935c126a2f83e89aae4154d9ea42734a64b4..39609521c347c358c901a120aa63d366315842c8 100644 (file)
@@ -52,13 +52,10 @@ var depsRules = `
        internal/goarch, unsafe
        < internal/abi;
 
-       unsafe
-       < internal/godebug;
-
        # RUNTIME is the core runtime group of packages, all of them very light-weight.
        internal/abi, internal/cpu, internal/goarch,
        internal/coverage/rtcov, internal/goexperiment,
-       internal/goos, internal/godebug, unsafe
+       internal/goos, unsafe
        < internal/bytealg
        < internal/itoa
        < internal/unsafeheader
@@ -70,6 +67,7 @@ var depsRules = `
        < sync/atomic
        < internal/race
        < sync
+       < internal/godebug
        < internal/reflectlite
        < errors
        < internal/oserror, math/bits
index c95cd517266013192e93eeb5e3e493b35a9e6c61..5aa277f960a2f4d51f07e37a24f4d786c9922a77 100644 (file)
@@ -48,7 +48,7 @@ func TestDisableAllCapabilities(t *testing.T) {
 func TestAllCapabilitiesDisabled(t *testing.T) {
        MustHaveDebugOptionsSupport(t)
 
-       if godebug.Get("cpu.all") != "off" {
+       if godebug.New("cpu.all").Value() != "off" {
                t.Skipf("skipping test: GODEBUG=cpu.all=off not set")
        }
 
index 43d6b211ea4cf5a64f8008df10c659d6797f4031..d7be4308a2a34b641105320651ffc635c3951806 100644 (file)
@@ -28,7 +28,7 @@ func TestDisableSSE3(t *testing.T) {
 func TestSSE3DebugOption(t *testing.T) {
        MustHaveDebugOptionsSupport(t)
 
-       if godebug.Get("cpu.sse3") != "off" {
+       if godebug.New("cpu.sse3").Value() != "off" {
                t.Skipf("skipping test: GODEBUG=cpu.sse3=off not set")
        }
 
index d0eb92dd9f8a6519dcd978a12b54d65dd7f2a1c2..7d4fe06198c8de21712beeb390fde93ae7cb1d02 100644 (file)
@@ -21,7 +21,6 @@ import (
        "reflect"
        "runtime"
        "strings"
-       "sync"
        "time"
 )
 
@@ -1077,14 +1076,8 @@ var zeroVals []any = []any{
        uint64(0),
 }
 
-var (
-       debugInfo     bool
-       debugInfoOnce sync.Once
-)
+var debugInfo = godebug.New("fuzzdebug").Value() == "1"
 
 func shouldPrintDebugInfo() bool {
-       debugInfoOnce.Do(func() {
-               debugInfo = godebug.Get("fuzzdebug") == "1"
-       })
        return debugInfo
 }
diff --git a/src/internal/godebug/export_test.go b/src/internal/godebug/export_test.go
deleted file mode 100644 (file)
index e84d9a9..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright 2022 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 godebug
-
-var Xget = get
index 65a8c4e3051078e8ba52c315c6ab81bd71fbf31c..dbcd98042deea48fc4dcb0a16362cca07a94f516 100644 (file)
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package godebug parses the GODEBUG environment variable.
+// Package godebug makes the settings in the $GODEBUG environment variable
+// available to other packages. These settings are often used for compatibility
+// tweaks, when we need to change a default behavior but want to let users
+// opt back in to the original. For example GODEBUG=http2server=0 disables
+// HTTP/2 support in the net/http server.
+//
+// In typical usage, code should declare a Setting as a global
+// and then call Value each time the current setting value is needed:
+//
+//     var http2server = godebug.New("http2server")
+//
+//     func ServeConn(c net.Conn) {
+//             if http2server.Value() == "0" {
+//                     disallow HTTP/2
+//                     ...
+//             }
+//             ...
+//     }
 package godebug
 
-import _ "unsafe" // go:linkname
+import (
+       "sync"
+       "sync/atomic"
+       _ "unsafe" // go:linkname
+)
 
-//go:linkname getGODEBUG
-func getGODEBUG() string
+// A Setting is a single setting in the $GODEBUG environment variable.
+type Setting struct {
+       name  string
+       once  sync.Once
+       value *atomic.Pointer[string]
+}
+
+// New returns a new Setting for the $GODEBUG setting with the given name.
+func New(name string) *Setting {
+       return &Setting{name: name}
+}
+
+// Name returns the name of the setting.
+func (s *Setting) Name() string {
+       return s.name
+}
 
-// Get returns the value for the provided GODEBUG key.
-func Get(key string) string {
-       return get(getGODEBUG(), key)
+// String returns a printable form for the setting: name=value.
+func (s *Setting) String() string {
+       return s.name + "=" + s.Value()
 }
 
-// get returns the value part of key=value in s (a GODEBUG value).
-func get(s, key string) string {
-       for i := 0; i < len(s)-len(key)-1; i++ {
-               if i > 0 && s[i-1] != ',' {
-                       continue
+// cache is a cache of all the GODEBUG settings,
+// a locked map[string]*atomic.Pointer[string].
+//
+// All Settings with the same name share a single
+// *atomic.Pointer[string], so that when GODEBUG
+// changes only that single atomic string pointer
+// needs to be updated.
+//
+// A name appears in the values map either if it is the
+// name of a Setting for which Value has been called
+// at least once, or if the name has ever appeared in
+// a name=value pair in the $GODEBUG environment variable.
+// Once entered into the map, the name is never removed.
+var cache sync.Map // name string -> value *atomic.Pointer[string]
+
+var empty string
+
+// Value returns the current value for the GODEBUG setting s.
+//
+// Value maintains an internal cache that is synchronized
+// with changes to the $GODEBUG environment variable,
+// making Value efficient to call as frequently as needed.
+// Clients should therefore typically not attempt their own
+// caching of Value's result.
+func (s *Setting) Value() string {
+       s.once.Do(func() {
+               v, ok := cache.Load(s.name)
+               if !ok {
+                       p := new(atomic.Pointer[string])
+                       p.Store(&empty)
+                       v, _ = cache.LoadOrStore(s.name, p)
                }
-               afterKey := s[i+len(key):]
-               if afterKey[0] != '=' || s[i:i+len(key)] != key {
-                       continue
+               s.value = v.(*atomic.Pointer[string])
+       })
+       return *s.value.Load()
+}
+
+// setUpdate is provided by package runtime.
+// It calls update(def, env), where def is the default GODEBUG setting
+// and env is the current value of the $GODEBUG environment variable.
+// After that first call, the runtime calls update(def, env)
+// again each time the environment variable changes
+// (due to use of os.Setenv, for example).
+//
+//go:linkname setUpdate
+func setUpdate(update func(string, string))
+
+func init() {
+       setUpdate(update)
+}
+
+var updateMu sync.Mutex
+
+// update records an updated GODEBUG setting.
+// def is the default GODEBUG setting for the running binary,
+// and env is the current value of the $GODEBUG environment variable.
+func update(def, env string) {
+       updateMu.Lock()
+       defer updateMu.Unlock()
+
+       // Update all the cached values, creating new ones as needed.
+       // We parse the environment variable first, so that any settings it has
+       // are already locked in place (did[name] = true) before we consider
+       // the defaults.
+       did := make(map[string]bool)
+       parse(did, env)
+       parse(did, def)
+
+       // Clear any cached values that are no longer present.
+       cache.Range(func(name, v any) bool {
+               if !did[name.(string)] {
+                       v.(*atomic.Pointer[string]).Store(&empty)
                }
-               val := afterKey[1:]
-               for i, b := range val {
-                       if b == ',' {
-                               return val[:i]
+               return true
+       })
+}
+
+// parse parses the GODEBUG setting string s,
+// which has the form k=v,k2=v2,k3=v3.
+// Later settings override earlier ones.
+// Parse only updates settings k=v for which did[k] = false.
+// It also sets did[k] = true for settings that it updates.
+func parse(did map[string]bool, s string) {
+       // Scan the string backward so that later settings are used
+       // and earlier settings are ignored.
+       // Note that a forward scan would cause cached values
+       // to temporarily use the ignored value before being
+       // updated to the "correct" one.
+       end := len(s)
+       eq := -1
+       for i := end - 1; i >= -1; i-- {
+               if i == -1 || s[i] == ',' {
+                       if eq >= 0 {
+                               name, value := s[i+1:eq], s[eq+1:end]
+                               if !did[name] {
+                                       did[name] = true
+                                       v, ok := cache.Load(name)
+                                       if !ok {
+                                               p := new(atomic.Pointer[string])
+                                               p.Store(&empty)
+                                               v, _ = cache.LoadOrStore(name, p)
+                                       }
+                                       v.(*atomic.Pointer[string]).Store(&value)
+                               }
                        }
+                       eq = -1
+                       end = i
+               } else if s[i] == '=' {
+                       eq = i
                }
-               return val
        }
-       return ""
 }
index d7a2a7a8d8a96b9120c1bfc1d3b69802e315bde6..319229dac9a82649c35ba9794154668c93252197 100644 (file)
@@ -10,28 +10,30 @@ import (
 )
 
 func TestGet(t *testing.T) {
+       foo := New("foo")
        tests := []struct {
                godebug string
-               key     string
+               setting *Setting
                want    string
        }{
-               {"", "", ""},
-               {"", "foo", ""},
-               {"foo=bar", "foo", "bar"},
-               {"foo=bar,after=x", "foo", "bar"},
-               {"before=x,foo=bar,after=x", "foo", "bar"},
-               {"before=x,foo=bar", "foo", "bar"},
-               {",,,foo=bar,,,", "foo", "bar"},
-               {"foodecoy=wrong,foo=bar", "foo", "bar"},
-               {"foo=", "foo", ""},
-               {"foo", "foo", ""},
-               {",foo", "foo", ""},
-               {"foo=bar,baz", "loooooooong", ""},
+               {"", New(""), ""},
+               {"", foo, ""},
+               {"foo=bar", foo, "bar"},
+               {"foo=bar,after=x", foo, "bar"},
+               {"before=x,foo=bar,after=x", foo, "bar"},
+               {"before=x,foo=bar", foo, "bar"},
+               {",,,foo=bar,,,", foo, "bar"},
+               {"foodecoy=wrong,foo=bar", foo, "bar"},
+               {"foo=", foo, ""},
+               {"foo", foo, ""},
+               {",foo", foo, ""},
+               {"foo=bar,baz", New("loooooooong"), ""},
        }
        for _, tt := range tests {
-               got := Xget(tt.godebug, tt.key)
+               t.Setenv("GODEBUG", tt.godebug)
+               got := tt.setting.Value()
                if got != tt.want {
-                       t.Errorf("get(%q, %q) = %q; want %q", tt.godebug, tt.key, got, tt.want)
+                       t.Errorf("get(%q, %q) = %q; want %q", tt.godebug, tt.setting.Name(), got, tt.want)
                }
        }
 }
index c7639b4668736a39ed2b8a0aeed0345aa24c8c0e..0e6852f7299188ac45cc2471f1e14d3f6bbd964a 100644 (file)
@@ -66,10 +66,12 @@ var (
        valSafe = safeMap()         // non-nil in safe+leaky mode
 )
 
+var intern = godebug.New("intern")
+
 // safeMap returns a non-nil map if we're in safe-but-leaky mode,
 // as controlled by GODEBUG=intern=leaky
 func safeMap() map[key]*Value {
-       if godebug.Get("intern") == "leaky" {
+       if intern.Value() == "leaky" {
                return map[key]*Value{}
        }
        return nil
index f6b015aba2494b0405b644f4b51f972c0d47db93..0157d7198bf2547570430a0e66687409a25631e4 100644 (file)
@@ -408,12 +408,14 @@ type lockedSource struct {
 //go:linkname fastrand64
 func fastrand64() uint64
 
+var randautoseed = godebug.New("randautoseed")
+
 // source returns r.s, allocating and seeding it if needed.
 // The caller must have locked r.
 func (r *lockedSource) source() *rngSource {
        if r.s == nil {
                var seed int64
-               if godebug.Get("randautoseed") == "0" {
+               if randautoseed.Value() == "0" {
                        seed = 1
                } else {
                        seed = int64(fastrand64())
index 77099ca100bbc1628dbe991dc919dcc586732108..b6bc195683419ed5c016e74f8dfd9b3ff55a60ef 100644 (file)
@@ -301,6 +301,8 @@ func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrde
        return fallbackOrder
 }
 
+var netdns = godebug.New("netdns")
+
 // goDebugNetDNS parses the value of the GODEBUG "netdns" value.
 // The netdns value can be of the form:
 //
@@ -314,7 +316,7 @@ func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrde
 //
 // etc.
 func goDebugNetDNS() (dnsMode string, debugLevel int) {
-       goDebug := godebug.Get("netdns")
+       goDebug := netdns.Value()
        parsePart := func(s string) {
                if s == "" {
                        return
index 698d0636fa8f12674299393b05df5ac55622134c..c3c3f91d9ab1ec36758e1d75a282608ec5d933a6 100644 (file)
@@ -3313,11 +3313,13 @@ func (srv *Server) onceSetNextProtoDefaults_Serve() {
        }
 }
 
+var http2server = godebug.New("http2server")
+
 // onceSetNextProtoDefaults configures HTTP/2, if the user hasn't
 // configured otherwise. (by setting srv.TLSNextProto non-nil)
 // It must only be called via srv.nextProtoOnce (use srv.setupHTTP2_*).
 func (srv *Server) onceSetNextProtoDefaults() {
-       if omitBundledHTTP2 || godebug.Get("http2server") == "0" {
+       if omitBundledHTTP2 || http2server.Value() == "0" {
                return
        }
        // Enable HTTP/2 by default if the user hasn't otherwise
index 184cf275183388a3bfde9340676d773a70013c91..e4434e8076c489e42d91fdad769be77afa63b2fa 100644 (file)
@@ -362,11 +362,13 @@ func (t *Transport) hasCustomTLSDialer() bool {
        return t.DialTLS != nil || t.DialTLSContext != nil
 }
 
+var http2client = godebug.New("http2client")
+
 // onceSetNextProtoDefaults initializes TLSNextProto.
 // It must be called via t.nextProtoOnce.Do.
 func (t *Transport) onceSetNextProtoDefaults() {
        t.tlsNextProtoWasNil = (t.TLSNextProto == nil)
-       if godebug.Get("http2client") == "0" {
+       if http2client.Value() == "0" {
                return
        }
 
index 46b09b9c0c10cbd78315f1524a444049a05061c6..2f4bdffe9c74577410fa09dbf4bbcc6da1cc46fd 100644 (file)
@@ -348,6 +348,9 @@ type ctxResult struct {
        timer *time.Timer
 }
 
+var execwait = godebug.New("execwait")
+var execerrdot = godebug.New("execerrdot")
+
 // Command returns the Cmd struct to execute the named program with
 // the given arguments.
 //
@@ -376,8 +379,8 @@ func Command(name string, arg ...string) *Cmd {
                Args: append([]string{name}, arg...),
        }
 
-       if execwait := godebug.Get("execwait"); execwait != "" {
-               if execwait == "2" {
+       if v := execwait.Value(); v != "" {
+               if v == "2" {
                        // Obtain the caller stack. (This is equivalent to runtime/debug.Stack,
                        // copied to avoid importing the whole package.)
                        stack := make([]byte, 1024)
index 092684f03a236b6a8f89f97ff042205ad6dc724c..59538d98a36f40775ade54ceb6111d05b3a35e1a 100644 (file)
@@ -6,7 +6,6 @@ package exec
 
 import (
        "errors"
-       "internal/godebug"
        "io/fs"
        "os"
        "path/filepath"
@@ -54,7 +53,7 @@ func LookPath(file string) (string, error) {
        for _, dir := range filepath.SplitList(path) {
                path := filepath.Join(dir, file)
                if err := findExecutable(path); err == nil {
-                       if !filepath.IsAbs(path) && godebug.Get("execerrdot") != "0" {
+                       if !filepath.IsAbs(path) && execerrdot.Value() != "0" {
                                return path, &Error{file, ErrDot}
                        }
                        return path, nil
index af68c2f268a8422d7ae2c906ebab821c48081bda..2af9b01cf6c85405380fe0454880f7a94dea6dd2 100644 (file)
@@ -8,7 +8,6 @@ package exec
 
 import (
        "errors"
-       "internal/godebug"
        "internal/syscall/unix"
        "io/fs"
        "os"
@@ -70,7 +69,7 @@ func LookPath(file string) (string, error) {
                }
                path := filepath.Join(dir, file)
                if err := findExecutable(path); err == nil {
-                       if !filepath.IsAbs(path) && godebug.Get("execerrdot") != "0" {
+                       if !filepath.IsAbs(path) && execerrdot.Value() != "0" {
                                return path, &Error{file, ErrDot}
                        }
                        return path, nil
index ec45db7459bc56fcc4cd23b9a04c4b5989197c1f..97bfa58244eb13e26ddb335fa8c5aa12d774e316 100644 (file)
@@ -6,7 +6,6 @@ package exec
 
 import (
        "errors"
-       "internal/godebug"
        "io/fs"
        "os"
        "path/filepath"
@@ -103,7 +102,7 @@ func LookPath(file string) (string, error) {
        )
        if _, found := syscall.Getenv("NoDefaultCurrentDirectoryInExePath"); !found {
                if f, err := findExecutable(filepath.Join(".", file), exts); err == nil {
-                       if godebug.Get("execerrdot") == "0" {
+                       if execerrdot.Value() == "0" {
                                return f, nil
                        }
                        dotf, dotErr = f, &Error{file, ErrDot}
@@ -128,7 +127,7 @@ func LookPath(file string) (string, error) {
                                }
                        }
 
-                       if !filepath.IsAbs(f) && godebug.Get("execerrdot") != "0" {
+                       if !filepath.IsAbs(f) && execerrdot.Value() != "0" {
                                return f, &Error{file, ErrDot}
                        }
                        return f, nil
index bc60b3ca75a8f0c318736d84965d17ac9769be65..9f68738aa7ba07c5992c4e6124ee0777a9f876ad 100644 (file)
@@ -66,14 +66,26 @@ func syscall_Exit(code int) {
        exit(int32(code))
 }
 
-var godebugenv atomic.Pointer[string] // set by parsedebugvars
+var godebugDefault string
+var godebugUpdate atomic.Pointer[func(string, string)]
+var godebugEnv atomic.Pointer[string] // set by parsedebugvars
+
+//go:linkname godebug_setUpdate internal/godebug.setUpdate
+func godebug_setUpdate(update func(string, string)) {
+       p := new(func(string, string))
+       *p = update
+       godebugUpdate.Store(p)
+       godebugNotify()
+}
 
-//go:linkname godebug_getGODEBUG internal/godebug.getGODEBUG
-func godebug_getGODEBUG() string {
-       if p := godebugenv.Load(); p != nil {
-               return *p
+func godebugNotify() {
+       if update := godebugUpdate.Load(); update != nil {
+               var env string
+               if p := godebugEnv.Load(); p != nil {
+                       env = *p
+               }
+               (*update)(godebugDefault, env)
        }
-       return ""
 }
 
 //go:linkname syscall_runtimeSetenv syscall.runtimeSetenv
@@ -82,7 +94,8 @@ func syscall_runtimeSetenv(key, value string) {
        if key == "GODEBUG" {
                p := new(string)
                *p = value
-               godebugenv.Store(p)
+               godebugEnv.Store(p)
+               godebugNotify()
        }
 }
 
@@ -90,7 +103,8 @@ func syscall_runtimeSetenv(key, value string) {
 func syscall_runtimeUnsetenv(key string) {
        unsetenv_c(key)
        if key == "GODEBUG" {
-               godebugenv.Store(nil)
+               godebugEnv.Store(nil)
+               godebugNotify()
        }
 }
 
index a29608329cb6b35e08eec6ea9d132072833487a3..76dca9ca7709890dea585cafac4e166a16023045 100644 (file)
@@ -375,7 +375,7 @@ func parsedebugvars() {
        }
 
        globalGODEBUG = gogetenv("GODEBUG")
-       godebugenv.StoreNoWB(&globalGODEBUG)
+       godebugEnv.StoreNoWB(&globalGODEBUG)
        for p := globalGODEBUG; p != ""; {
                field := ""
                i := bytealg.IndexByteString(p, ',')