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