Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(98)

Unified Diff: go/src/infra/tools/cipd/internal/tagcache.go

Issue 1381583007: cipd: Implement local cache for ResolveVersion(...) call with tags. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@cipd-init
Patch Set: rebase Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: go/src/infra/tools/cipd/internal/tagcache.go
diff --git a/go/src/infra/tools/cipd/internal/tagcache.go b/go/src/infra/tools/cipd/internal/tagcache.go
new file mode 100644
index 0000000000000000000000000000000000000000..0ef783daa78990f0a3478cbab12e5154a107b068
--- /dev/null
+++ b/go/src/infra/tools/cipd/internal/tagcache.go
@@ -0,0 +1,164 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package internal
+
+import (
+ "io/ioutil"
+ "os"
+ "sync"
+
+ "infra/tools/cipd/common"
+ "infra/tools/cipd/internal/messages"
+)
+
+// MaxTagCacheSize is how many entries to keep in TagCache database.
+const MaxTagCacheSize = 300
+
+// TagCache provides a thread safe mapping (package name, tag) -> instance ID.
+// This mapping is safe to cache because tags are not detachable: once a tag is
+// successfully resolved to an instance ID it is guaranteed to resolve to same
+// instance ID later or not resolve at all (e.g. if one tag is attached to
+// multiple instances, in which case the tag is misused anyway). In any case,
+// returning a cached instance ID does make sense. The primary purpose of this
+// cache is to avoid round trips to the service to increase reliability of
+// 'cipd ensure' calls that use only tags to specify versions. It happens to be
+// the most common case of 'cipd ensure' usage by far.
+type TagCache struct {
+ lock sync.Mutex
+ cache messages.TagCache
+ dirty bool
+}
+
+// LoadTagCacheFromFile reads tag cache state from given file path if it exists.
+// Returns empty cache if file doesn't exist.
+func LoadTagCacheFromFile(path string) (*TagCache, error) {
+ buf, err := ioutil.ReadFile(path)
+ if os.IsNotExist(err) {
+ return &TagCache{}, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ cache := &TagCache{}
+ if err := cache.Load(buf); err != nil {
+ return nil, err
+ }
+ return cache, nil
+}
+
+// Load loads the state from given buffer.
+func (c *TagCache) Load(buf []byte) error {
+ cache := messages.TagCache{}
+ if err := UnmarshalWithSHA1(buf, &cache); err != nil {
+ return err
+ }
+
+ // Validate entries. Make sure to keep only MaxTagCacheSize number of them.
+ goodOnes := make([]*messages.TagCache_Entry, 0, MaxTagCacheSize)
+ for i := 0; i < len(cache.Entries) && len(goodOnes) < MaxTagCacheSize; i++ {
+ e := cache.Entries[i]
+ valid := e != nil &&
+ common.ValidatePackageName(e.GetPackage()) == nil &&
+ common.ValidateInstanceTag(e.GetTag()) == nil &&
+ common.ValidateInstanceID(e.GetInstanceId()) == nil
+ if valid {
+ goodOnes = append(goodOnes, e)
+ }
+ }
+
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ c.cache.Entries = goodOnes
+ c.dirty = false
+ return nil
+}
+
+// Save dumps state to the byte buffer. Also resets 'Dirty' flag.
+func (c *TagCache) Save() ([]byte, error) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ // Remove all "holes" left from moving entries in AddTag.
+ compacted := make([]*messages.TagCache_Entry, 0, len(c.cache.Entries))
+ for _, e := range c.cache.Entries {
+ if e != nil {
+ compacted = append(compacted, e)
+ }
+ }
+
+ // Keep at most MaxTagCacheSize entries. Truncate head of the slice, since
+ // it's where old items are. All new hotness is at the tail, we need
+ // to keep it.
+ if len(compacted) > MaxTagCacheSize {
+ compacted = compacted[len(compacted)-MaxTagCacheSize:]
+ }
+ c.cache.Entries = compacted
+
+ out, err := MarshalWithSHA1(&c.cache)
+ if err == nil {
+ c.dirty = false
+ }
+ return out, err
+}
+
+// Dirty returns true if Save() needs to be called to persist changes.
+func (c *TagCache) Dirty() bool {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ return c.dirty
+}
+
+// ResolveTag returns cached tag or empty Pin{} if such tag is not in cache.
+func (c *TagCache) ResolveTag(pkg, tag string) common.Pin {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ for i := len(c.cache.Entries) - 1; i >= 0; i-- {
+ e := c.cache.Entries[i]
+ if e != nil && e.GetPackage() == pkg && e.GetTag() == tag {
+ return common.Pin{
+ PackageName: pkg,
+ InstanceID: e.GetInstanceId(),
+ }
+ }
+ }
+ return common.Pin{}
+}
+
+// AddTag records that (pin.PackageName, tag) maps to pin.InstanceID.
+func (c *TagCache) AddTag(pin common.Pin, tag string) {
+ // Just skip invalid data. It should not be here anyway.
+ bad := common.ValidatePackageName(pin.PackageName) != nil ||
+ common.ValidateInstanceID(pin.InstanceID) != nil ||
+ common.ValidateInstanceTag(tag) != nil
+ if bad {
+ return
+ }
+
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ // Try to find an existing entry in the cache. It will be moved to bottom
+ // (thus promoted as "most recent one"). We put a "hole" (nil) in previous
+ // position to avoid shifting array for no good reason. All "holes" are
+ // compacted in Save().
+ var existing *messages.TagCache_Entry
+ for i, e := range c.cache.Entries {
+ if e != nil && e.GetPackage() == pin.PackageName && e.GetTag() == tag {
+ existing = e
+ c.cache.Entries[i] = nil
+ break
+ }
+ }
+ if existing == nil {
+ existing = &messages.TagCache_Entry{
+ Package: &pin.PackageName,
+ Tag: &tag,
+ }
+ }
+ existing.InstanceId = &pin.InstanceID
+
+ c.dirty = true
+ c.cache.Entries = append(c.cache.Entries, existing)
+}
« no previous file with comments | « go/src/infra/tools/cipd/internal/messages/messages.pb.go ('k') | go/src/infra/tools/cipd/internal/tagcache_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698