Skip to content

Commit f96d725

Browse files
authored
dev: sync cache internals with go1.24 (#5576)
1 parent 161776b commit f96d725

24 files changed

+375
-451
lines changed

Diff for: internal/go/base/error_notunix.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !unix
6+
7+
package base
8+
9+
func IsETXTBSY(err error) bool {
10+
// syscall.ETXTBSY is only meaningful on Unix platforms.
11+
return false
12+
}

Diff for: internal/go/base/error_unix.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build unix
6+
7+
package base
8+
9+
import (
10+
"errors"
11+
"syscall"
12+
)
13+
14+
func IsETXTBSY(err error) bool {
15+
return errors.Is(err, syscall.ETXTBSY)
16+
}

Diff for: internal/go/base/readme.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# quoted
2+
3+
Extracted from `go/src/cmd/go/internal/base/` (related to `cache`).
4+
5+
Only the function `IsETXTBSY` is extracted.
6+
7+
## History
8+
9+
- https://github.com/golangci/golangci-lint/pull/5576
10+
- sync go1.24.1

Diff for: internal/go/cache/cache.go

+97-45
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import (
2323
"time"
2424

2525
"github.com/rogpeppe/go-internal/lockedfile"
26+
"github.com/rogpeppe/go-internal/robustio"
2627

28+
"github.com/golangci/golangci-lint/v2/internal/go/base"
2729
"github.com/golangci/golangci-lint/v2/internal/go/mmap"
28-
"github.com/golangci/golangci-lint/v2/internal/go/robustio"
2930
)
3031

3132
// An ActionID is a cache action key, the hash of a complete description of a
@@ -41,8 +42,8 @@ type Cache interface {
4142
// Get returns the cache entry for the provided ActionID.
4243
// On miss, the error type should be of type *entryNotFoundError.
4344
//
44-
// After a success call to Get, OutputFile(Entry.OutputID) must
45-
// exist on disk for until Close is called (at the end of the process).
45+
// After a successful call to Get, OutputFile(Entry.OutputID) must
46+
// exist on disk until Close is called (at the end of the process).
4647
Get(ActionID) (Entry, error)
4748

4849
// Put adds an item to the cache.
@@ -53,14 +54,14 @@ type Cache interface {
5354
// As a special case, if the ReadSeeker is of type noVerifyReadSeeker,
5455
// the verification from GODEBUG=goverifycache=1 is skipped.
5556
//
56-
// After a success call to Get, OutputFile(Entry.OutputID) must
57-
// exist on disk for until Close is called (at the end of the process).
57+
// After a successful call to Put, OutputFile(OutputID) must
58+
// exist on disk until Close is called (at the end of the process).
5859
Put(ActionID, io.ReadSeeker) (_ OutputID, size int64, _ error)
5960

6061
// Close is called at the end of the go process. Implementations can do
6162
// cache cleanup work at this phase, or wait for and report any errors from
62-
// background cleanup work started earlier. Any cache trimming should in one
63-
// process should not violate cause the invariants of this interface to be
63+
// background cleanup work started earlier. Any cache trimming in one
64+
// process should not cause the invariants of this interface to be
6465
// violated in another process. Namely, a cache trim from one process should
6566
// not delete an ObjectID from disk that was recently Get or Put from
6667
// another process. As a rule of thumb, don't trim things used in the last
@@ -105,7 +106,7 @@ func Open(dir string) (*DiskCache, error) {
105106
}
106107
for i := 0; i < 256; i++ {
107108
name := filepath.Join(dir, fmt.Sprintf("%02x", i))
108-
if err := os.MkdirAll(name, 0744); err != nil {
109+
if err := os.MkdirAll(name, 0o777); err != nil {
109110
return nil, err
110111
}
111112
}
@@ -161,13 +162,13 @@ var errVerifyMode = errors.New("gocacheverify=1")
161162
var DebugTest = false
162163

163164
// func init() { initEnv() }
164-
165+
//
165166
// var (
166167
// gocacheverify = godebug.New("gocacheverify")
167168
// gocachehash = godebug.New("gocachehash")
168169
// gocachetest = godebug.New("gocachetest")
169170
// )
170-
171+
//
171172
// func initEnv() {
172173
// if gocacheverify.Value() == "1" {
173174
// gocacheverify.IncNonDefault()
@@ -258,10 +259,7 @@ func (c *DiskCache) get(id ActionID) (Entry, error) {
258259
return missing(errors.New("negative timestamp"))
259260
}
260261

261-
err = c.used(c.fileName(id, "a"))
262-
if err != nil {
263-
return Entry{}, fmt.Errorf("failed to mark %s as used: %w", c.fileName(id, "a"), err)
264-
}
262+
c.markUsed(c.fileName(id, "a"))
265263

266264
return Entry{buf, size, time.Unix(0, tm)}, nil
267265
}
@@ -305,25 +303,35 @@ func GetBytes(c Cache, id ActionID) ([]byte, Entry, error) {
305303
// GetMmap looks up the action ID in the cache and returns
306304
// the corresponding output bytes.
307305
// GetMmap should only be used for data that can be expected to fit in memory.
308-
func GetMmap(c Cache, id ActionID) ([]byte, Entry, error) {
306+
func GetMmap(c Cache, id ActionID) ([]byte, Entry, bool, error) {
309307
entry, err := c.Get(id)
310308
if err != nil {
311-
return nil, entry, err
309+
return nil, entry, false, err
312310
}
313-
md, err := mmap.Mmap(c.OutputFile(entry.OutputID))
311+
md, opened, err := mmap.Mmap(c.OutputFile(entry.OutputID))
314312
if err != nil {
315-
return nil, Entry{}, err
313+
return nil, Entry{}, opened, err
316314
}
317315
if int64(len(md.Data)) != entry.Size {
318-
return nil, Entry{}, &entryNotFoundError{Err: errors.New("file incomplete")}
316+
return nil, Entry{}, true, &entryNotFoundError{Err: errors.New("file incomplete")}
319317
}
320-
return md.Data, entry, nil
318+
return md.Data, entry, true, nil
321319
}
322320

323321
// OutputFile returns the name of the cache file storing output with the given OutputID.
324322
func (c *DiskCache) OutputFile(out OutputID) string {
325323
file := c.fileName(out, "d")
326-
c.used(file)
324+
isDir := c.markUsed(file)
325+
if isDir { // => cached executable
326+
entries, err := os.ReadDir(file)
327+
if err != nil {
328+
return fmt.Sprintf("DO NOT USE - missing binary cache entry: %v", err)
329+
}
330+
if len(entries) != 1 {
331+
return "DO NOT USE - invalid binary cache entry"
332+
}
333+
return filepath.Join(file, entries[0].Name())
334+
}
327335
return file
328336
}
329337

@@ -345,7 +353,7 @@ const (
345353
trimLimit = 5 * 24 * time.Hour
346354
)
347355

348-
// used makes a best-effort attempt to update mtime on file,
356+
// markUsed makes a best-effort attempt to update mtime on file,
349357
// so that mtime reflects cache access time.
350358
//
351359
// Because the reflection only needs to be approximate,
@@ -354,25 +362,17 @@ const (
354362
// mtime is more than an hour old. This heuristic eliminates
355363
// nearly all of the mtime updates that would otherwise happen,
356364
// while still keeping the mtimes useful for cache trimming.
357-
func (c *DiskCache) used(file string) error {
365+
//
366+
// markUsed reports whether the file is a directory (an executable cache entry).
367+
func (c *DiskCache) markUsed(file string) (isDir bool) {
358368
info, err := os.Stat(file)
359-
if err == nil && c.now().Sub(info.ModTime()) < mtimeInterval {
360-
return nil
361-
}
362-
363369
if err != nil {
364-
if os.IsNotExist(err) {
365-
return &entryNotFoundError{Err: err}
366-
}
367-
return &entryNotFoundError{Err: fmt.Errorf("failed to stat file %s: %w", file, err)}
370+
return false
368371
}
369-
370-
err = os.Chtimes(file, c.now(), c.now())
371-
if err != nil {
372-
return fmt.Errorf("failed to change time of file %s: %w", file, err)
372+
if now := c.now(); now.Sub(info.ModTime()) >= mtimeInterval {
373+
os.Chtimes(file, now, now)
373374
}
374-
375-
return nil
375+
return info.IsDir()
376376
}
377377

378378
func (c *DiskCache) Close() error { return c.Trim() }
@@ -410,7 +410,7 @@ func (c *DiskCache) Trim() error {
410410
// cache will appear older than it is, and we'll trim it again next time.
411411
var b bytes.Buffer
412412
fmt.Fprintf(&b, "%d", now.Unix())
413-
if err := lockedfile.Write(filepath.Join(c.dir, "trim.txt"), &b, 0666); err != nil {
413+
if err := lockedfile.Write(filepath.Join(c.dir, "trim.txt"), &b, 0o666); err != nil {
414414
return err
415415
}
416416

@@ -439,6 +439,10 @@ func (c *DiskCache) trimSubdir(subdir string, cutoff time.Time) {
439439
entry := filepath.Join(subdir, name)
440440
info, err := os.Stat(entry)
441441
if err == nil && info.ModTime().Before(cutoff) {
442+
if info.IsDir() { // executable cache entry
443+
os.RemoveAll(entry)
444+
continue
445+
}
442446
os.Remove(entry)
443447
}
444448
}
@@ -471,7 +475,7 @@ func (c *DiskCache) putIndexEntry(id ActionID, out OutputID, size int64, allowVe
471475

472476
// Copy file to cache directory.
473477
mode := os.O_WRONLY | os.O_CREATE
474-
f, err := os.OpenFile(file, mode, 0666)
478+
f, err := os.OpenFile(file, mode, 0o666)
475479
if err != nil {
476480
return err
477481
}
@@ -517,7 +521,21 @@ func (c *DiskCache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error
517521
if isNoVerify {
518522
file = wrapper.ReadSeeker
519523
}
520-
return c.put(id, file, !isNoVerify)
524+
return c.put(id, "", file, !isNoVerify)
525+
}
526+
527+
// PutExecutable is used to store the output as the output for the action ID into a
528+
// file with the given base name, with the executable mode bit set.
529+
// It may read file twice. The content of file must not change between the two passes.
530+
func (c *DiskCache) PutExecutable(id ActionID, name string, file io.ReadSeeker) (OutputID, int64, error) {
531+
if name == "" {
532+
panic("PutExecutable called without a name")
533+
}
534+
wrapper, isNoVerify := file.(noVerifyReadSeeker)
535+
if isNoVerify {
536+
file = wrapper.ReadSeeker
537+
}
538+
return c.put(id, name, file, !isNoVerify)
521539
}
522540

523541
// PutNoVerify is like Put but disables the verify check
@@ -528,7 +546,7 @@ func PutNoVerify(c Cache, id ActionID, file io.ReadSeeker) (OutputID, int64, err
528546
return c.Put(id, noVerifyReadSeeker{file})
529547
}
530548

531-
func (c *DiskCache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID, int64, error) {
549+
func (c *DiskCache) put(id ActionID, executableName string, file io.ReadSeeker, allowVerify bool) (OutputID, int64, error) {
532550
// Compute output ID.
533551
h := sha256.New()
534552
if _, err := file.Seek(0, 0); err != nil {
@@ -542,7 +560,11 @@ func (c *DiskCache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (Outp
542560
h.Sum(out[:0])
543561

544562
// Copy to cached output file (if not already present).
545-
if err := c.copyFile(file, out, size); err != nil {
563+
fileMode := fs.FileMode(0o666)
564+
if executableName != "" {
565+
fileMode = 0o777
566+
}
567+
if err := c.copyFile(file, executableName, out, size, fileMode); err != nil {
546568
return out, size, err
547569
}
548570

@@ -558,9 +580,33 @@ func PutBytes(c Cache, id ActionID, data []byte) error {
558580

559581
// copyFile copies file into the cache, expecting it to have the given
560582
// output ID and size, if that file is not present already.
561-
func (c *DiskCache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
562-
name := c.fileName(out, "d")
583+
func (c *DiskCache) copyFile(file io.ReadSeeker, executableName string, out OutputID, size int64, perm os.FileMode) error {
584+
name := c.fileName(out, "d") // TODO(matloob): use a different suffix for the executable cache?
563585
info, err := os.Stat(name)
586+
if executableName != "" {
587+
// This is an executable file. The file at name won't hold the output itself, but will
588+
// be a directory that holds the output, named according to executableName. Check to see
589+
// if the directory already exists, and if it does not, create it. Then reset name
590+
// to the name we want the output written to.
591+
if err != nil {
592+
if !os.IsNotExist(err) {
593+
return err
594+
}
595+
if err := os.Mkdir(name, 0o777); err != nil {
596+
return err
597+
}
598+
if info, err = os.Stat(name); err != nil {
599+
return err
600+
}
601+
}
602+
if !info.IsDir() {
603+
return errors.New("internal error: invalid binary cache entry: not a directory")
604+
}
605+
606+
// directory exists. now set name to the inner file
607+
name = filepath.Join(name, executableName)
608+
info, err = os.Stat(name)
609+
}
564610
if err == nil && info.Size() == size {
565611
// Check hash.
566612
if f, err := os.Open(name); err == nil {
@@ -585,8 +631,14 @@ func (c *DiskCache) copyFile(file io.ReadSeeker, out OutputID, size int64) error
585631
if err == nil && info.Size() > size { // shouldn't happen but fix in case
586632
mode |= os.O_TRUNC
587633
}
588-
f, err := os.OpenFile(name, mode, 0666)
634+
f, err := os.OpenFile(name, mode, perm)
589635
if err != nil {
636+
if base.IsETXTBSY(err) {
637+
// This file is being used by an executable. It must have
638+
// already been written by another go process and then run.
639+
// return without an error.
640+
return nil
641+
}
590642
return err
591643
}
592644
defer f.Close()

Diff for: internal/go/cache/cache_test.go

+6-27
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,14 @@ func init() {
2121
}
2222

2323
func TestBasic(t *testing.T) {
24-
dir, err := os.MkdirTemp("", "cachetest-")
25-
if err != nil {
26-
t.Fatal(err)
27-
}
28-
defer os.RemoveAll(dir)
29-
_, err = Open(filepath.Join(dir, "notexist"))
24+
dir := t.TempDir()
25+
_, err := Open(filepath.Join(dir, "notexist"))
3026
if err == nil {
3127
t.Fatal(`Open("tmp/notexist") succeeded, want failure`)
3228
}
3329

3430
cdir := filepath.Join(dir, "c1")
35-
if err := os.Mkdir(cdir, 0744); err != nil {
31+
if err := os.Mkdir(cdir, 0777); err != nil {
3632
t.Fatal(err)
3733
}
3834

@@ -66,13 +62,7 @@ func TestBasic(t *testing.T) {
6662
}
6763

6864
func TestGrowth(t *testing.T) {
69-
dir, err := os.MkdirTemp("", "cachetest-")
70-
if err != nil {
71-
t.Fatal(err)
72-
}
73-
defer os.RemoveAll(dir)
74-
75-
c, err := Open(dir)
65+
c, err := Open(t.TempDir())
7666
if err != nil {
7767
t.Fatalf("Open: %v", err)
7868
}
@@ -119,13 +109,7 @@ func TestGrowth(t *testing.T) {
119109
// t.Fatal("initEnv did not set verify")
120110
// }
121111
//
122-
// dir, err := os.MkdirTemp("", "cachetest-")
123-
// if err != nil {
124-
// t.Fatal(err)
125-
// }
126-
// defer os.RemoveAll(dir)
127-
//
128-
// c, err := Open(dir)
112+
// c, err := Open(t.TempDir())
129113
// if err != nil {
130114
// t.Fatalf("Open: %v", err)
131115
// }
@@ -152,12 +136,7 @@ func dummyID(x int) [HashSize]byte {
152136
}
153137

154138
func TestCacheTrim(t *testing.T) {
155-
dir, err := os.MkdirTemp("", "cachetest-")
156-
if err != nil {
157-
t.Fatal(err)
158-
}
159-
defer os.RemoveAll(dir)
160-
139+
dir := t.TempDir()
161140
c, err := Open(dir)
162141
if err != nil {
163142
t.Fatalf("Open: %v", err)

0 commit comments

Comments
 (0)