diff options
author | Ian Lance Taylor <iant@golang.org> | 2022-02-11 15:02:44 -0800 |
---|---|---|
committer | Ian Lance Taylor <iant@golang.org> | 2022-02-11 15:02:44 -0800 |
commit | 9a510fb0970d3d9a4201bce8965cabe67850386b (patch) | |
tree | 43d7fd2bbfd7ad8c9625a718a5e8718889351994 /libgo/go/runtime/debug | |
parent | a6d3012b274f38b20e2a57162106f625746af6c6 (diff) | |
parent | 8dc2499aa62f768c6395c9754b8cabc1ce25c494 (diff) | |
download | gcc-9a510fb0970d3d9a4201bce8965cabe67850386b.zip gcc-9a510fb0970d3d9a4201bce8965cabe67850386b.tar.gz gcc-9a510fb0970d3d9a4201bce8965cabe67850386b.tar.bz2 |
Merge from trunk revision 8dc2499aa62f768c6395c9754b8cabc1ce25c494
Diffstat (limited to 'libgo/go/runtime/debug')
-rw-r--r-- | libgo/go/runtime/debug/garbage_test.go | 73 | ||||
-rw-r--r-- | libgo/go/runtime/debug/mod.go | 188 | ||||
-rw-r--r-- | libgo/go/runtime/debug/panic_test.go | 1 |
3 files changed, 200 insertions, 62 deletions
diff --git a/libgo/go/runtime/debug/garbage_test.go b/libgo/go/runtime/debug/garbage_test.go index 62eeb2c..8986606 100644 --- a/libgo/go/runtime/debug/garbage_test.go +++ b/libgo/go/runtime/debug/garbage_test.go @@ -6,6 +6,7 @@ package debug_test import ( "internal/testenv" + "os" "runtime" . "runtime/debug" "testing" @@ -87,27 +88,75 @@ func TestReadGCStats(t *testing.T) { } } -var big = make([]byte, 1<<20) +var big []byte func TestFreeOSMemory(t *testing.T) { - var ms1, ms2 runtime.MemStats - - if big == nil { - t.Skip("test is not reliable when run multiple times") + if runtime.Compiler == "gccgo" { + t.Skip("conservative GC") } - big = nil + + // Tests FreeOSMemory by making big susceptible to collection + // and checking that at least that much memory is returned to + // the OS after. + + const bigBytes = 32 << 20 + big = make([]byte, bigBytes) + + // Make sure any in-progress GCs are complete. runtime.GC() - runtime.ReadMemStats(&ms1) + + var before runtime.MemStats + runtime.ReadMemStats(&before) + + // Clear the last reference to the big allocation, making it + // susceptible to collection. + big = nil + + // FreeOSMemory runs a GC cycle before releasing memory, + // so it's fine to skip a GC here. + // + // It's possible the background scavenger runs concurrently + // with this function and does most of the work for it. + // If that happens, it's OK. What we want is a test that fails + // often if FreeOSMemory does not work correctly, and a test + // that passes every time if it does. FreeOSMemory() - runtime.ReadMemStats(&ms2) - if ms1.HeapReleased >= ms2.HeapReleased { - t.Errorf("released before=%d; released after=%d; did not go up", ms1.HeapReleased, ms2.HeapReleased) + + var after runtime.MemStats + runtime.ReadMemStats(&after) + + // Check to make sure that the big allocation (now freed) + // had its memory shift into HeapReleased as a result of that + // FreeOSMemory. + if after.HeapReleased <= before.HeapReleased { + t.Fatalf("no memory released: %d -> %d", before.HeapReleased, after.HeapReleased) + } + + // Check to make sure bigBytes was released, plus some slack. Pages may get + // allocated in between the two measurements above for a variety for reasons, + // most commonly for GC work bufs. Since this can get fairly high, depending + // on scheduling and what GOMAXPROCS is, give a lot of slack up-front. + // + // Add a little more slack too if the page size is bigger than the runtime page size. + // "big" could end up unaligned on its ends, forcing the scavenger to skip at worst + // 2x pages. + slack := uint64(bigBytes / 2) + pageSize := uint64(os.Getpagesize()) + if pageSize > 8<<10 { + slack += pageSize * 2 + } + if slack > bigBytes { + // We basically already checked this. + return + } + if after.HeapReleased-before.HeapReleased < bigBytes-slack { + t.Fatalf("less than %d released: %d -> %d", bigBytes, before.HeapReleased, after.HeapReleased) } } var ( - setGCPercentBallast interface{} - setGCPercentSink interface{} + setGCPercentBallast any + setGCPercentSink any ) func TestSetGCPercent(t *testing.T) { diff --git a/libgo/go/runtime/debug/mod.go b/libgo/go/runtime/debug/mod.go index feac168..61b15ad 100644 --- a/libgo/go/runtime/debug/mod.go +++ b/libgo/go/runtime/debug/mod.go @@ -5,6 +5,9 @@ package debug import ( + "bytes" + "fmt" + "runtime" "strings" _ "unsafe" // for go:linkname ) @@ -16,15 +19,32 @@ func modinfo() string // in the running binary. The information is available only // in binaries built with module support. func ReadBuildInfo() (info *BuildInfo, ok bool) { - return readBuildInfo(modinfo()) + data := modinfo() + if len(data) < 32 { + return nil, false + } + data = data[16 : len(data)-16] + bi := &BuildInfo{} + if err := bi.UnmarshalText([]byte(data)); err != nil { + return nil, false + } + + // The go version is stored separately from other build info, mostly for + // historical reasons. It is not part of the modinfo() string, and + // ParseBuildInfo does not recognize it. We inject it here to hide this + // awkwardness from the user. + bi.GoVersion = runtime.Version() + + return bi, true } -// BuildInfo represents the build information read from -// the running binary. +// BuildInfo represents the build information read from a Go binary. type BuildInfo struct { - Path string // The main package path - Main Module // The module containing the main package - Deps []*Module // Module dependencies + GoVersion string // Version of Go that produced this binary. + Path string // The main package path + Main Module // The module containing the main package + Deps []*Module // Module dependencies + Settings []BuildSetting // Other information about the build. } // Module represents a module. @@ -35,81 +55,151 @@ type Module struct { Replace *Module // replaced by this module } -func readBuildInfo(data string) (*BuildInfo, bool) { - if len(data) < 32 { - return nil, false +// BuildSetting describes a setting that may be used to understand how the +// binary was built. For example, VCS commit and dirty status is stored here. +type BuildSetting struct { + // Key and Value describe the build setting. + // Key must not contain an equals sign, space, tab, or newline. + // Value must not contain newlines ('\n'). + Key, Value string +} + +func (bi *BuildInfo) MarshalText() ([]byte, error) { + buf := &bytes.Buffer{} + if bi.GoVersion != "" { + fmt.Fprintf(buf, "go\t%s\n", bi.GoVersion) } - data = data[16 : len(data)-16] + if bi.Path != "" { + fmt.Fprintf(buf, "path\t%s\n", bi.Path) + } + var formatMod func(string, Module) + formatMod = func(word string, m Module) { + buf.WriteString(word) + buf.WriteByte('\t') + buf.WriteString(m.Path) + mv := m.Version + if mv == "" { + mv = "(devel)" + } + buf.WriteByte('\t') + buf.WriteString(mv) + if m.Replace == nil { + buf.WriteByte('\t') + buf.WriteString(m.Sum) + } else { + buf.WriteByte('\n') + formatMod("=>", *m.Replace) + } + buf.WriteByte('\n') + } + if bi.Main.Path != "" { + formatMod("mod", bi.Main) + } + for _, dep := range bi.Deps { + formatMod("dep", *dep) + } + for _, s := range bi.Settings { + if strings.ContainsAny(s.Key, "= \t\n") { + return nil, fmt.Errorf("invalid build setting key %q", s.Key) + } + if strings.Contains(s.Value, "\n") { + return nil, fmt.Errorf("invalid build setting value for key %q: contains newline", s.Value) + } + fmt.Fprintf(buf, "build\t%s=%s\n", s.Key, s.Value) + } + + return buf.Bytes(), nil +} - const ( - pathLine = "path\t" - modLine = "mod\t" - depLine = "dep\t" - repLine = "=>\t" +func (bi *BuildInfo) UnmarshalText(data []byte) (err error) { + *bi = BuildInfo{} + lineNum := 1 + defer func() { + if err != nil { + err = fmt.Errorf("could not parse Go build info: line %d: %w", lineNum, err) + } + }() + + var ( + pathLine = []byte("path\t") + modLine = []byte("mod\t") + depLine = []byte("dep\t") + repLine = []byte("=>\t") + buildLine = []byte("build\t") + newline = []byte("\n") + tab = []byte("\t") ) - readEntryFirstLine := func(elem []string) (Module, bool) { + readModuleLine := func(elem [][]byte) (Module, error) { if len(elem) != 2 && len(elem) != 3 { - return Module{}, false + return Module{}, fmt.Errorf("expected 2 or 3 columns; got %d", len(elem)) } sum := "" if len(elem) == 3 { - sum = elem[2] + sum = string(elem[2]) } return Module{ - Path: elem[0], - Version: elem[1], + Path: string(elem[0]), + Version: string(elem[1]), Sum: sum, - }, true + }, nil } var ( - info = &BuildInfo{} last *Module - line string + line []byte ok bool ) - // Reverse of cmd/go/internal/modload.PackageBuildInfo + // Reverse of BuildInfo.String(), except for go version. for len(data) > 0 { - i := strings.IndexByte(data, '\n') - if i < 0 { + line, data, ok = bytes.Cut(data, newline) + if !ok { break } - line, data = data[:i], data[i+1:] switch { - case strings.HasPrefix(line, pathLine): + case bytes.HasPrefix(line, pathLine): elem := line[len(pathLine):] - info.Path = elem - case strings.HasPrefix(line, modLine): - elem := strings.Split(line[len(modLine):], "\t") - last = &info.Main - *last, ok = readEntryFirstLine(elem) - if !ok { - return nil, false + bi.Path = string(elem) + case bytes.HasPrefix(line, modLine): + elem := bytes.Split(line[len(modLine):], tab) + last = &bi.Main + *last, err = readModuleLine(elem) + if err != nil { + return err } - case strings.HasPrefix(line, depLine): - elem := strings.Split(line[len(depLine):], "\t") + case bytes.HasPrefix(line, depLine): + elem := bytes.Split(line[len(depLine):], tab) last = new(Module) - info.Deps = append(info.Deps, last) - *last, ok = readEntryFirstLine(elem) - if !ok { - return nil, false + bi.Deps = append(bi.Deps, last) + *last, err = readModuleLine(elem) + if err != nil { + return err } - case strings.HasPrefix(line, repLine): - elem := strings.Split(line[len(repLine):], "\t") + case bytes.HasPrefix(line, repLine): + elem := bytes.Split(line[len(repLine):], tab) if len(elem) != 3 { - return nil, false + return fmt.Errorf("expected 3 columns for replacement; got %d", len(elem)) } if last == nil { - return nil, false + return fmt.Errorf("replacement with no module on previous line") } last.Replace = &Module{ - Path: elem[0], - Version: elem[1], - Sum: elem[2], + Path: string(elem[0]), + Version: string(elem[1]), + Sum: string(elem[2]), } last = nil + case bytes.HasPrefix(line, buildLine): + key, val, ok := strings.Cut(string(line[len(buildLine):]), "=") + if !ok { + return fmt.Errorf("invalid build line") + } + if key == "" { + return fmt.Errorf("empty key") + } + bi.Settings = append(bi.Settings, BuildSetting{Key: key, Value: val}) } + lineNum++ } - return info, true + return nil } diff --git a/libgo/go/runtime/debug/panic_test.go b/libgo/go/runtime/debug/panic_test.go index 65f9555..ec5294c 100644 --- a/libgo/go/runtime/debug/panic_test.go +++ b/libgo/go/runtime/debug/panic_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd -// +build aix darwin dragonfly freebsd linux netbsd openbsd // TODO: test on Windows? |