Move source files to root dir
This commit is contained in:
187
cache/cache.go
vendored
Normal file
187
cache/cache.go
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// Cache stores loaded content, enforcing size limits and keeping track of size usage.
|
||||
type Cache struct {
|
||||
CacheSize uint32 // Total allowed cumulative size of values (not code) in cache
|
||||
CacheUseSize uint32 // Currently used bytes by all values (not code) in cache
|
||||
Cache []map[string]string // All loaded cache items
|
||||
sizes map[string]uint16 // Size limits for all loaded symbols.
|
||||
}
|
||||
|
||||
// NewCache creates a new ready-to-use cache object
|
||||
func NewCache() *Cache {
|
||||
ca := &Cache{
|
||||
Cache: []map[string]string{make(map[string]string)},
|
||||
sizes: make(map[string]uint16),
|
||||
}
|
||||
return ca
|
||||
}
|
||||
|
||||
// WithCacheSize applies a cumulative cache size limitation for all cached items.
|
||||
func(ca *Cache) WithCacheSize(cacheSize uint32) *Cache {
|
||||
ca.CacheSize = cacheSize
|
||||
return ca
|
||||
}
|
||||
|
||||
// Add adds a cache value under a cache symbol key.
|
||||
//
|
||||
// Also stores the size limitation of for key for later updates.
|
||||
//
|
||||
// Fails if:
|
||||
// - key already defined
|
||||
// - value is longer than size limit
|
||||
// - adding value exceeds cumulative cache capacity
|
||||
func(ca *Cache) Add(key string, value string, sizeLimit uint16) error {
|
||||
if sizeLimit > 0 {
|
||||
l := uint16(len(value))
|
||||
if l > sizeLimit {
|
||||
return fmt.Errorf("value length %v exceeds value size limit %v", l, sizeLimit)
|
||||
}
|
||||
}
|
||||
checkFrame := ca.frameOf(key)
|
||||
if checkFrame > -1 {
|
||||
if checkFrame == len(ca.Cache) - 1 {
|
||||
log.Printf("Ignoring load request on frame that has symbol already loaded")
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("key %v already defined in frame %v", key, checkFrame)
|
||||
}
|
||||
sz := ca.checkCapacity(value)
|
||||
if sz == 0 {
|
||||
return fmt.Errorf("Cache capacity exceeded %v of %v", ca.CacheUseSize + sz, ca.CacheSize)
|
||||
}
|
||||
log.Printf("add key %s value size %v limit %v", key, sz, sizeLimit)
|
||||
ca.Cache[len(ca.Cache)-1][key] = value
|
||||
ca.CacheUseSize += sz
|
||||
ca.sizes[key] = sizeLimit
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReservedSize returns the maximum byte size available for the given symbol.
|
||||
func(ca *Cache) ReservedSize(key string) (uint16, error) {
|
||||
v, ok := ca.sizes[key]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("unknown symbol: %s", key)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Update sets a new value for an existing key.
|
||||
//
|
||||
// Uses the size limitation from when the key was added.
|
||||
//
|
||||
// Fails if:
|
||||
// - key not defined
|
||||
// - value is longer than size limit
|
||||
// - replacing value exceeds cumulative cache capacity
|
||||
func(ca *Cache) Update(key string, value string) error {
|
||||
sizeLimit := ca.sizes[key]
|
||||
if ca.sizes[key] > 0 {
|
||||
l := uint16(len(value))
|
||||
if l > sizeLimit {
|
||||
return fmt.Errorf("update value length %v exceeds value size limit %v", l, sizeLimit)
|
||||
}
|
||||
}
|
||||
checkFrame := ca.frameOf(key)
|
||||
if checkFrame == -1 {
|
||||
return fmt.Errorf("key %v not defined", key)
|
||||
}
|
||||
r := ca.Cache[checkFrame][key]
|
||||
l := uint32(len(r))
|
||||
ca.Cache[checkFrame][key] = ""
|
||||
ca.CacheUseSize -= l
|
||||
sz := ca.checkCapacity(value)
|
||||
if sz == 0 {
|
||||
baseUseSize := ca.CacheUseSize
|
||||
ca.Cache[checkFrame][key] = r
|
||||
ca.CacheUseSize += l
|
||||
return fmt.Errorf("Cache capacity exceeded %v of %v", baseUseSize + sz, ca.CacheSize)
|
||||
}
|
||||
ca.Cache[checkFrame][key] = value
|
||||
ca.CacheUseSize += uint32(len(value))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the content currently loaded for a single key, loaded at any level.
|
||||
//
|
||||
// Fails if key has not been loaded.
|
||||
func(ca *Cache) Get(key string) (string, error) {
|
||||
i := ca.frameOf(key)
|
||||
r, ok := ca.Cache[i][key]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unknown key: %s", key)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Reset flushes all state contents below the top level.
|
||||
func(ca *Cache) Reset() {
|
||||
if len(ca.Cache) == 0 {
|
||||
return
|
||||
}
|
||||
ca.Cache = ca.Cache[:1]
|
||||
ca.CacheUseSize = 0
|
||||
return
|
||||
}
|
||||
|
||||
// Push adds a new level to the cache.
|
||||
func (ca *Cache) Push() error {
|
||||
m := make(map[string]string)
|
||||
ca.Cache = append(ca.Cache, m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pop frees the cache of the current level and makes the previous level the current level.
|
||||
//
|
||||
// Fails if already on top level.
|
||||
func (ca *Cache) Pop() error {
|
||||
l := len(ca.Cache)
|
||||
if l == 0 {
|
||||
return fmt.Errorf("already at top level")
|
||||
}
|
||||
l -= 1
|
||||
m := ca.Cache[l]
|
||||
for k, v := range m {
|
||||
sz := len(v)
|
||||
ca.CacheUseSize -= uint32(sz)
|
||||
log.Printf("free frame %v key %v value size %v", l, k, sz)
|
||||
}
|
||||
ca.Cache = ca.Cache[:l]
|
||||
//ca.resetCurrent()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check returns true if a key already exists in the cache.
|
||||
func(ca *Cache) Check(key string) bool {
|
||||
return ca.frameOf(key) == -1
|
||||
}
|
||||
|
||||
// bytes that will be added to cache use size for string
|
||||
// returns 0 if capacity would be exceeded
|
||||
func(ca *Cache) checkCapacity(v string) uint32 {
|
||||
sz := uint32(len(v))
|
||||
if ca.CacheSize == 0 {
|
||||
return sz
|
||||
}
|
||||
if ca.CacheUseSize + sz > ca.CacheSize {
|
||||
return 0
|
||||
}
|
||||
return sz
|
||||
}
|
||||
|
||||
// return 0-indexed frame number where key is defined. -1 if not defined
|
||||
func(ca *Cache) frameOf(key string) int {
|
||||
for i, m := range ca.Cache {
|
||||
for k, _ := range m {
|
||||
if k == key {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
104
cache/cache_test.go
vendored
Normal file
104
cache/cache_test.go
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewCache(t *testing.T) {
|
||||
ca := NewCache()
|
||||
if ca.CacheSize != 0 {
|
||||
t.Errorf("cache size not 0")
|
||||
}
|
||||
ca = ca.WithCacheSize(102525)
|
||||
if ca.CacheSize != 102525 {
|
||||
t.Errorf("cache size not 102525")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateCacheUse(t *testing.T) {
|
||||
ca := NewCache()
|
||||
ca = ca.WithCacheSize(10)
|
||||
ca.Push()
|
||||
err := ca.Add("bar", "baz", 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = ca.Add("inky", "pinky", 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = ca.Add("blinky", "clyde", 0)
|
||||
if err == nil {
|
||||
t.Errorf("expected capacity error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateDownUp(t *testing.T) {
|
||||
ca := NewCache()
|
||||
err := ca.Push()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = ca.Add("foo", "bar", 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = ca.Add("baz", "xyzzy", 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if ca.CacheUseSize != 8 {
|
||||
t.Errorf("expected cache use size 8 got %v", ca.CacheUseSize)
|
||||
}
|
||||
err = ca.Pop()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = ca.Pop()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = ca.Pop()
|
||||
if err == nil {
|
||||
t.Errorf("expected out of top frame error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheReset(t *testing.T) {
|
||||
ca := NewCache()
|
||||
err := ca.Add("foo", "bar", 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = ca.Add("baz", "xyzzy", 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
ca.Reset()
|
||||
if ca.CacheUseSize != 0 {
|
||||
t.Errorf("expected cache use size 0, got %v", ca.CacheUseSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheLoadDup(t *testing.T) {
|
||||
ca := NewCache()
|
||||
err := ca.Push()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = ca.Add("foo", "xyzzy", 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = ca.Push()
|
||||
err = ca.Add("foo", "baz", 0)
|
||||
if err == nil {
|
||||
t.Errorf("expected fail on duplicate load")
|
||||
}
|
||||
ca.Pop()
|
||||
err = ca.Add("foo", "baz", 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
12
cache/memory.go
vendored
Normal file
12
cache/memory.go
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package cache
|
||||
|
||||
// Memory defines the interface for store of a symbol mapped content store.
|
||||
type Memory interface {
|
||||
Add(key string, val string, sizeLimit uint16) error
|
||||
Update(key string, val string) error
|
||||
ReservedSize(key string) (uint16, error)
|
||||
Get(key string) (string, error)
|
||||
Push() error
|
||||
Pop() error
|
||||
Reset()
|
||||
}
|
||||
Reference in New Issue
Block a user