Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Commit a6b1e89

Browse files
authored
Merge pull request #1711 from jmank88/cache
gps: source cache: enable opt-in persistent caching via DEPCACHEAGE env var
2 parents 3e697f6 + 3a48552 commit a6b1e89

File tree

10 files changed

+229
-92
lines changed

10 files changed

+229
-92
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ NEW FEATURES:
44

55
* Add CI tests against go1.10. Drop support for go1.8. ([#1620](https://github.com/golang/dep/pull/1620))
66
* Added `install.sh` script. ([#1533](https://github.com/golang/dep/pull/1533))
7-
* List out of date projects in dep status ([#1553](https://github.com/golang/dep/pull/1553)).
7+
* List out of date projects in dep status. ([#1553](https://github.com/golang/dep/pull/1553)).
8+
* Enabled opt-in persistent caching via $DEPCACHEAGE env var. ([#1711](https://github.com/golang/dep/pull/1711))
89

910
BUG FIXES:
1011

cmd/dep/main.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"runtime/pprof"
1919
"strings"
2020
"text/tabwriter"
21+
"time"
2122

2223
"github.com/golang/dep"
2324
"github.com/golang/dep/internal/fs"
@@ -216,13 +217,24 @@ func (c *Config) Run() int {
216217
}
217218
}
218219

220+
var cacheAge time.Duration
221+
if env := getEnv(c.Env, "DEPCACHEAGE"); env != "" {
222+
var err error
223+
cacheAge, err = time.ParseDuration(env)
224+
if err != nil {
225+
errLogger.Printf("dep: failed to parse $DEPCACHEAGE duration %q: %v\n", env, err)
226+
return errorExitCode
227+
}
228+
}
229+
219230
// Set up dep context.
220231
ctx := &dep.Ctx{
221232
Out: outLogger,
222233
Err: errLogger,
223234
Verbose: *verbose,
224235
DisableLocking: getEnv(c.Env, "DEPNOLOCK") != "",
225236
Cachedir: cachedir,
237+
CacheAge: cacheAge,
226238
}
227239

228240
GOPATHS := filepath.SplitList(getEnv(c.Env, "GOPATH"))

context.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"path/filepath"
1111
"runtime"
12+
"time"
1213

1314
"github.com/golang/dep/gps"
1415
"github.com/golang/dep/internal/fs"
@@ -34,13 +35,14 @@ import (
3435
// }
3536
//
3637
type Ctx struct {
37-
WorkingDir string // Where to execute.
38-
GOPATH string // Selected Go path, containing WorkingDir.
39-
GOPATHs []string // Other Go paths.
40-
Out, Err *log.Logger // Required loggers.
41-
Verbose bool // Enables more verbose logging.
42-
DisableLocking bool // When set, no lock file will be created to protect against simultaneous dep processes.
43-
Cachedir string // Cache directory loaded from environment.
38+
WorkingDir string // Where to execute.
39+
GOPATH string // Selected Go path, containing WorkingDir.
40+
GOPATHs []string // Other Go paths.
41+
Out, Err *log.Logger // Required loggers.
42+
Verbose bool // Enables more verbose logging.
43+
DisableLocking bool // When set, no lock file will be created to protect against simultaneous dep processes.
44+
Cachedir string // Cache directory loaded from environment.
45+
CacheAge time.Duration // Maximum valid age of cached source data. <=0: Don't cache.
4446
}
4547

4648
// SetPaths sets the WorkingDir and GOPATHs fields. If GOPATHs is empty, then
@@ -99,6 +101,7 @@ func (c *Ctx) SourceManager() (*gps.SourceMgr, error) {
99101
}
100102

101103
return gps.NewSourceManager(gps.SourceManagerConfig{
104+
CacheAge: c.CacheAge,
102105
Cachedir: cachedir,
103106
Logger: c.Out,
104107
DisableLocking: c.DisableLocking,

gps/source.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,22 +67,33 @@ type sourceCoordinator struct {
6767
psrcmut sync.Mutex // guards protoSrcs map
6868
protoSrcs map[string][]chan srcReturn
6969
cachedir string
70+
cache sourceCache
7071
logger *log.Logger
7172
}
7273

73-
func newSourceCoordinator(superv *supervisor, deducer deducer, cachedir string, logger *log.Logger) *sourceCoordinator {
74+
// newSourceCoordinator returns a new sourceCoordinator.
75+
// Passing a nil sourceCache defaults to an in-memory cache.
76+
func newSourceCoordinator(superv *supervisor, deducer deducer, cachedir string, cache sourceCache, logger *log.Logger) *sourceCoordinator {
77+
if cache == nil {
78+
cache = memoryCache{}
79+
}
7480
return &sourceCoordinator{
7581
supervisor: superv,
7682
deducer: deducer,
7783
cachedir: cachedir,
84+
cache: cache,
7885
logger: logger,
7986
srcs: make(map[string]*sourceGateway),
8087
nameToURL: make(map[string]string),
8188
protoSrcs: make(map[string][]chan srcReturn),
8289
}
8390
}
8491

85-
func (sc *sourceCoordinator) close() {}
92+
func (sc *sourceCoordinator) close() {
93+
if err := sc.cache.close(); err != nil {
94+
sc.logger.Println(errors.Wrap(err, "failed to close the source cache"))
95+
}
96+
}
8697

8798
func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id ProjectIdentifier) (*sourceGateway, error) {
8899
if err := sc.supervisor.ctx.Err(); err != nil {
@@ -216,7 +227,8 @@ func (sc *sourceCoordinator) getSourceGatewayFor(ctx context.Context, id Project
216227
}
217228
src, err := m.try(ctx, sc.cachedir)
218229
if err == nil {
219-
srcGate, err = newSourceGateway(ctx, src, sc.supervisor, sc.cachedir)
230+
cache := sc.cache.newSingleSourceCache(id)
231+
srcGate, err = newSourceGateway(ctx, src, sc.supervisor, sc.cachedir, cache)
220232
if err == nil {
221233
sc.srcs[url] = srcGate
222234
break
@@ -260,7 +272,7 @@ type sourceGateway struct {
260272

261273
// newSourceGateway returns a new gateway for src. If the source exists locally,
262274
// the local state may be cleaned, otherwise we ping upstream.
263-
func newSourceGateway(ctx context.Context, src source, superv *supervisor, cachedir string) (*sourceGateway, error) {
275+
func newSourceGateway(ctx context.Context, src source, superv *supervisor, cachedir string, cache singleSourceCache) (*sourceGateway, error) {
264276
var state sourceState
265277
local := src.existsLocally(ctx)
266278
if local {
@@ -276,9 +288,9 @@ func newSourceGateway(ctx context.Context, src source, superv *supervisor, cache
276288
srcState: state,
277289
src: src,
278290
cachedir: cachedir,
291+
cache: cache,
279292
suprvsr: superv,
280293
}
281-
sg.cache = sg.createSingleSourceCache()
282294

283295
if !local {
284296
if err := sg.require(ctx, sourceExistsUpstream); err != nil {
@@ -542,14 +554,6 @@ func (sg *sourceGateway) disambiguateRevision(ctx context.Context, r Revision) (
542554
return sg.src.disambiguateRevision(ctx, r)
543555
}
544556

545-
// createSingleSourceCache creates a singleSourceCache instance for use by
546-
// the encapsulated source.
547-
func (sg *sourceGateway) createSingleSourceCache() singleSourceCache {
548-
// TODO(sdboyer) when persistent caching is ready, just drop in the creation
549-
// of a source-specific handle here
550-
return newMemoryCache()
551-
}
552-
553557
// sourceExistsUpstream verifies that the source exists upstream and that the
554558
// upstreamURL has not changed and returns any additional sourceState, or an error.
555559
func (sg *sourceGateway) sourceExistsUpstream(ctx context.Context) (sourceState, error) {

gps/source_cache.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ import (
1313
"github.com/golang/dep/gps/pkgtree"
1414
)
1515

16+
// sourceCache is an interface for creating singleSourceCaches, and safely
17+
// releasing backing resources via close.
18+
type sourceCache interface {
19+
// newSingleSourceCache creates a new singleSourceCache for id, which
20+
// remains valid until close is called.
21+
newSingleSourceCache(id ProjectIdentifier) singleSourceCache
22+
// close releases background resources.
23+
close() error
24+
}
25+
1626
// singleSourceCache provides a method set for storing and retrieving data about
1727
// a single source.
1828
type singleSourceCache interface {
@@ -62,6 +72,15 @@ type singleSourceCache interface {
6272
toUnpaired(v Version) (UnpairedVersion, bool)
6373
}
6474

75+
// memoryCache is a sourceCache which creates singleSourceCacheMemory instances.
76+
type memoryCache struct{}
77+
78+
func (memoryCache) newSingleSourceCache(ProjectIdentifier) singleSourceCache {
79+
return newMemoryCache()
80+
}
81+
82+
func (memoryCache) close() error { return nil }
83+
6584
type singleSourceCacheMemory struct {
6685
// Protects all fields.
6786
mut sync.RWMutex

gps/source_cache_bolt.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import (
2121
"github.com/pkg/errors"
2222
)
2323

24+
// boltCacheFilename is a versioned filename for the bolt cache. The version
25+
// must be incremented whenever incompatible changes are made.
26+
const boltCacheFilename = "bolt-v1.db"
27+
2428
// boltCache manages a bolt.DB cache and provides singleSourceCaches.
2529
type boltCache struct {
2630
db *bolt.DB
@@ -30,7 +34,7 @@ type boltCache struct {
3034

3135
// newBoltCache returns a new boltCache backed by a BoltDB file under the cache directory.
3236
func newBoltCache(cd string, epoch int64, logger *log.Logger) (*boltCache, error) {
33-
path := sourceCachePath(cd, "bolt") + ".db"
37+
path := filepath.Join(cd, boltCacheFilename)
3438
dir := filepath.Dir(path)
3539
if fi, err := os.Stat(dir); os.IsNotExist(err) {
3640
if err := os.MkdirAll(dir, os.ModeDir|os.ModePerm); err != nil {

gps/source_cache_multi.go

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,72 @@ import (
88
"github.com/golang/dep/gps/pkgtree"
99
)
1010

11-
// A multiCache manages two cache levels, ephemeral in-memory and persistent on-disk.
11+
// multiCache creates singleSourceMultiCaches, and coordinates their async updates.
12+
type multiCache struct {
13+
mem, disk sourceCache
14+
// Asynchronous disk cache updates. Closed by the close method.
15+
async chan func()
16+
// Closed when async has completed processing.
17+
done chan struct{}
18+
}
19+
20+
// newMultiCache returns a new multiCache backed by mem and disk sourceCaches.
21+
// Spawns a single background goroutine which lives until close() is called.
22+
func newMultiCache(mem, disk sourceCache) *multiCache {
23+
m := &multiCache{
24+
mem: mem,
25+
disk: disk,
26+
async: make(chan func(), 50),
27+
done: make(chan struct{}),
28+
}
29+
go m.processAsync()
30+
return m
31+
}
32+
33+
func (c *multiCache) processAsync() {
34+
for f := range c.async {
35+
f()
36+
}
37+
close(c.done)
38+
}
39+
40+
// close releases resources after blocking until async writes complete.
41+
func (c *multiCache) close() error {
42+
close(c.async)
43+
_ = c.mem.close()
44+
<-c.done
45+
return c.disk.close()
46+
}
47+
48+
// newSingleSourceCache returns a singleSourceMultiCache for id.
49+
func (c *multiCache) newSingleSourceCache(id ProjectIdentifier) singleSourceCache {
50+
return &singleSourceMultiCache{
51+
mem: c.mem.newSingleSourceCache(id),
52+
disk: c.disk.newSingleSourceCache(id),
53+
async: c.async,
54+
}
55+
}
56+
57+
// singleSourceMultiCache manages two cache levels, ephemeral in-memory and persistent on-disk.
1258
//
1359
// The in-memory cache is always checked first, with the on-disk used as a fallback.
1460
// Values read from disk are set in-memory when an appropriate method exists.
1561
//
16-
// Set values are cached both in-memory and on-disk.
17-
type multiCache struct {
62+
// Set values are cached both in-memory and on-disk. Values are set synchronously
63+
// in-memory. Writes to the on-disk cache are asynchronous, and executed in order by a
64+
// background goroutine.
65+
type singleSourceMultiCache struct {
1866
mem, disk singleSourceCache
67+
// Asynchronous disk cache updates.
68+
async chan<- func()
1969
}
2070

21-
func (c *multiCache) setManifestAndLock(r Revision, ai ProjectAnalyzerInfo, m Manifest, l Lock) {
71+
func (c *singleSourceMultiCache) setManifestAndLock(r Revision, ai ProjectAnalyzerInfo, m Manifest, l Lock) {
2272
c.mem.setManifestAndLock(r, ai, m, l)
23-
c.disk.setManifestAndLock(r, ai, m, l)
73+
c.async <- func() { c.disk.setManifestAndLock(r, ai, m, l) }
2474
}
2575

26-
func (c *multiCache) getManifestAndLock(r Revision, ai ProjectAnalyzerInfo) (Manifest, Lock, bool) {
76+
func (c *singleSourceMultiCache) getManifestAndLock(r Revision, ai ProjectAnalyzerInfo) (Manifest, Lock, bool) {
2777
m, l, ok := c.mem.getManifestAndLock(r, ai)
2878
if ok {
2979
return m, l, true
@@ -38,12 +88,12 @@ func (c *multiCache) getManifestAndLock(r Revision, ai ProjectAnalyzerInfo) (Man
3888
return nil, nil, false
3989
}
4090

41-
func (c *multiCache) setPackageTree(r Revision, ptree pkgtree.PackageTree) {
91+
func (c *singleSourceMultiCache) setPackageTree(r Revision, ptree pkgtree.PackageTree) {
4292
c.mem.setPackageTree(r, ptree)
43-
c.disk.setPackageTree(r, ptree)
93+
c.async <- func() { c.disk.setPackageTree(r, ptree) }
4494
}
4595

46-
func (c *multiCache) getPackageTree(r Revision, pr ProjectRoot) (pkgtree.PackageTree, bool) {
96+
func (c *singleSourceMultiCache) getPackageTree(r Revision, pr ProjectRoot) (pkgtree.PackageTree, bool) {
4797
ptree, ok := c.mem.getPackageTree(r, pr)
4898
if ok {
4999
return ptree, true
@@ -58,17 +108,17 @@ func (c *multiCache) getPackageTree(r Revision, pr ProjectRoot) (pkgtree.Package
58108
return pkgtree.PackageTree{}, false
59109
}
60110

61-
func (c *multiCache) markRevisionExists(r Revision) {
111+
func (c *singleSourceMultiCache) markRevisionExists(r Revision) {
62112
c.mem.markRevisionExists(r)
63-
c.disk.markRevisionExists(r)
113+
c.async <- func() { c.disk.markRevisionExists(r) }
64114
}
65115

66-
func (c *multiCache) setVersionMap(pvs []PairedVersion) {
116+
func (c *singleSourceMultiCache) setVersionMap(pvs []PairedVersion) {
67117
c.mem.setVersionMap(pvs)
68-
c.disk.setVersionMap(pvs)
118+
c.async <- func() { c.disk.setVersionMap(pvs) }
69119
}
70120

71-
func (c *multiCache) getVersionsFor(rev Revision) ([]UnpairedVersion, bool) {
121+
func (c *singleSourceMultiCache) getVersionsFor(rev Revision) ([]UnpairedVersion, bool) {
72122
uvs, ok := c.mem.getVersionsFor(rev)
73123
if ok {
74124
return uvs, true
@@ -77,7 +127,7 @@ func (c *multiCache) getVersionsFor(rev Revision) ([]UnpairedVersion, bool) {
77127
return c.disk.getVersionsFor(rev)
78128
}
79129

80-
func (c *multiCache) getAllVersions() ([]PairedVersion, bool) {
130+
func (c *singleSourceMultiCache) getAllVersions() ([]PairedVersion, bool) {
81131
pvs, ok := c.mem.getAllVersions()
82132
if ok {
83133
return pvs, true
@@ -92,7 +142,7 @@ func (c *multiCache) getAllVersions() ([]PairedVersion, bool) {
92142
return nil, false
93143
}
94144

95-
func (c *multiCache) getRevisionFor(uv UnpairedVersion) (Revision, bool) {
145+
func (c *singleSourceMultiCache) getRevisionFor(uv UnpairedVersion) (Revision, bool) {
96146
rev, ok := c.mem.getRevisionFor(uv)
97147
if ok {
98148
return rev, true
@@ -101,7 +151,7 @@ func (c *multiCache) getRevisionFor(uv UnpairedVersion) (Revision, bool) {
101151
return c.disk.getRevisionFor(uv)
102152
}
103153

104-
func (c *multiCache) toRevision(v Version) (Revision, bool) {
154+
func (c *singleSourceMultiCache) toRevision(v Version) (Revision, bool) {
105155
rev, ok := c.mem.toRevision(v)
106156
if ok {
107157
return rev, true
@@ -110,7 +160,7 @@ func (c *multiCache) toRevision(v Version) (Revision, bool) {
110160
return c.disk.toRevision(v)
111161
}
112162

113-
func (c *multiCache) toUnpaired(v Version) (UnpairedVersion, bool) {
163+
func (c *singleSourceMultiCache) toUnpaired(v Version) (UnpairedVersion, bool) {
114164
uv, ok := c.mem.toUnpaired(v)
115165
if ok {
116166
return uv, true

0 commit comments

Comments
 (0)