@@ -23,6 +23,7 @@ import (
2323 "time"
2424
2525 "github.com/golangci/golangci-lint/internal/mmap"
26+ "github.com/golangci/golangci-lint/internal/robustio"
2627 "github.com/rogpeppe/go-internal/lockedfile"
2728)
2829
@@ -34,8 +35,50 @@ type ActionID [HashSize]byte
3435// An OutputID is a cache output key, the hash of an output of a computation.
3536type OutputID [HashSize ]byte
3637
38+ // Cache is the interface as used by the cmd/go.
39+ type Cache interface {
40+ // Get returns the cache entry for the provided ActionID.
41+ // On miss, the error type should be of type *entryNotFoundError.
42+ //
43+ // After a success call to Get, OutputFile(Entry.OutputID) must
44+ // exist on disk for until Close is called (at the end of the process).
45+ Get (ActionID ) (Entry , error )
46+
47+ // Put adds an item to the cache.
48+ //
49+ // The seeker is only used to seek to the beginning. After a call to Put,
50+ // the seek position is not guaranteed to be in any particular state.
51+ //
52+ // As a special case, if the ReadSeeker is of type noVerifyReadSeeker,
53+ // the verification from GODEBUG=goverifycache=1 is skipped.
54+ //
55+ // After a success call to Get, OutputFile(Entry.OutputID) must
56+ // exist on disk for until Close is called (at the end of the process).
57+ Put (ActionID , io.ReadSeeker ) (_ OutputID , size int64 , _ error )
58+
59+ // Close is called at the end of the go process. Implementations can do
60+ // cache cleanup work at this phase, or wait for and report any errors from
61+ // background cleanup work started earlier. Any cache trimming should in one
62+ // process should not violate cause the invariants of this interface to be
63+ // violated in another process. Namely, a cache trim from one process should
64+ // not delete an ObjectID from disk that was recently Get or Put from
65+ // another process. As a rule of thumb, don't trim things used in the last
66+ // day.
67+ Close () error
68+
69+ // OutputFile returns the path on disk where OutputID is stored.
70+ //
71+ // It's only called after a successful get or put call so it doesn't need
72+ // to return an error; it's assumed that if the previous get or put succeeded,
73+ // it's already on disk.
74+ OutputFile (OutputID ) string
75+
76+ // FuzzDir returns where fuzz files are stored.
77+ FuzzDir () string
78+ }
79+
3780// A Cache is a package cache, backed by a file system directory tree.
38- type Cache struct {
81+ type DiskCache struct {
3982 dir string
4083 now func () time.Time
4184}
@@ -51,7 +94,7 @@ type Cache struct {
5194// to share a cache directory (for example, if the directory were stored
5295// in a network file system). File locking is notoriously unreliable in
5396// network file systems and may not suffice to protect the cache.
54- func Open (dir string ) (* Cache , error ) {
97+ func Open (dir string ) (* DiskCache , error ) {
5598 info , err := os .Stat (dir )
5699 if err != nil {
57100 return nil , err
@@ -65,15 +108,15 @@ func Open(dir string) (*Cache, error) {
65108 return nil , err
66109 }
67110 }
68- c := & Cache {
111+ c := & DiskCache {
69112 dir : dir ,
70113 now : time .Now ,
71114 }
72115 return c , nil
73116}
74117
75118// fileName returns the name of the file corresponding to the given id.
76- func (c * Cache ) fileName (id [HashSize ]byte , key string ) string {
119+ func (c * DiskCache ) fileName (id [HashSize ]byte , key string ) string {
77120 return filepath .Join (c .dir , fmt .Sprintf ("%02x" , id [0 ]), fmt .Sprintf ("%x" , id )+ "-" + key )
78121}
79122
@@ -116,30 +159,34 @@ var errVerifyMode = errors.New("gocacheverify=1")
116159// DebugTest is set when GODEBUG=gocachetest=1 is in the environment.
117160var DebugTest = false
118161
119- func init () { initEnv () }
120-
121- func initEnv () {
122- verify = false
123- debugHash = false
124- debug := strings .Split (os .Getenv ("GODEBUG" ), "," )
125- for _ , f := range debug {
126- if f == "gocacheverify=1" {
127- verify = true
128- }
129- if f == "gocachehash=1" {
130- debugHash = true
131- }
132- if f == "gocachetest=1" {
133- DebugTest = true
134- }
135- }
136- }
162+ // func init() { initEnv() }
163+
164+ // var (
165+ // goCacheVerify = godebug.New("gocacheverify")
166+ // goDebugHash = godebug.New("gocachehash")
167+ // goCacheTest = godebug.New("gocachetest")
168+ // )
169+
170+ // func initEnv() {
171+ // if goCacheVerify.Value() == "1" {
172+ // goCacheVerify.IncNonDefault()
173+ // verify = true
174+ // }
175+ // if goDebugHash.Value() == "1" {
176+ // goDebugHash.IncNonDefault()
177+ // debugHash = true
178+ // }
179+ // if goCacheTest.Value() == "1" {
180+ // goCacheTest.IncNonDefault()
181+ // DebugTest = true
182+ // }
183+ // }
137184
138185// Get looks up the action ID in the cache,
139186// returning the corresponding output ID and file size, if any.
140187// Note that finding an output ID does not guarantee that the
141188// saved file for that output ID is still available.
142- func (c * Cache ) Get (id ActionID ) (Entry , error ) {
189+ func (c * DiskCache ) Get (id ActionID ) (Entry , error ) {
143190 if verify {
144191 return Entry {}, & entryNotFoundError {Err : errVerifyMode }
145192 }
@@ -149,11 +196,11 @@ func (c *Cache) Get(id ActionID) (Entry, error) {
149196type Entry struct {
150197 OutputID OutputID
151198 Size int64
152- Time time.Time
199+ Time time.Time // when added to cache
153200}
154201
155202// get is Get but does not respect verify mode, so that Put can use it.
156- func (c * Cache ) get (id ActionID ) (Entry , error ) {
203+ func (c * DiskCache ) get (id ActionID ) (Entry , error ) {
157204 missing := func (reason error ) (Entry , error ) {
158205 return Entry {}, & entryNotFoundError {Err : reason }
159206 }
@@ -220,17 +267,12 @@ func (c *Cache) get(id ActionID) (Entry, error) {
220267
221268// GetFile looks up the action ID in the cache and returns
222269// the name of the corresponding data file.
223- func (c * Cache ) GetFile ( id ActionID ) (file string , entry Entry , err error ) {
270+ func GetFile (c Cache , id ActionID ) (file string , entry Entry , err error ) {
224271 entry , err = c .Get (id )
225272 if err != nil {
226273 return "" , Entry {}, err
227274 }
228-
229- file , err = c .OutputFile (entry .OutputID )
230- if err != nil {
231- return "" , Entry {}, err
232- }
233-
275+ file = c .OutputFile (entry .OutputID )
234276 info , err := os .Stat (file )
235277 if err != nil {
236278 return "" , Entry {}, & entryNotFoundError {Err : err }
@@ -244,12 +286,12 @@ func (c *Cache) GetFile(id ActionID) (file string, entry Entry, err error) {
244286// GetBytes looks up the action ID in the cache and returns
245287// the corresponding output bytes.
246288// GetBytes should only be used for data that can be expected to fit in memory.
247- func (c * Cache ) GetBytes ( id ActionID ) ([]byte , Entry , error ) {
289+ func GetBytes (c Cache , id ActionID ) ([]byte , Entry , error ) {
248290 entry , err := c .Get (id )
249291 if err != nil {
250292 return nil , entry , err
251293 }
252- data , err := c . readFileCGIL (c .OutputFile (entry .OutputID ))
294+ data , err := robustio . ReadFile (c .OutputFile (entry .OutputID ))
253295 if err != nil {
254296 return nil , entry , err
255297 }
@@ -262,16 +304,12 @@ func (c *Cache) GetBytes(id ActionID) ([]byte, Entry, error) {
262304// GetMmap looks up the action ID in the cache and returns
263305// the corresponding output bytes.
264306// GetMmap should only be used for data that can be expected to fit in memory.
265- func (c * Cache ) GetMmap ( id ActionID ) ([]byte , Entry , error ) {
307+ func GetMmap (c Cache , id ActionID ) ([]byte , Entry , error ) {
266308 entry , err := c .Get (id )
267309 if err != nil {
268310 return nil , entry , err
269311 }
270- outputFile , err := c .OutputFile (entry .OutputID )
271- if err != nil {
272- return nil , entry , err
273- }
274- md , err := mmap .Mmap (outputFile )
312+ md , err := mmap .Mmap (c .OutputFile (entry .OutputID ))
275313 if err != nil {
276314 return nil , Entry {}, err
277315 }
@@ -282,13 +320,10 @@ func (c *Cache) GetMmap(id ActionID) ([]byte, Entry, error) {
282320}
283321
284322// OutputFile returns the name of the cache file storing output with the given OutputID.
285- func (c * Cache ) OutputFile (out OutputID ) ( string , error ) {
323+ func (c * DiskCache ) OutputFile (out OutputID ) string {
286324 file := c .fileName (out , "d" )
287- err := c .used (file )
288- if err != nil {
289- return "" , err
290- }
291- return file , nil
325+ c .used (file )
326+ return file
292327}
293328
294329// Time constants for cache expiration.
@@ -318,7 +353,7 @@ const (
318353// mtime is more than an hour old. This heuristic eliminates
319354// nearly all of the mtime updates that would otherwise happen,
320355// while still keeping the mtimes useful for cache trimming.
321- func (c * Cache ) used (file string ) error {
356+ func (c * DiskCache ) used (file string ) error {
322357 info , err := os .Stat (file )
323358 if err == nil && c .now ().Sub (info .ModTime ()) < mtimeInterval {
324359 return nil
@@ -339,8 +374,10 @@ func (c *Cache) used(file string) error {
339374 return nil
340375}
341376
377+ func (c * DiskCache ) Close () error { return c .Trim () }
378+
342379// Trim removes old cache entries that are likely not to be reused.
343- func (c * Cache ) Trim () {
380+ func (c * DiskCache ) Trim () error {
344381 now := c .now ()
345382
346383 // We maintain in dir/trim.txt the time of the last completed cache trim.
@@ -354,7 +391,7 @@ func (c *Cache) Trim() {
354391 if t , err := strconv .ParseInt (strings .TrimSpace (string (data )), 10 , 64 ); err == nil {
355392 lastTrim := time .Unix (t , 0 )
356393 if d := now .Sub (lastTrim ); d < trimInterval && d > - mtimeInterval {
357- return
394+ return nil
358395 }
359396 }
360397 }
@@ -373,12 +410,14 @@ func (c *Cache) Trim() {
373410 var b bytes.Buffer
374411 fmt .Fprintf (& b , "%d" , now .Unix ())
375412 if err := lockedfile .Write (filepath .Join (c .dir , "trim.txt" ), & b , 0666 ); err != nil {
376- return
413+ return err
377414 }
415+
416+ return nil
378417}
379418
380419// trimSubdir trims a single cache subdirectory.
381- func (c * Cache ) trimSubdir (subdir string , cutoff time.Time ) {
420+ func (c * DiskCache ) trimSubdir (subdir string , cutoff time.Time ) {
382421 // Read all directory entries from subdir before removing
383422 // any files, in case removing files invalidates the file offset
384423 // in the directory scan. Also, ignore error from f.Readdirnames,
@@ -406,7 +445,7 @@ func (c *Cache) trimSubdir(subdir string, cutoff time.Time) {
406445
407446// putIndexEntry adds an entry to the cache recording that executing the action
408447// with the given id produces an output with the given output id (hash) and size.
409- func (c * Cache ) putIndexEntry (id ActionID , out OutputID , size int64 , allowVerify bool ) error {
448+ func (c * DiskCache ) putIndexEntry (id ActionID , out OutputID , size int64 , allowVerify bool ) error {
410449 // Note: We expect that for one reason or another it may happen
411450 // that repeating an action produces a different output hash
412451 // (for example, if the output contains a time stamp or temp dir name).
@@ -463,21 +502,32 @@ func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify
463502 return nil
464503}
465504
505+ // noVerifyReadSeeker is a io.ReadSeeker wrapper sentinel type
506+ // that says that Cache.Put should skip the verify check
507+ // (from GODEBUG=goverifycache=1).
508+ type noVerifyReadSeeker struct {
509+ io.ReadSeeker
510+ }
511+
466512// Put stores the given output in the cache as the output for the action ID.
467513// It may read file twice. The content of file must not change between the two passes.
468- func (c * Cache ) Put (id ActionID , file io.ReadSeeker ) (OutputID , int64 , error ) {
469- return c .put (id , file , true )
514+ func (c * DiskCache ) Put (id ActionID , file io.ReadSeeker ) (OutputID , int64 , error ) {
515+ wrapper , isNoVerify := file .(noVerifyReadSeeker )
516+ if isNoVerify {
517+ file = wrapper .ReadSeeker
518+ }
519+ return c .put (id , file , ! isNoVerify )
470520}
471521
472522// PutNoVerify is like Put but disables the verify check
473523// when GODEBUG=goverifycache=1 is set.
474524// It is meant for data that is OK to cache but that we expect to vary slightly from run to run,
475525// like test output containing times and the like.
476- func (c * Cache ) PutNoVerify ( id ActionID , file io.ReadSeeker ) (OutputID , int64 , error ) {
477- return c .put (id , file , false )
526+ func PutNoVerify (c Cache , id ActionID , file io.ReadSeeker ) (OutputID , int64 , error ) {
527+ return c .Put (id , noVerifyReadSeeker { file } )
478528}
479529
480- func (c * Cache ) put (id ActionID , file io.ReadSeeker , allowVerify bool ) (OutputID , int64 , error ) {
530+ func (c * DiskCache ) put (id ActionID , file io.ReadSeeker , allowVerify bool ) (OutputID , int64 , error ) {
481531 // Compute output ID.
482532 h := sha256 .New ()
483533 if _ , err := file .Seek (0 , 0 ); err != nil {
@@ -500,14 +550,14 @@ func (c *Cache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID
500550}
501551
502552// PutBytes stores the given bytes in the cache as the output for the action ID.
503- func (c * Cache ) PutBytes ( id ActionID , data []byte ) error {
553+ func PutBytes (c Cache , id ActionID , data []byte ) error {
504554 _ , _ , err := c .Put (id , bytes .NewReader (data ))
505555 return err
506556}
507557
508558// copyFile copies file into the cache, expecting it to have the given
509559// output ID and size, if that file is not present already.
510- func (c * Cache ) copyFile (file io.ReadSeeker , out OutputID , size int64 ) error {
560+ func (c * DiskCache ) copyFile (file io.ReadSeeker , out OutputID , size int64 ) error {
511561 name := c .fileName (out , "d" )
512562 info , err := os .Stat (name )
513563 if err == nil && info .Size () == size {
@@ -608,6 +658,6 @@ func (c *Cache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
608658// They may be removed with 'go clean -fuzzcache'.
609659//
610660// TODO(#48526): make Trim remove unused files from this directory.
611- func (c * Cache ) FuzzDir () string {
661+ func (c * DiskCache ) FuzzDir () string {
612662 return filepath .Join (c .dir , "fuzz" )
613663}
0 commit comments