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  }