@@ -91,6 +91,7 @@ const maxConcurrentRequests = 1000
9191// DiskCache is a local disk LRU CAS and Action Cache cache.
9292type DiskCache struct {
9393 root string // path to the root directory of the disk cache.
94+ prefixes sync.Map // set of existing 2-character digest prefixes.
9495 maxCapacityBytes uint64 // if disk size exceeds this, old items will be evicted as needed.
9596 mu sync.Mutex // protects the queue.
9697 store sync.Map // map of keys to qitems.
@@ -125,15 +126,18 @@ func New(ctx context.Context, root string, maxCapacityBytes uint64) *DiskCache {
125126 if d .IsDir () {
126127 return nil
127128 }
128- fname := d .Name ()
129- k , err := res .getKeyFromFileName (fname )
129+ // We use Git's directory/file naming structure as inspiration:
130+ // https://git-scm.com/book/en/v2/Git-Internals-Git-Objects#:~:text=The%20subdirectory%20is%20named%20with%20the%20first%202%20characters%20of%20the%20SHA%2D1%2C%20and%20the%20filename%20is%20the%20remaining%2038%20characters.
131+ subdir := filepath .Base (filepath .Dir (path ))
132+ res .prefixes .Store (subdir , true )
133+ k , err := res .getKeyFromFileName (subdir + d .Name ())
130134 if err != nil {
131- log .Errorf ("Error parsing cached file name %s: %v" , fname , err )
135+ log .Errorf ("Error parsing cached file name %s: %v" , path , err )
132136 return nil
133137 }
134- atime , err := GetLastAccessTime (filepath . Join ( root , fname ) )
138+ atime , err := GetLastAccessTime (path )
135139 if err != nil {
136- log .Errorf ("Error getting last accessed time of %s: %v" , fname , err )
140+ log .Errorf ("Error getting last accessed time of %s: %v" , path , err )
137141 return nil
138142 }
139143 it := & qitem {
@@ -142,7 +146,7 @@ func New(ctx context.Context, root string, maxCapacityBytes uint64) *DiskCache {
142146 }
143147 size , err := res .getItemSize (k )
144148 if err != nil {
145- log .Errorf ("Error getting file size of %s: %v" , fname , err )
149+ log .Errorf ("Error getting file size of %s: %v" , path , err )
146150 return nil
147151 }
148152 res .store .Store (k , it )
@@ -175,25 +179,25 @@ func (d *DiskCache) TotalSizeBytes() uint64 {
175179 return uint64 (atomic .LoadInt64 (& d .sizeBytes ))
176180}
177181
178- // This function is defined in https://pkg.go.dev/strings#CutPrefix
182+ // This function is defined in https://pkg.go.dev/strings#CutSuffix
179183// It is copy/pasted here as a hack, because I failed to upgrade the *Reclient* repo to the latest Go 1.20.7.
180- func CutPrefix (s , prefix string ) (after string , found bool ) {
181- if ! strings .HasPrefix (s , prefix ) {
184+ func CutSuffix (s , suffix string ) (before string , found bool ) {
185+ if ! strings .HasSuffix (s , suffix ) {
182186 return s , false
183187 }
184- return s [len (prefix ): ], true
188+ return s [: len (s ) - len ( suffix ) ], true
185189}
186190
187191func (d * DiskCache ) getKeyFromFileName (fname string ) (key , error ) {
188192 pair := strings .Split (fname , "." )
189193 if len (pair ) != 2 {
190- return key {}, fmt .Errorf ("Expected file name in the form [ac_]hash/size, got %s" , fname )
194+ return key {}, fmt .Errorf ("expected file name in the form [ac_]hash/size, got %s" , fname )
191195 }
192196 size , err := strconv .ParseInt (pair [1 ], 10 , 64 )
193197 if err != nil {
194198 return key {}, fmt .Errorf ("invalid size in digest %s: %s" , fname , err )
195199 }
196- hash , isAc := CutPrefix (pair [0 ], "ac_" )
200+ hash , isAc := CutSuffix (pair [0 ], "ac_" )
197201 dg , err := digest .New (hash , size )
198202 if err != nil {
199203 return key {}, fmt .Errorf ("invalid digest from file name %s: %v" , fname , err )
@@ -202,11 +206,18 @@ func (d *DiskCache) getKeyFromFileName(fname string) (key, error) {
202206}
203207
204208func (d * DiskCache ) getPath (k key ) string {
205- prefix := ""
209+ suffix := ""
206210 if ! k .isCas {
207- prefix = "ac_"
211+ suffix = "_ac"
212+ }
213+ return filepath .Join (d .root , k .digest .Hash [:2 ], fmt .Sprintf ("%s%s.%d" , k .digest .Hash [2 :], suffix , k .digest .Size ))
214+ }
215+
216+ func (d * DiskCache ) maybeCreatePrefixDir (k key ) {
217+ prefix := k .digest .Hash [:2 ]
218+ if _ , loaded := d .prefixes .LoadOrStore (prefix , true ); ! loaded {
219+ os .MkdirAll (filepath .Join (d .root , prefix ), os .ModePerm )
208220 }
209- return filepath .Join (d .root , fmt .Sprintf ("%s%s.%d" , prefix , k .digest .Hash , k .digest .Size ))
210221}
211222
212223func (d * DiskCache ) StoreCas (dg digest.Digest , path string ) error {
@@ -226,6 +237,7 @@ func (d *DiskCache) StoreCas(dg digest.Digest, path string) error {
226237 d .mu .Lock ()
227238 heap .Push (d .queue , it )
228239 d .mu .Unlock ()
240+ d .maybeCreatePrefixDir (it .key )
229241 if err := copyFile (path , d .getPath (it .key ), dg .Size ); err != nil {
230242 return err
231243 }
@@ -259,6 +271,7 @@ func (d *DiskCache) StoreActionCache(dg digest.Digest, ar *repb.ActionResult) er
259271 d .mu .Lock ()
260272 heap .Push (d .queue , it )
261273 d .mu .Unlock ()
274+ d .maybeCreatePrefixDir (it .key )
262275 if err := os .WriteFile (d .getPath (it .key ), bytes , 0644 ); err != nil {
263276 return err
264277 }
@@ -292,6 +305,7 @@ func (d *DiskCache) gc() {
292305 }
293306 atomic .AddInt64 (& d .sizeBytes , - size )
294307 it .mu .Lock ()
308+ // We only delete the files, and not the prefix directories, because the prefixes are not worth worrying about.
295309 if err := os .Remove (d .getPath (it .key )); err != nil {
296310 log .Errorf ("Error removing file: %v" , err )
297311 }
0 commit comments