github.com/rajeev159/opa@v0.45.0/topdown/cache/cache.go (about) 1 // Copyright 2020 The OPA Authors. All rights reserved. 2 // Use of this source code is governed by an Apache2 3 // license that can be found in the LICENSE file. 4 5 // Package cache defines the inter-query cache interface that can cache data across queries 6 package cache 7 8 import ( 9 "container/list" 10 11 "github.com/open-policy-agent/opa/ast" 12 13 "sync" 14 15 "github.com/open-policy-agent/opa/util" 16 ) 17 18 const ( 19 defaultMaxSizeBytes = int64(0) // unlimited 20 ) 21 22 // Config represents the configuration of the inter-query cache. 23 type Config struct { 24 InterQueryBuiltinCache InterQueryBuiltinCacheConfig `json:"inter_query_builtin_cache"` 25 } 26 27 // InterQueryBuiltinCacheConfig represents the configuration of the inter-query cache that built-in functions can utilize. 28 type InterQueryBuiltinCacheConfig struct { 29 MaxSizeBytes *int64 `json:"max_size_bytes,omitempty"` 30 } 31 32 // ParseCachingConfig returns the config for the inter-query cache. 33 func ParseCachingConfig(raw []byte) (*Config, error) { 34 if raw == nil { 35 maxSize := new(int64) 36 *maxSize = defaultMaxSizeBytes 37 return &Config{InterQueryBuiltinCache: InterQueryBuiltinCacheConfig{MaxSizeBytes: maxSize}}, nil 38 } 39 40 var config Config 41 42 if err := util.Unmarshal(raw, &config); err == nil { 43 if err = config.validateAndInjectDefaults(); err != nil { 44 return nil, err 45 } 46 } else { 47 return nil, err 48 } 49 50 return &config, nil 51 } 52 53 func (c *Config) validateAndInjectDefaults() error { 54 if c.InterQueryBuiltinCache.MaxSizeBytes == nil { 55 maxSize := new(int64) 56 *maxSize = defaultMaxSizeBytes 57 c.InterQueryBuiltinCache.MaxSizeBytes = maxSize 58 } 59 return nil 60 } 61 62 // InterQueryCacheValue defines the interface for the data that the inter-query cache holds. 63 type InterQueryCacheValue interface { 64 SizeInBytes() int64 65 } 66 67 // InterQueryCache defines the interface for the inter-query cache. 68 type InterQueryCache interface { 69 Get(key ast.Value) (value InterQueryCacheValue, found bool) 70 Insert(key ast.Value, value InterQueryCacheValue) int 71 Delete(key ast.Value) 72 UpdateConfig(config *Config) 73 } 74 75 // NewInterQueryCache returns a new inter-query cache. 76 func NewInterQueryCache(config *Config) InterQueryCache { 77 return &cache{ 78 items: map[string]InterQueryCacheValue{}, 79 usage: 0, 80 config: config, 81 l: list.New(), 82 } 83 } 84 85 type cache struct { 86 items map[string]InterQueryCacheValue 87 usage int64 88 config *Config 89 l *list.List 90 mtx sync.Mutex 91 } 92 93 // Insert inserts a key k into the cache with value v. 94 func (c *cache) Insert(k ast.Value, v InterQueryCacheValue) (dropped int) { 95 c.mtx.Lock() 96 defer c.mtx.Unlock() 97 return c.unsafeInsert(k, v) 98 } 99 100 // Get returns the value in the cache for k. 101 func (c *cache) Get(k ast.Value) (InterQueryCacheValue, bool) { 102 c.mtx.Lock() 103 defer c.mtx.Unlock() 104 return c.unsafeGet(k) 105 } 106 107 // Delete deletes the value in the cache for k. 108 func (c *cache) Delete(k ast.Value) { 109 c.mtx.Lock() 110 defer c.mtx.Unlock() 111 c.unsafeDelete(k) 112 } 113 114 func (c *cache) UpdateConfig(config *Config) { 115 if config == nil { 116 return 117 } 118 c.mtx.Lock() 119 defer c.mtx.Unlock() 120 c.config = config 121 } 122 123 func (c *cache) unsafeInsert(k ast.Value, v InterQueryCacheValue) (dropped int) { 124 size := v.SizeInBytes() 125 limit := c.maxSizeBytes() 126 127 if limit > 0 { 128 if size > limit { 129 dropped++ 130 return dropped 131 } 132 133 for key := c.l.Front(); key != nil && (c.usage+size > limit); key = key.Next() { 134 dropKey := key.Value.(ast.Value) 135 c.unsafeDelete(dropKey) 136 c.l.Remove(key) 137 dropped++ 138 } 139 } 140 141 c.items[k.String()] = v 142 c.l.PushBack(k) 143 c.usage += size 144 return dropped 145 } 146 147 func (c *cache) unsafeGet(k ast.Value) (InterQueryCacheValue, bool) { 148 value, ok := c.items[k.String()] 149 return value, ok 150 } 151 152 func (c *cache) unsafeDelete(k ast.Value) { 153 value, ok := c.unsafeGet(k) 154 if !ok { 155 return 156 } 157 158 c.usage -= int64(value.SizeInBytes()) 159 delete(c.items, k.String()) 160 } 161 162 func (c *cache) maxSizeBytes() int64 { 163 if c.config == nil { 164 return defaultMaxSizeBytes 165 } 166 return *c.config.InterQueryBuiltinCache.MaxSizeBytes 167 }