+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
package main
import (
- "fmt"
"sync"
"sync/atomic"
"time"
)
type cache struct {
- TTL arvados.Duration
- CollectionEntries int
- CollectionBytes int64
- PermissionEntries int
- UUIDEntries int
+ TTL arvados.Duration
+ MaxCollectionEntries int
+ MaxCollectionBytes int64
+ MaxPermissionEntries int
+ MaxUUIDEntries int
stats cacheStats
pdhs *lru.TwoQueueCache
}
type cacheStats struct {
- Requests uint64
- CollectionHits uint64
- PDHHits uint64
- PermissionHits uint64
- APICalls uint64
+ Requests uint64 `json:"Cache.Requests"`
+ CollectionBytes uint64 `json:"Cache.CollectionBytes"`
+ CollectionEntries int `json:"Cache.CollectionEntries"`
+ CollectionHits uint64 `json:"Cache.CollectionHits"`
+ PDHHits uint64 `json:"Cache.UUIDHits"`
+ PermissionHits uint64 `json:"Cache.PermissionHits"`
+ APICalls uint64 `json:"Cache.APICalls"`
}
type cachedPDH struct {
type cachedCollection struct {
expire time.Time
- collection map[string]interface{}
+ collection *arvados.Collection
}
type cachedPermission struct {
func (c *cache) setup() {
var err error
- c.pdhs, err = lru.New2Q(c.UUIDEntries)
+ c.pdhs, err = lru.New2Q(c.MaxUUIDEntries)
if err != nil {
panic(err)
}
- c.collections, err = lru.New2Q(c.CollectionEntries)
+ c.collections, err = lru.New2Q(c.MaxCollectionEntries)
if err != nil {
panic(err)
}
- c.permissions, err = lru.New2Q(c.PermissionEntries)
+ c.permissions, err = lru.New2Q(c.MaxPermissionEntries)
if err != nil {
panic(err)
}
}
func (c *cache) Stats() cacheStats {
+ c.setupOnce.Do(c.setup)
return cacheStats{
- Requests: atomic.LoadUint64(&c.stats.Requests),
- CollectionHits: atomic.LoadUint64(&c.stats.CollectionHits),
- PDHHits: atomic.LoadUint64(&c.stats.PDHHits),
- PermissionHits: atomic.LoadUint64(&c.stats.PermissionHits),
- APICalls: atomic.LoadUint64(&c.stats.APICalls),
+ Requests: atomic.LoadUint64(&c.stats.Requests),
+ CollectionBytes: c.collectionBytes(),
+ CollectionEntries: c.collections.Len(),
+ CollectionHits: atomic.LoadUint64(&c.stats.CollectionHits),
+ PDHHits: atomic.LoadUint64(&c.stats.PDHHits),
+ PermissionHits: atomic.LoadUint64(&c.stats.PermissionHits),
+ APICalls: atomic.LoadUint64(&c.stats.APICalls),
}
}
-func (c *cache) Get(arv *arvadosclient.ArvadosClient, targetID string, forceReload bool) (map[string]interface{}, error) {
+func (c *cache) Get(arv *arvadosclient.ArvadosClient, targetID string, forceReload bool) (*arvados.Collection, error) {
c.setupOnce.Do(c.setup)
atomic.AddUint64(&c.stats.Requests, 1)
permOK := false
permKey := arv.ApiToken + "\000" + targetID
- if ent, cached := c.permissions.Get(permKey); cached {
+ if forceReload {
+ } else if ent, cached := c.permissions.Get(permKey); cached {
ent := ent.(*cachedPermission)
if ent.expire.Before(time.Now()) {
c.permissions.Remove(permKey)
}
}
- collection := c.lookupCollection(pdh)
-
- if collection != nil && permOK && !forceReload {
- return collection, nil
+ var collection *arvados.Collection
+ if pdh != "" {
+ collection = c.lookupCollection(pdh)
}
- if collection != nil {
+ if collection != nil && permOK {
+ return collection, nil
+ } else if collection != nil {
// Ask API for current PDH for this targetID. Most
// likely, the cached PDH is still correct; if so,
// _and_ the current token has permission, we can
// use our cached manifest.
atomic.AddUint64(&c.stats.APICalls, 1)
- var current map[string]interface{}
+ var current arvados.Collection
err := arv.Get("collections", targetID, selectPDH, ¤t)
if err != nil {
return nil, err
}
- if checkPDH, ok := current["portable_data_hash"].(string); !ok {
- return nil, fmt.Errorf("API response for %q had no PDH", targetID)
- } else if checkPDH == pdh {
+ if current.PortableDataHash == pdh {
exp := time.Now().Add(time.Duration(c.TTL))
c.permissions.Add(permKey, &cachedPermission{
expire: exp,
// PDH changed, but now we know we have
// permission -- and maybe we already have the
// new PDH in the cache.
- if coll := c.lookupCollection(checkPDH); coll != nil {
+ if coll := c.lookupCollection(current.PortableDataHash); coll != nil {
return coll, nil
}
}
if err != nil {
return nil, err
}
- pdh, ok := collection["portable_data_hash"].(string)
- if !ok {
- return nil, fmt.Errorf("API response for %q had no PDH", targetID)
- }
exp := time.Now().Add(time.Duration(c.TTL))
c.permissions.Add(permKey, &cachedPermission{
expire: exp,
})
c.pdhs.Add(targetID, &cachedPDH{
expire: exp,
- pdh: pdh,
- })
- c.collections.Add(pdh, &cachedCollection{
- expire: exp,
- collection: collection,
+ pdh: collection.PortableDataHash,
})
- if int64(len(collection["manifest_text"].(string))) > c.CollectionBytes/int64(c.CollectionEntries) {
- c.pruneCollections()
- }
+ // Disabled, see #11945
+ // c.collections.Add(collection.PortableDataHash, &cachedCollection{
+ // expire: exp,
+ // collection: collection,
+ // })
+ // if int64(len(collection.ManifestText)) > c.MaxCollectionBytes/int64(c.MaxCollectionEntries) {
+ // go c.pruneCollections()
+ // }
return collection, nil
}
continue
}
ent := v.(*cachedCollection)
- n := len(ent.collection["manifest_text"].(string))
+ n := len(ent.collection.ManifestText)
size += int64(n)
entsize[i] = n
expired[i] = ent.expire.Before(now)
}
}
for i, k := range keys {
- if size <= c.CollectionBytes {
+ if size <= c.MaxCollectionBytes {
break
}
if expired[i] {
}
}
-func (c *cache) lookupCollection(pdh string) map[string]interface{} {
+// collectionBytes returns the approximate memory size of the
+// collection cache.
+func (c *cache) collectionBytes() uint64 {
+ var size uint64
+ for _, k := range c.collections.Keys() {
+ v, ok := c.collections.Peek(k)
+ if !ok {
+ continue
+ }
+ size += uint64(len(v.(*cachedCollection).collection.ManifestText))
+ }
+ return size
+}
+
+func (c *cache) lookupCollection(pdh string) *arvados.Collection {
if pdh == "" {
return nil
} else if ent, cached := c.collections.Get(pdh); !cached {