Skip to content

Commit 6123209

Browse files
committed
cmd/go/internal/cache: always check error from stat in markUsed
markUsed was not checking that the error from os.Stat was nil before trying to access the FileInfo entry returned by it. Instead, always check the error and return false if it's non-nil (usually because the file does not exist). This can happen if an index entry exists in the cache, but the output entry it points to does not. markUsed is called at different points for the index entry and for the output entry, so it's possible for the index entry to be marked used, and then for another go process to trim the cache, deleting the output entry. I'm not sure how likely that is, or if this is what has been triggering the user observed instances of #70600, but it's enough for a test case. Fixes #70600 Change-Id: Ia6be14b4a56736d06488ccf93c3596fff8159f22 Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest,gotip-windows-amd64-longtest Reviewed-on: https://go-review.googlesource.com/c/go/+/633037 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 6f42fe9 commit 6123209

File tree

2 files changed

+51
-6
lines changed

2 files changed

+51
-6
lines changed

src/cmd/go/internal/cache/cache.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ func GetMmap(c Cache, id ActionID) ([]byte, Entry, error) {
314314
// OutputFile returns the name of the cache file storing output with the given OutputID.
315315
func (c *DiskCache) OutputFile(out OutputID) string {
316316
file := c.fileName(out, "d")
317-
isExecutable := c.markUsed(file)
318-
if isExecutable {
317+
isDir := c.markUsed(file)
318+
if isDir { // => cached executable
319319
entries, err := os.ReadDir(file)
320320
if err != nil {
321321
return fmt.Sprintf("DO NOT USE - missing binary cache entry: %v", err)
@@ -357,12 +357,14 @@ const (
357357
// while still keeping the mtimes useful for cache trimming.
358358
//
359359
// markUsed reports whether the file is a directory (an executable cache entry).
360-
func (c *DiskCache) markUsed(file string) (isExecutable bool) {
360+
func (c *DiskCache) markUsed(file string) (isDir bool) {
361361
info, err := os.Stat(file)
362-
if err == nil && c.now().Sub(info.ModTime()) < mtimeInterval {
363-
return info.IsDir()
362+
if err != nil {
363+
return false
364+
}
365+
if now := c.now(); now.Sub(info.ModTime()) >= mtimeInterval {
366+
os.Chtimes(file, now, now)
364367
}
365-
os.Chtimes(file, c.now(), c.now())
366368
return info.IsDir()
367369
}
368370

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Test that the go command does not panic if it tries to read
2+
# a file from the cache that has an index entry, but is missing
3+
# an entry for the output. This test creates that situation by
4+
# running a go list (creating index and output cache entries for
5+
# the module index) and then removing just the output entries.
6+
7+
[short] skip 'runs go build'
8+
9+
go build -o roe$GOEXE ./remove_output_entries.go
10+
11+
# populate new cache
12+
env GOCACHE=$WORK/newcache
13+
go list runtime
14+
15+
# remove output entries and check the panic doesn't happen
16+
exec ./roe$GOEXE $WORK/newcache
17+
go list runtime
18+
19+
-- remove_output_entries.go --
20+
package main
21+
22+
import (
23+
"io/fs"
24+
"log"
25+
"os"
26+
"path/filepath"
27+
"strings"
28+
)
29+
30+
func main() {
31+
cachedir := os.Args[1]
32+
err := filepath.WalkDir(cachedir, func(path string, d fs.DirEntry, err error) error {
33+
if strings.HasSuffix(path, "-d") { // output entries end in "-d"
34+
if err := os.RemoveAll(path); err != nil {
35+
return err
36+
}
37+
}
38+
return nil
39+
})
40+
if err != nil {
41+
log.Fatal(err)
42+
}
43+
}

0 commit comments

Comments
 (0)