Skip to content

Commit 337b68d

Browse files
committed
cmd/go: abstract build cache, support implementations via child process
Via setting GOCACHEPROG to a binary which speaks JSON over stdin/stdout. Updates golang#59719 Signed-off-by: Brad Fitzpatrick <[email protected]> Change-Id: I824ff04d5ebdf0ba4d1b5bc2e9fbaee26d34c80f
1 parent 903a25a commit 337b68d

File tree

8 files changed

+484
-54
lines changed

8 files changed

+484
-54
lines changed

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

+74-22
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,50 @@ type ActionID [HashSize]byte
3131
// An OutputID is a cache output key, the hash of an output of a computation.
3232
type OutputID [HashSize]byte
3333

34+
// Cache is the interface as used by the cmd/go.
35+
type Cache interface {
36+
// Get returns the cache entry for the provided ActionID.
37+
// On miss, the error type should be of type *entryNotFoundError.
38+
//
39+
// After a success call to Get, OutputFile(Entry.OutputID) must
40+
// exist on disk for until Close is called (at the end of the process).
41+
Get(ActionID) (Entry, error)
42+
43+
// Put adds an item to the cache.
44+
//
45+
// The seeker is only used to seek to the beginning. After a call to Put,
46+
// the seek position is not guaranteed to be in any particular state.
47+
//
48+
// As a special case, if the ReadSeeker is of type noVerifyReadSeeker,
49+
// the verification from GODEBUG=goverifycache=1 is skipped.
50+
//
51+
// After a success call to Get, OutputFile(Entry.OutputID) must
52+
// exist on disk for until Close is called (at the end of the process).
53+
Put(ActionID, io.ReadSeeker) (_ OutputID, size int64, _ error)
54+
55+
// Close is called at the end of the go process. Implementations can do
56+
// cache cleanup work at this phase, or wait for and report any errors from
57+
// background cleanup work started earlier. Any cache trimming should in one
58+
// process should not violate cause the invariants of this interface to be
59+
// violated in another process. Namely, a cache trim from one process should
60+
// not delete an ObjectID from disk that was recently Get or Put from
61+
// another process. As a rule of thumb, don't trim things used in the last
62+
// day.
63+
Close() error
64+
65+
// OutputFile returns the path on disk where OutputID is stored.
66+
//
67+
// It's only called after a successful get or put call so it doesn't need
68+
// to return an error; it's assumed that if the previous get or put succeeded,
69+
// it's already on disk.
70+
OutputFile(OutputID) string
71+
72+
// FuzzDir returns where fuzz files are stored.
73+
FuzzDir() string
74+
}
75+
3476
// A Cache is a package cache, backed by a file system directory tree.
35-
type Cache struct {
77+
type DiskCache struct {
3678
dir string
3779
now func() time.Time
3880
}
@@ -48,7 +90,7 @@ type Cache struct {
4890
// to share a cache directory (for example, if the directory were stored
4991
// in a network file system). File locking is notoriously unreliable in
5092
// network file systems and may not suffice to protect the cache.
51-
func Open(dir string) (*Cache, error) {
93+
func Open(dir string) (*DiskCache, error) {
5294
info, err := os.Stat(dir)
5395
if err != nil {
5496
return nil, err
@@ -62,15 +104,15 @@ func Open(dir string) (*Cache, error) {
62104
return nil, err
63105
}
64106
}
65-
c := &Cache{
107+
c := &DiskCache{
66108
dir: dir,
67109
now: time.Now,
68110
}
69111
return c, nil
70112
}
71113

72114
// fileName returns the name of the file corresponding to the given id.
73-
func (c *Cache) fileName(id [HashSize]byte, key string) string {
115+
func (c *DiskCache) fileName(id [HashSize]byte, key string) string {
74116
return filepath.Join(c.dir, fmt.Sprintf("%02x", id[0]), fmt.Sprintf("%x", id)+"-"+key)
75117
}
76118

@@ -136,7 +178,7 @@ func initEnv() {
136178
// returning the corresponding output ID and file size, if any.
137179
// Note that finding an output ID does not guarantee that the
138180
// saved file for that output ID is still available.
139-
func (c *Cache) Get(id ActionID) (Entry, error) {
181+
func (c *DiskCache) Get(id ActionID) (Entry, error) {
140182
if verify {
141183
return Entry{}, &entryNotFoundError{Err: errVerifyMode}
142184
}
@@ -150,7 +192,7 @@ type Entry struct {
150192
}
151193

152194
// get is Get but does not respect verify mode, so that Put can use it.
153-
func (c *Cache) get(id ActionID) (Entry, error) {
195+
func (c *DiskCache) get(id ActionID) (Entry, error) {
154196
missing := func(reason error) (Entry, error) {
155197
return Entry{}, &entryNotFoundError{Err: reason}
156198
}
@@ -214,7 +256,7 @@ func (c *Cache) get(id ActionID) (Entry, error) {
214256

215257
// GetFile looks up the action ID in the cache and returns
216258
// the name of the corresponding data file.
217-
func (c *Cache) GetFile(id ActionID) (file string, entry Entry, err error) {
259+
func GetFile(c Cache, id ActionID) (file string, entry Entry, err error) {
218260
entry, err = c.Get(id)
219261
if err != nil {
220262
return "", Entry{}, err
@@ -233,7 +275,7 @@ func (c *Cache) GetFile(id ActionID) (file string, entry Entry, err error) {
233275
// GetBytes looks up the action ID in the cache and returns
234276
// the corresponding output bytes.
235277
// GetBytes should only be used for data that can be expected to fit in memory.
236-
func (c *Cache) GetBytes(id ActionID) ([]byte, Entry, error) {
278+
func GetBytes(c Cache, id ActionID) ([]byte, Entry, error) {
237279
entry, err := c.Get(id)
238280
if err != nil {
239281
return nil, entry, err
@@ -248,7 +290,7 @@ func (c *Cache) GetBytes(id ActionID) ([]byte, Entry, error) {
248290
// GetMmap looks up the action ID in the cache and returns
249291
// the corresponding output bytes.
250292
// GetMmap should only be used for data that can be expected to fit in memory.
251-
func (c *Cache) GetMmap(id ActionID) ([]byte, Entry, error) {
293+
func GetMmap(c Cache, id ActionID) ([]byte, Entry, error) {
252294
entry, err := c.Get(id)
253295
if err != nil {
254296
return nil, entry, err
@@ -264,7 +306,7 @@ func (c *Cache) GetMmap(id ActionID) ([]byte, Entry, error) {
264306
}
265307

266308
// OutputFile returns the name of the cache file storing output with the given OutputID.
267-
func (c *Cache) OutputFile(out OutputID) string {
309+
func (c *DiskCache) OutputFile(out OutputID) string {
268310
file := c.fileName(out, "d")
269311
c.used(file)
270312
return file
@@ -297,16 +339,18 @@ const (
297339
// mtime is more than an hour old. This heuristic eliminates
298340
// nearly all of the mtime updates that would otherwise happen,
299341
// while still keeping the mtimes useful for cache trimming.
300-
func (c *Cache) used(file string) {
342+
func (c *DiskCache) used(file string) {
301343
info, err := os.Stat(file)
302344
if err == nil && c.now().Sub(info.ModTime()) < mtimeInterval {
303345
return
304346
}
305347
os.Chtimes(file, c.now(), c.now())
306348
}
307349

350+
func (c *DiskCache) Close() error { return c.Trim() }
351+
308352
// Trim removes old cache entries that are likely not to be reused.
309-
func (c *Cache) Trim() error {
353+
func (c *DiskCache) Trim() error {
310354
now := c.now()
311355

312356
// We maintain in dir/trim.txt the time of the last completed cache trim.
@@ -346,7 +390,7 @@ func (c *Cache) Trim() error {
346390
}
347391

348392
// trimSubdir trims a single cache subdirectory.
349-
func (c *Cache) trimSubdir(subdir string, cutoff time.Time) {
393+
func (c *DiskCache) trimSubdir(subdir string, cutoff time.Time) {
350394
// Read all directory entries from subdir before removing
351395
// any files, in case removing files invalidates the file offset
352396
// in the directory scan. Also, ignore error from f.Readdirnames,
@@ -374,7 +418,7 @@ func (c *Cache) trimSubdir(subdir string, cutoff time.Time) {
374418

375419
// putIndexEntry adds an entry to the cache recording that executing the action
376420
// with the given id produces an output with the given output id (hash) and size.
377-
func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify bool) error {
421+
func (c *DiskCache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify bool) error {
378422
// Note: We expect that for one reason or another it may happen
379423
// that repeating an action produces a different output hash
380424
// (for example, if the output contains a time stamp or temp dir name).
@@ -428,21 +472,29 @@ func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify
428472
return nil
429473
}
430474

475+
// noVerifyReadSeeker is a io.ReadSeeker wrapper sentinel type
476+
// that says that Cache.Put should skip the verify check
477+
// (from GODEBUG=goverifycache=1).
478+
type noVerifyReadSeeker struct {
479+
io.ReadSeeker
480+
}
481+
431482
// Put stores the given output in the cache as the output for the action ID.
432483
// It may read file twice. The content of file must not change between the two passes.
433-
func (c *Cache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
434-
return c.put(id, file, true)
484+
func (c *DiskCache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
485+
_, isNoVerify := file.(noVerifyReadSeeker)
486+
return c.put(id, file, !isNoVerify)
435487
}
436488

437489
// PutNoVerify is like Put but disables the verify check
438490
// when GODEBUG=goverifycache=1 is set.
439491
// It is meant for data that is OK to cache but that we expect to vary slightly from run to run,
440492
// like test output containing times and the like.
441-
func (c *Cache) PutNoVerify(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
442-
return c.put(id, file, false)
493+
func PutNoVerify(c Cache, id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
494+
return c.Put(id, noVerifyReadSeeker{file})
443495
}
444496

445-
func (c *Cache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID, int64, error) {
497+
func (c *DiskCache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID, int64, error) {
446498
// Compute output ID.
447499
h := sha256.New()
448500
if _, err := file.Seek(0, 0); err != nil {
@@ -465,14 +517,14 @@ func (c *Cache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID
465517
}
466518

467519
// PutBytes stores the given bytes in the cache as the output for the action ID.
468-
func (c *Cache) PutBytes(id ActionID, data []byte) error {
520+
func PutBytes(c Cache, id ActionID, data []byte) error {
469521
_, _, err := c.Put(id, bytes.NewReader(data))
470522
return err
471523
}
472524

473525
// copyFile copies file into the cache, expecting it to have the given
474526
// output ID and size, if that file is not present already.
475-
func (c *Cache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
527+
func (c *DiskCache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
476528
name := c.fileName(out, "d")
477529
info, err := os.Stat(name)
478530
if err == nil && info.Size() == size {
@@ -562,6 +614,6 @@ func (c *Cache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
562614
// They may be removed with 'go clean -fuzzcache'.
563615
//
564616
// TODO(#48526): make Trim remove unused files from this directory.
565-
func (c *Cache) FuzzDir() string {
617+
func (c *DiskCache) FuzzDir() string {
566618
return filepath.Join(c.dir, "fuzz")
567619
}

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

+10-4
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ import (
1616

1717
// Default returns the default cache to use.
1818
// It never returns nil.
19-
func Default() *Cache {
19+
func Default() Cache {
2020
defaultOnce.Do(initDefaultCache)
2121
return defaultCache
2222
}
2323

2424
var (
2525
defaultOnce sync.Once
26-
defaultCache *Cache
26+
defaultCache Cache
2727
)
2828

2929
// cacheREADME is a message stored in a README in the cache directory.
@@ -53,11 +53,17 @@ func initDefaultCache() {
5353
os.WriteFile(filepath.Join(dir, "README"), []byte(cacheREADME), 0666)
5454
}
5555

56-
c, err := Open(dir)
56+
diskCache, err := Open(dir)
5757
if err != nil {
5858
base.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
5959
}
60-
defaultCache = c
60+
61+
if v := cfg.Getenv("GOCACHEPROG"); v != "" {
62+
defaultCache = startCacheProg(v, diskCache)
63+
return
64+
} else {
65+
defaultCache = diskCache
66+
}
6167
}
6268

6369
var (

0 commit comments

Comments
 (0)