@@ -7,14 +7,18 @@ import (
7
7
"os"
8
8
"path/filepath"
9
9
"runtime"
10
+ "sort"
10
11
"sync"
12
+ "time"
11
13
12
14
"github.com/go-openapi/strfmt"
13
15
16
+ "github.com/ActiveState/cli/internal/constants"
14
17
"github.com/ActiveState/cli/internal/errs"
15
18
"github.com/ActiveState/cli/internal/fileutils"
16
19
"github.com/ActiveState/cli/internal/installation/storage"
17
20
"github.com/ActiveState/cli/internal/logging"
21
+ configMediator "github.com/ActiveState/cli/internal/mediators/config"
18
22
"github.com/ActiveState/cli/internal/sliceutils"
19
23
"github.com/ActiveState/cli/internal/smartlink"
20
24
)
@@ -24,7 +28,8 @@ const (
24
28
)
25
29
26
30
type depotConfig struct {
27
- Deployments map [strfmt.UUID ][]deployment `json:"deployments"`
31
+ Deployments map [strfmt.UUID ][]deployment `json:"deployments"`
32
+ Cache map [strfmt.UUID ]* artifactInfo `json:"cache"`
28
33
}
29
34
30
35
type deployment struct {
@@ -41,6 +46,14 @@ const (
41
46
deploymentTypeCopy = "copy"
42
47
)
43
48
49
+ type artifactInfo struct {
50
+ InUse bool `json:"inUse"`
51
+ Size int64 `json:"size"`
52
+ LastAccessTime int64 `json:"lastAccessTime"`
53
+
54
+ id strfmt.UUID // for convenience when removing stale artifacts; should NOT have json tag
55
+ }
56
+
44
57
type ErrVolumeMismatch struct {
45
58
DepotVolume string
46
59
PathVolume string
@@ -55,8 +68,15 @@ type depot struct {
55
68
depotPath string
56
69
artifacts map [strfmt.UUID ]struct {}
57
70
fsMutex * sync.Mutex
71
+ cacheSize int64
72
+ }
73
+
74
+ func init () {
75
+ configMediator .RegisterOption (constants .RuntimeCacheSizeConfigKey , configMediator .Int , 500 )
58
76
}
59
77
78
+ const MB int64 = 1024 * 1024
79
+
60
80
func newDepot (runtimePath string ) (* depot , error ) {
61
81
depotPath := filepath .Join (storage .CachePath (), depotName )
62
82
@@ -73,6 +93,7 @@ func newDepot(runtimePath string) (*depot, error) {
73
93
result := & depot {
74
94
config : depotConfig {
75
95
Deployments : map [strfmt.UUID ][]deployment {},
96
+ Cache : map [strfmt.UUID ]* artifactInfo {},
76
97
},
77
98
depotPath : depotPath ,
78
99
artifacts : map [strfmt.UUID ]struct {}{},
@@ -122,6 +143,10 @@ func newDepot(runtimePath string) (*depot, error) {
122
143
return result , nil
123
144
}
124
145
146
+ func (d * depot ) SetCacheSize (mb int ) {
147
+ d .cacheSize = int64 (mb ) * MB
148
+ }
149
+
125
150
func (d * depot ) Exists (id strfmt.UUID ) bool {
126
151
_ , ok := d .artifacts [id ]
127
152
return ok
@@ -196,6 +221,10 @@ func (d *depot) DeployViaLink(id strfmt.UUID, relativeSrc, absoluteDest string)
196
221
Files : files .RelativePaths (),
197
222
RelativeSrc : relativeSrc ,
198
223
})
224
+ err = d .recordUse (id )
225
+ if err != nil {
226
+ return errs .Wrap (err , "Could not record artifact use" )
227
+ }
199
228
200
229
return nil
201
230
}
@@ -253,7 +282,25 @@ func (d *depot) DeployViaCopy(id strfmt.UUID, relativeSrc, absoluteDest string)
253
282
Files : files .RelativePaths (),
254
283
RelativeSrc : relativeSrc ,
255
284
})
285
+ err = d .recordUse (id )
286
+ if err != nil {
287
+ return errs .Wrap (err , "Could not record artifact use" )
288
+ }
289
+
290
+ return nil
291
+ }
256
292
293
+ func (d * depot ) recordUse (id strfmt.UUID ) error {
294
+ // Ensure a cache entry for this artifact exists and then update its last access time.
295
+ if _ , exists := d .config .Cache [id ]; ! exists {
296
+ size , err := fileutils .GetDirSize (d .Path (id ))
297
+ if err != nil {
298
+ return errs .Wrap (err , "Could not get artifact size on disk" )
299
+ }
300
+ d .config .Cache [id ] = & artifactInfo {Size : size , id : id }
301
+ }
302
+ d .config .Cache [id ].InUse = true
303
+ d .config .Cache [id ].LastAccessTime = time .Now ().Unix ()
257
304
return nil
258
305
}
259
306
@@ -372,14 +419,17 @@ func (d *depot) getSharedFilesToRedeploy(id strfmt.UUID, deploy deployment, path
372
419
// Save will write config changes to disk (ie. links between depot artifacts and runtimes that use it).
373
420
// It will also delete any stale artifacts which are not used by any runtime.
374
421
func (d * depot ) Save () error {
375
- // Delete artifacts that are no longer used
422
+ // Mark artifacts that are no longer used and remove the old ones.
376
423
for id := range d .artifacts {
377
424
if deployments , ok := d .config .Deployments [id ]; ! ok || len (deployments ) == 0 {
378
- if err := os .RemoveAll (d .Path (id )); err != nil {
379
- return errs .Wrap (err , "failed to remove stale artifact" )
380
- }
425
+ d .config .Cache [id ].InUse = false
426
+ logging .Debug ("Artifact '%s' is no longer in use" , id .String ())
381
427
}
382
428
}
429
+ err := d .removeStaleArtifacts ()
430
+ if err != nil {
431
+ return errs .Wrap (err , "Could not remove stale artifacts" )
432
+ }
383
433
384
434
// Write config file changes to disk
385
435
configFile := filepath .Join (d .depotPath , depotFile )
@@ -422,3 +472,40 @@ func someFilesExist(filePaths []string, basePath string) bool {
422
472
}
423
473
return false
424
474
}
475
+
476
+ // removeStaleArtifacts iterates over all unused artifacts in the depot, sorts them by last access
477
+ // time, and removes them until the size of cached artifacts is under the limit.
478
+ func (d * depot ) removeStaleArtifacts () error {
479
+ var totalSize int64
480
+ unusedArtifacts := make ([]* artifactInfo , 0 )
481
+
482
+ for _ , info := range d .config .Cache {
483
+ if ! info .InUse {
484
+ totalSize += info .Size
485
+ unusedArtifacts = append (unusedArtifacts , info )
486
+ }
487
+ }
488
+ logging .Debug ("There are %d unused artifacts totaling %.1f MB in size" , len (unusedArtifacts ), float64 (totalSize )/ float64 (MB ))
489
+
490
+ sort .Slice (unusedArtifacts , func (i , j int ) bool {
491
+ return unusedArtifacts [i ].LastAccessTime < unusedArtifacts [j ].LastAccessTime
492
+ })
493
+
494
+ var rerr error
495
+ for _ , artifact := range unusedArtifacts {
496
+ if totalSize <= d .cacheSize {
497
+ break // done
498
+ }
499
+ if err := os .RemoveAll (d .Path (artifact .id )); err == nil {
500
+ totalSize -= artifact .Size
501
+ } else {
502
+ if err := errs .Wrap (err , "Could not delete old artifact" ); rerr == nil {
503
+ rerr = err
504
+ } else {
505
+ rerr = errs .Pack (rerr , err )
506
+ }
507
+ }
508
+ delete (d .config .Cache , artifact .id )
509
+ }
510
+ return rerr
511
+ }
0 commit comments