Files
opencloud/ocis-pkg/sync/cache.go
Wilko Nienhaus 011c6b76ee move uint64s to beginning of struct to ensure they are 64-bit aligned
See https://golang.org/pkg/sync/atomic/#pkg-note-BUG for reference:
"The first word in (...) an allocated struct (...) can be relied up to
be 64-bit aligned"

This fixes an "unaligned 64-bit atomic operation" panic on 32-bit ARM,
(in my case a Raspberry Pi 4, running 32-bit Raspbian). The panic
happens (at least) during the first login after a service (re)start.
2021-04-02 10:35:29 +03:00

95 lines
2.0 KiB
Go

package sync
import (
"sync"
"sync/atomic"
"time"
)
// Cache is a barebones cache implementation.
type Cache struct {
// capacity and length have to be the first words
// in order to be 64-aligned on 32-bit architectures.
capacity, length uint64 // access atomically
entries sync.Map
pool sync.Pool
}
// CacheEntry represents an entry on the cache. You can type assert on V.
type CacheEntry struct {
V interface{}
expiration time.Time
}
// NewCache returns a new instance of Cache.
func NewCache(capacity int) Cache {
return Cache{
capacity: uint64(capacity),
pool: sync.Pool{New: func() interface{} {
return new(CacheEntry)
}},
}
}
// Load loads an entry by given key
func (c *Cache) Load(key string) *CacheEntry {
if mapEntry, ok := c.entries.Load(key); ok {
entry := mapEntry.(*CacheEntry)
if c.expired(entry) {
c.entries.Delete(key)
return nil
}
return entry
}
return nil
}
// Store adds an entry for given key and value
func (c *Cache) Store(key string, val interface{}, expiration time.Time) {
if c.length > c.capacity {
c.evict()
}
poolEntry := c.pool.Get() //nolint: ifshort
if mapEntry, loaded := c.entries.LoadOrStore(key, poolEntry); loaded {
entry := mapEntry.(*CacheEntry)
entry.V = val
entry.expiration = expiration
c.pool.Put(poolEntry)
} else {
entry := poolEntry.(*CacheEntry)
entry.V = val
entry.expiration = expiration
atomic.AddUint64(&c.length, 1)
}
}
// Delete removes an entry by given key
func (c *Cache) Delete(key string) bool {
_, loaded := c.entries.LoadAndDelete(key)
if loaded {
atomic.AddUint64(&c.length, ^uint64(0))
}
return loaded
}
// evict frees memory from the cache by removing entries that exceeded the cache TTL.
func (c *Cache) evict() {
c.entries.Range(func(key, mapEntry interface{}) bool {
entry := mapEntry.(*CacheEntry)
if c.expired(entry) {
c.Delete(key.(string))
}
return true
})
}
// expired checks if an entry is expired
func (c *Cache) expired(e *CacheEntry) bool {
return e.expiration.Before(time.Now())
}