[feature] Read + Write tombstones for deleted Actors (#1005)

* [feature] Read + Write tombstones for deleted Actors

* copyTombstone

* update to use resultcache instead of old ttl cache

Signed-off-by: kim <grufwub@gmail.com>

* update go-cache library to fix result cache capacity / ordering bugs

Signed-off-by: kim <grufwub@gmail.com>

* bump go-cache/v3 to v3.1.6 to fix bugs

Signed-off-by: kim <grufwub@gmail.com>

* switch on status code

* better explain ErrGone reasoning

Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: kim <grufwub@gmail.com>
This commit is contained in:
tobi
2022-11-11 12:18:38 +01:00
committed by GitHub
parent 948e90b95a
commit edcee14d07
47 changed files with 3808 additions and 7 deletions

9
vendor/codeberg.org/gruf/go-cache/v3/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2022 gruf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

341
vendor/codeberg.org/gruf/go-cache/v3/result/cache.go generated vendored Normal file
View File

@@ -0,0 +1,341 @@
package result
import (
"reflect"
"time"
"codeberg.org/gruf/go-cache/v3/ttl"
)
// Cache ...
type Cache[Value any] struct {
cache ttl.Cache[int64, result[Value]] // underlying result cache
lookups structKeys // pre-determined struct lookups
copy func(Value) Value // copies a Value type
next int64 // update key counter
}
// New returns a new initialized Cache, with given lookups and underlying value copy function.
func New[Value any](lookups []string, copy func(Value) Value) *Cache[Value] {
return NewSized(lookups, copy, 64)
}
// NewSized returns a new initialized Cache, with given lookups, underlying value copy function and provided capacity.
func NewSized[Value any](lookups []string, copy func(Value) Value, cap int) *Cache[Value] {
var z Value
// Determine generic type
t := reflect.TypeOf(z)
// Iteratively deref pointer type
for t.Kind() == reflect.Pointer {
t = t.Elem()
}
// Ensure that this is a struct type
if t.Kind() != reflect.Struct {
panic("generic parameter type must be struct (or ptr to)")
}
// Allocate new cache object
c := &Cache[Value]{copy: copy}
c.lookups = make([]keyFields, len(lookups))
for i, lookup := range lookups {
// Generate keyed field info for lookup
c.lookups[i].pkeys = make(map[string]int64, cap)
c.lookups[i].lookup = lookup
c.lookups[i].populate(t)
}
// Create and initialize underlying cache
c.cache.Init(0, cap, 0)
c.SetEvictionCallback(nil)
c.SetInvalidateCallback(nil)
return c
}
// Start will start the cache background eviction routine with given sweep frequency. If already
// running or a freq <= 0 provided, this is a no-op. This will block until eviction routine started.
func (c *Cache[Value]) Start(freq time.Duration) bool {
return c.cache.Start(freq)
}
// Stop will stop cache background eviction routine. If not running this
// is a no-op. This will block until the eviction routine has stopped.
func (c *Cache[Value]) Stop() bool {
return c.cache.Stop()
}
// SetTTL sets the cache item TTL. Update can be specified to force updates of existing items
// in the cache, this will simply add the change in TTL to their current expiry time.
func (c *Cache[Value]) SetTTL(ttl time.Duration, update bool) {
c.cache.SetTTL(ttl, update)
}
// SetEvictionCallback sets the eviction callback to the provided hook.
func (c *Cache[Value]) SetEvictionCallback(hook func(Value)) {
if hook == nil {
// Ensure non-nil hook.
hook = func(Value) {}
}
c.cache.SetEvictionCallback(func(item *ttl.Entry[int64, result[Value]]) {
for _, key := range item.Value.Keys {
// Delete key->pkey lookup
pkeys := key.fields.pkeys
delete(pkeys, key.value)
}
if item.Value.Error != nil {
// Skip error hooks
return
}
// Call user hook.
hook(item.Value.Value)
})
}
// SetInvalidateCallback sets the invalidate callback to the provided hook.
func (c *Cache[Value]) SetInvalidateCallback(hook func(Value)) {
if hook == nil {
// Ensure non-nil hook.
hook = func(Value) {}
}
c.cache.SetInvalidateCallback(func(item *ttl.Entry[int64, result[Value]]) {
for _, key := range item.Value.Keys {
if key.fields != nil {
// Delete key->pkey lookup
pkeys := key.fields.pkeys
delete(pkeys, key.value)
}
}
if item.Value.Error != nil {
// Skip error hooks
return
}
// Call user hook.
hook(item.Value.Value)
})
}
// Load ...
func (c *Cache[Value]) Load(lookup string, load func() (Value, error), keyParts ...any) (Value, error) {
var (
zero Value
res result[Value]
)
// Get lookup map by name.
kfields := c.getFields(lookup)
lmap := kfields.pkeys
// Generate cache key string.
ckey := genkey(keyParts...)
// Acquire cache lock
c.cache.Lock()
// Look for primary key
pkey, ok := lmap[ckey]
if ok {
// Fetch the result for primary key
entry, _ := c.cache.Cache.Get(pkey)
res = entry.Value
}
// Done with lock
c.cache.Unlock()
if !ok {
// Generate new result from fresh load.
res.Value, res.Error = load()
if res.Error != nil {
// This load returned an error, only
// store this item under provided key.
res.Keys = []cacheKey{{
value: ckey,
fields: kfields,
}}
} else {
// This was a successful load, generate keys.
res.Keys = c.lookups.generate(res.Value)
}
// Acquire cache lock.
c.cache.Lock()
defer c.cache.Unlock()
// Attempt to cache this result.
if key, ok := c.storeResult(res); !ok {
return zero, ConflictError{key}
}
}
// Catch and return error
if res.Error != nil {
return zero, res.Error
}
// Return a copy of value from cache
return c.copy(res.Value), nil
}
// Store ...
func (c *Cache[Value]) Store(value Value, store func() error) error {
// Attempt to store this value.
if err := store(); err != nil {
return err
}
// Prepare cached result.
result := result[Value]{
Keys: c.lookups.generate(value),
Value: c.copy(value),
Error: nil,
}
// Acquire cache lock.
c.cache.Lock()
defer c.cache.Unlock()
// Attempt to cache result, only return conflict
// error if the appropriate flag has been set.
if key, ok := c.storeResult(result); !ok {
return ConflictError{key}
}
return nil
}
// Has ...
func (c *Cache[Value]) Has(lookup string, keyParts ...any) bool {
var res result[Value]
// Get lookup map by name.
kfields := c.getFields(lookup)
lmap := kfields.pkeys
// Generate cache key string.
ckey := genkey(keyParts...)
// Acquire cache lock
c.cache.Lock()
// Look for primary key
pkey, ok := lmap[ckey]
if ok {
// Fetch the result for primary key
entry, _ := c.cache.Cache.Get(pkey)
res = entry.Value
}
// Done with lock
c.cache.Unlock()
// Check for non-error result.
return ok && (res.Error == nil)
}
// Invalidate ...
func (c *Cache[Value]) Invalidate(lookup string, keyParts ...any) {
// Get lookup map by name.
kfields := c.getFields(lookup)
lmap := kfields.pkeys
// Generate cache key string.
ckey := genkey(keyParts...)
// Look for primary key
c.cache.Lock()
pkey, ok := lmap[ckey]
c.cache.Unlock()
if !ok {
return
}
// Invalid by primary key
c.cache.Invalidate(pkey)
}
// Clear empties the cache, calling the invalidate callback.
func (c *Cache[Value]) Clear() {
c.cache.Clear()
}
// Len ...
func (c *Cache[Value]) Len() int {
return c.cache.Cache.Len()
}
// Cap ...
func (c *Cache[Value]) Cap() int {
return c.cache.Cache.Cap()
}
func (c *Cache[Value]) getFields(name string) *keyFields {
for _, k := range c.lookups {
// Find key fields with name
if k.lookup == name {
return &k
}
}
panic("invalid lookup: " + name)
}
func (c *Cache[Value]) storeResult(res result[Value]) (string, bool) {
for _, key := range res.Keys {
pkeys := key.fields.pkeys
// Look for cache primary key
pkey, ok := pkeys[key.value]
if ok {
// Look for overlap with non error keys,
// as an overlap for some but not all keys
// could produce inconsistent results.
entry, _ := c.cache.Cache.Get(pkey)
if entry.Value.Error == nil {
return key.value, false
}
}
}
// Get primary key
pkey := c.next
c.next++
// Store all primary key lookups
for _, key := range res.Keys {
pkeys := key.fields.pkeys
pkeys[key.value] = pkey
}
// Store main entry under primary key, using evict hook if needed
c.cache.Cache.SetWithHook(pkey, &ttl.Entry[int64, result[Value]]{
Expiry: time.Now().Add(c.cache.TTL),
Key: pkey,
Value: res,
}, func(_ int64, item *ttl.Entry[int64, result[Value]]) {
c.cache.Evict(item)
})
return "", true
}
type result[Value any] struct {
// keys accessible under
Keys []cacheKey
// cached value
Value Value
// cached error
Error error
}

22
vendor/codeberg.org/gruf/go-cache/v3/result/error.go generated vendored Normal file
View File

@@ -0,0 +1,22 @@
package result
import "errors"
// ErrUnkownLookup ...
var ErrUnknownLookup = errors.New("unknown lookup identifier")
// IsConflictErr returns whether error is due to key conflict.
func IsConflictErr(err error) bool {
_, ok := err.(ConflictError)
return ok
}
// ConflictError is returned on cache key conflict.
type ConflictError struct {
Key string
}
// Error returns the message for this key conflict error.
func (c ConflictError) Error() string {
return "cache conflict for key \"" + c.Key + "\""
}

184
vendor/codeberg.org/gruf/go-cache/v3/result/key.go generated vendored Normal file
View File

@@ -0,0 +1,184 @@
package result
import (
"reflect"
"strings"
"sync"
"unicode"
"unicode/utf8"
"codeberg.org/gruf/go-byteutil"
"codeberg.org/gruf/go-mangler"
)
// structKeys provides convience methods for a list
// of struct field combinations used for cache keys.
type structKeys []keyFields
// get fetches the key-fields for given lookup (else, panics).
func (sk structKeys) get(lookup string) *keyFields {
for i := range sk {
if sk[i].lookup == lookup {
return &sk[i]
}
}
panic("unknown lookup: \"" + lookup + "\"")
}
// generate will calculate the value string for each required
// cache key as laid-out by the receiving structKeys{}.
func (sk structKeys) generate(a any) []cacheKey {
// Get reflected value in order
// to access the struct fields
v := reflect.ValueOf(a)
// Iteratively deref pointer value
for v.Kind() == reflect.Pointer {
if v.IsNil() {
panic("nil ptr")
}
v = v.Elem()
}
// Preallocate expected slice of keys
keys := make([]cacheKey, len(sk))
// Acquire byte buffer
buf := bufpool.Get().(*byteutil.Buffer)
defer bufpool.Put(buf)
for i := range sk {
// Reset buffer
buf.B = buf.B[:0]
// Set the key-fields reference
keys[i].fields = &sk[i]
// Calculate cache-key value
keys[i].populate(buf, v)
}
return keys
}
// cacheKey represents an actual cache key.
type cacheKey struct {
// value is the actual string representing
// this cache key for hashmap lookups.
value string
// fieldsRO is a read-only slice (i.e. we should
// NOT be modifying them, only using for reference)
// of struct fields encapsulated by this cache key.
fields *keyFields
}
// populate will calculate the cache key's value string for given
// value's reflected information. Passed encoder is for string building.
func (k *cacheKey) populate(buf *byteutil.Buffer, v reflect.Value) {
// Append each field value to buffer.
for _, idx := range k.fields.fields {
fv := v.Field(idx)
fi := fv.Interface()
buf.B = mangler.Append(buf.B, fi)
buf.B = append(buf.B, '.')
}
// Drop last '.'
buf.Truncate(1)
// Create string copy from buf
k.value = string(buf.B)
}
// keyFields represents a list of struct fields
// encompassed in a single cache key, the string name
// of the lookup, and the lookup map to primary keys.
type keyFields struct {
// lookup is the calculated (well, provided)
// cache key lookup, consisting of dot sep'd
// struct field names.
lookup string
// fields is a slice of runtime struct field
// indices, of the fields encompassed by this key.
fields []int
// pkeys is a lookup of stored struct key values
// to the primary cache lookup key (int64).
pkeys map[string]int64
}
// populate will populate this keyFields{} object's .fields member by determining
// the field names from the given lookup, and querying given reflected type to get
// the runtime field indices for each of the fields. this speeds-up future value lookups.
func (kf *keyFields) populate(t reflect.Type) {
// Split dot-separated lookup to get
// the individual struct field names
names := strings.Split(kf.lookup, ".")
if len(names) == 0 {
panic("no key fields specified")
}
// Pre-allocate slice of expected length
kf.fields = make([]int, len(names))
for i, name := range names {
// Get field info for given name
ft, ok := t.FieldByName(name)
if !ok {
panic("no field found for name: \"" + name + "\"")
}
// Check field is usable
if !isExported(name) {
panic("field must be exported")
}
// Set the runtime field index
kf.fields[i] = ft.Index[0]
}
}
// genkey generates a cache key for given key values.
func genkey(parts ...any) string {
if len(parts) < 1 {
// Panic to prevent annoying usecase
// where user forgets to pass lookup
// and instead only passes a key part,
// e.g. cache.Get("key")
// which then always returns false.
panic("no key parts provided")
}
// Acquire buffer and reset
buf := bufpool.Get().(*byteutil.Buffer)
defer bufpool.Put(buf)
buf.Reset()
// Encode each key part
for _, part := range parts {
buf.B = mangler.Append(buf.B, part)
buf.B = append(buf.B, '.')
}
// Drop last '.'
buf.Truncate(1)
// Return string copy
return string(buf.B)
}
// isExported checks whether function name is exported.
func isExported(fnName string) bool {
r, _ := utf8.DecodeRuneInString(fnName)
return unicode.IsUpper(r)
}
// bufpool provides a memory pool of byte
// buffers use when encoding key types.
var bufpool = sync.Pool{
New: func() any {
return &byteutil.Buffer{B: make([]byte, 0, 512)}
},
}

20
vendor/codeberg.org/gruf/go-cache/v3/ttl/schedule.go generated vendored Normal file
View File

@@ -0,0 +1,20 @@
package ttl
import (
"time"
"codeberg.org/gruf/go-sched"
)
// scheduler is the global cache runtime scheduler
// for handling regular cache evictions.
var scheduler sched.Scheduler
// schedule will given sweep routine to the global scheduler, and start global scheduler.
func schedule(sweep func(time.Time), freq time.Duration) func() {
if !scheduler.Running() {
// ensure running
_ = scheduler.Start()
}
return scheduler.Schedule(sched.NewJob(sweep).Every(freq))
}

412
vendor/codeberg.org/gruf/go-cache/v3/ttl/ttl.go generated vendored Normal file
View File

@@ -0,0 +1,412 @@
package ttl
import (
"sync"
"time"
"codeberg.org/gruf/go-maps"
)
// Entry represents an item in the cache, with it's currently calculated Expiry time.
type Entry[Key comparable, Value any] struct {
Key Key
Value Value
Expiry time.Time
}
// Cache is the underlying Cache implementation, providing both the base Cache interface and unsafe access to underlying map to allow flexibility in building your own.
type Cache[Key comparable, Value any] struct {
// TTL is the cache item TTL.
TTL time.Duration
// Evict is the hook that is called when an item is evicted from the cache, includes manual delete.
Evict func(*Entry[Key, Value])
// Invalid is the hook that is called when an item's data in the cache is invalidated.
Invalid func(*Entry[Key, Value])
// Cache is the underlying hashmap used for this cache.
Cache maps.LRUMap[Key, *Entry[Key, Value]]
// stop is the eviction routine cancel func.
stop func()
// pool is a memory pool of entry objects.
pool []*Entry[Key, Value]
// Embedded mutex.
sync.Mutex
}
// New returns a new initialized Cache with given initial length, maximum capacity and item TTL.
func New[K comparable, V any](len, cap int, ttl time.Duration) *Cache[K, V] {
c := new(Cache[K, V])
c.Init(len, cap, ttl)
return c
}
// Init will initialize this cache with given initial length, maximum capacity and item TTL.
func (c *Cache[K, V]) Init(len, cap int, ttl time.Duration) {
if ttl <= 0 {
// Default duration
ttl = time.Second * 5
}
c.TTL = ttl
c.SetEvictionCallback(nil)
c.SetInvalidateCallback(nil)
c.Cache.Init(len, cap)
}
// Start: implements cache.Cache's Start().
func (c *Cache[K, V]) Start(freq time.Duration) (ok bool) {
// Nothing to start
if freq <= 0 {
return false
}
// Safely start
c.Lock()
if ok = c.stop == nil; ok {
// Not yet running, schedule us
c.stop = schedule(c.Sweep, freq)
}
// Done with lock
c.Unlock()
return
}
// Stop: implements cache.Cache's Stop().
func (c *Cache[K, V]) Stop() (ok bool) {
// Safely stop
c.Lock()
if ok = c.stop != nil; ok {
// We're running, cancel evicts
c.stop()
c.stop = nil
}
// Done with lock
c.Unlock()
return
}
// Sweep attempts to evict expired items (with callback!) from cache.
func (c *Cache[K, V]) Sweep(now time.Time) {
var after int
// Sweep within lock
c.Lock()
defer c.Unlock()
// Sentinel value
after = -1
// The cache will be ordered by expiry date, we iterate until we reach the index of
// the youngest item that hsa expired, as all succeeding items will also be expired.
c.Cache.RangeIf(0, c.Cache.Len(), func(i int, _ K, item *Entry[K, V]) bool {
if now.After(item.Expiry) {
after = i
// All older than this (including) can be dropped
return false
}
// Continue looping
return true
})
if after == -1 {
// No Truncation needed
return
}
// Truncate items, calling eviction hook
c.truncate(c.Cache.Len()-after, c.Evict)
}
// SetEvictionCallback: implements cache.Cache's SetEvictionCallback().
func (c *Cache[K, V]) SetEvictionCallback(hook func(*Entry[K, V])) {
// Ensure non-nil hook
if hook == nil {
hook = func(*Entry[K, V]) {}
}
// Update within lock
c.Lock()
defer c.Unlock()
// Update hook
c.Evict = hook
}
// SetInvalidateCallback: implements cache.Cache's SetInvalidateCallback().
func (c *Cache[K, V]) SetInvalidateCallback(hook func(*Entry[K, V])) {
// Ensure non-nil hook
if hook == nil {
hook = func(*Entry[K, V]) {}
}
// Update within lock
c.Lock()
defer c.Unlock()
// Update hook
c.Invalid = hook
}
// SetTTL: implements cache.Cache's SetTTL().
func (c *Cache[K, V]) SetTTL(ttl time.Duration, update bool) {
if ttl < 0 {
panic("ttl must be greater than zero")
}
// Update within lock
c.Lock()
defer c.Unlock()
// Set updated TTL
diff := ttl - c.TTL
c.TTL = ttl
if update {
// Update existing cache entries with new expiry time
c.Cache.Range(0, c.Cache.Len(), func(i int, key K, item *Entry[K, V]) {
item.Expiry = item.Expiry.Add(diff)
})
}
}
// Get: implements cache.Cache's Get().
func (c *Cache[K, V]) Get(key K) (V, bool) {
// Read within lock
c.Lock()
defer c.Unlock()
// Check for item in cache
item, ok := c.Cache.Get(key)
if !ok {
var value V
return value, false
}
// Update item expiry and return
item.Expiry = time.Now().Add(c.TTL)
return item.Value, true
}
// Add: implements cache.Cache's Add().
func (c *Cache[K, V]) Add(key K, value V) bool {
// Write within lock
c.Lock()
defer c.Unlock()
// If already cached, return
if c.Cache.Has(key) {
return false
}
// Alloc new item
item := c.alloc()
item.Key = key
item.Value = value
item.Expiry = time.Now().Add(c.TTL)
var hook func(K, *Entry[K, V])
if c.Evict != nil {
// Pass evicted entry to user hook
hook = func(_ K, item *Entry[K, V]) {
c.Evict(item)
}
}
// Place new item in the map with hook
c.Cache.SetWithHook(key, item, hook)
return true
}
// Set: implements cache.Cache's Set().
func (c *Cache[K, V]) Set(key K, value V) {
// Write within lock
c.Lock()
defer c.Unlock()
// Check if already exists
item, ok := c.Cache.Get(key)
if ok {
if c.Invalid != nil {
// Invalidate existing
c.Invalid(item)
}
} else {
// Allocate new item
item = c.alloc()
item.Key = key
c.Cache.Set(key, item)
}
// Update the item value + expiry
item.Expiry = time.Now().Add(c.TTL)
item.Value = value
}
// CAS: implements cache.Cache's CAS().
func (c *Cache[K, V]) CAS(key K, old V, new V, cmp func(V, V) bool) bool {
// CAS within lock
c.Lock()
defer c.Unlock()
// Check for item in cache
item, ok := c.Cache.Get(key)
if !ok || !cmp(item.Value, old) {
return false
}
if c.Invalid != nil {
// Invalidate item
c.Invalid(item)
}
// Update item + Expiry
item.Value = new
item.Expiry = time.Now().Add(c.TTL)
return ok
}
// Swap: implements cache.Cache's Swap().
func (c *Cache[K, V]) Swap(key K, swp V) V {
// Swap within lock
c.Lock()
defer c.Unlock()
// Check for item in cache
item, ok := c.Cache.Get(key)
if !ok {
var value V
return value
}
if c.Invalid != nil {
// invalidate old
c.Invalid(item)
}
old := item.Value
// update item + Expiry
item.Value = swp
item.Expiry = time.Now().Add(c.TTL)
return old
}
// Has: implements cache.Cache's Has().
func (c *Cache[K, V]) Has(key K) bool {
c.Lock()
ok := c.Cache.Has(key)
c.Unlock()
return ok
}
// Invalidate: implements cache.Cache's Invalidate().
func (c *Cache[K, V]) Invalidate(key K) bool {
// Delete within lock
c.Lock()
defer c.Unlock()
// Check if we have item with key
item, ok := c.Cache.Get(key)
if !ok {
return false
}
// Remove from cache map
_ = c.Cache.Delete(key)
if c.Invalid != nil {
// Invalidate item
c.Invalid(item)
}
// Return item to pool
c.free(item)
return true
}
// Clear: implements cache.Cache's Clear().
func (c *Cache[K, V]) Clear() {
c.Lock()
defer c.Unlock()
c.truncate(c.Cache.Len(), c.Invalid)
}
// Len: implements cache.Cache's Len().
func (c *Cache[K, V]) Len() int {
c.Lock()
l := c.Cache.Len()
c.Unlock()
return l
}
// Cap: implements cache.Cache's Cap().
func (c *Cache[K, V]) Cap() int {
c.Lock()
l := c.Cache.Cap()
c.Unlock()
return l
}
// truncate will call Cache.Truncate(sz), and if provided a hook will temporarily store deleted items before passing them to the hook. This is required in order to prevent cache writes during .Truncate().
func (c *Cache[K, V]) truncate(sz int, hook func(*Entry[K, V])) {
if hook == nil {
// No hook was provided, we can simply truncate and free items immediately.
c.Cache.Truncate(sz, func(_ K, item *Entry[K, V]) { c.free(item) })
return
}
// Store list of deleted items for later callbacks
deleted := make([]*Entry[K, V], 0, sz)
// Truncate and store list of deleted items
c.Cache.Truncate(sz, func(_ K, item *Entry[K, V]) {
deleted = append(deleted, item)
})
// Pass each deleted to hook, then free
for _, item := range deleted {
hook(item)
c.free(item)
}
}
// alloc will acquire cache entry from pool, or allocate new.
func (c *Cache[K, V]) alloc() *Entry[K, V] {
if len(c.pool) == 0 {
return &Entry[K, V]{}
}
idx := len(c.pool) - 1
e := c.pool[idx]
c.pool = c.pool[:idx]
return e
}
// free will reset entry fields and place back in pool.
func (c *Cache[K, V]) free(e *Entry[K, V]) {
var (
zk K
zv V
)
e.Key = zk
e.Value = zv
e.Expiry = time.Time{}
c.pool = append(c.pool, e)
}

9
vendor/codeberg.org/gruf/go-mangler/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2022 gruf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

40
vendor/codeberg.org/gruf/go-mangler/README.md generated vendored Normal file
View File

@@ -0,0 +1,40 @@
# go-mangler
[Documentation](https://pkg.go.dev/codeberg.org/gruf/go-mangler).
To put it simply is a bit of an odd library. It aims to provide incredibly fast, unique string outputs for all default supported input data types during a given runtime instance.
It is useful, for example, for use as part of larger abstractions involving hashmaps. That was my particular usecase anyways...
This package does make liberal use of the "unsafe" package.
Benchmarks are below. Those with missing values panicked during our set of benchmarks, usually a case of not handling nil values elegantly. Please note the more important thing to notice here is the relative difference in benchmark scores, the actual `ns/op`,`B/op`,`allocs/op` accounts for running through over 80 possible test cases, including some not-ideal situations.
The choice of libraries in the benchmark are just a selection of libraries that could be used in a similar manner to this one, i.e. serializing in some manner.
```
goos: linux
goarch: amd64
pkg: codeberg.org/gruf/go-mangler
cpu: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
BenchmarkMangle
BenchmarkMangle-8 723278 1593 ns/op 1168 B/op 120 allocs/op
BenchmarkMangleHash
BenchmarkMangleHash-8 405380 2788 ns/op 4496 B/op 214 allocs/op
BenchmarkJSON
BenchmarkJSON-8 199360 6116 ns/op 4243 B/op 142 allocs/op
BenchmarkBinary
BenchmarkBinary-8 ------ ---- ns/op ---- B/op --- allocs/op
BenchmarkFmt
BenchmarkFmt-8 168500 7111 ns/op 2256 B/op 161 allocs/op
BenchmarkKelindarBinary
BenchmarkKelindarBinary-8 ------ ---- ns/op ---- B/op --- allocs/op
BenchmarkFxmackerCbor
BenchmarkFxmackerCbor-8 361416 3255 ns/op 1495 B/op 122 allocs/op
BenchmarkMitchellhHashStructure
BenchmarkMitchellhHashStructure-8 117672 10493 ns/op 8443 B/op 961 allocs/op
BenchmarkCnfStructhash
BenchmarkCnfStructhash-8 7078 161926 ns/op 288644 B/op 5843 allocs/op
PASS
ok codeberg.org/gruf/go-mangler 14.377s
```

97
vendor/codeberg.org/gruf/go-mangler/helpers.go generated vendored Normal file
View File

@@ -0,0 +1,97 @@
package mangler
import (
"reflect"
"unsafe"
)
func deref_ptr_mangler(mangle Mangler, count int) rMangler {
return func(buf []byte, v reflect.Value) []byte {
for i := 0; i < count; i++ {
// Check for nil
if v.IsNil() {
buf = append(buf, '0')
return buf
}
// Further deref ptr
buf = append(buf, '1')
v = v.Elem()
}
// Mangle fully deref'd ptr
return mangle(buf, v.Interface())
}
}
func deref_ptr_rmangler(mangle rMangler, count int) rMangler {
return func(buf []byte, v reflect.Value) []byte {
for i := 0; i < count; i++ {
// Check for nil
if v.IsNil() {
buf = append(buf, '0')
return buf
}
// Further deref ptr
buf = append(buf, '1')
v = v.Elem()
}
// Mangle fully deref'd ptr
return mangle(buf, v)
}
}
func iter_array_mangler(mangle Mangler) rMangler {
return func(buf []byte, v reflect.Value) []byte {
n := v.Len()
for i := 0; i < n; i++ {
buf = mangle(buf, v.Index(i).Interface())
buf = append(buf, ',')
}
if n > 0 {
buf = buf[:len(buf)-1]
}
return buf
}
}
func iter_array_rmangler(mangle rMangler) rMangler {
return func(buf []byte, v reflect.Value) []byte {
n := v.Len()
for i := 0; i < n; i++ {
buf = mangle(buf, v.Index(i))
buf = append(buf, ',')
}
if n > 0 {
buf = buf[:len(buf)-1]
}
return buf
}
}
func iter_map_rmangler(kMangle, vMangle rMangler) rMangler {
return func(buf []byte, v reflect.Value) []byte {
r := v.MapRange()
for r.Next() {
buf = kMangle(buf, r.Key())
buf = append(buf, ':')
buf = vMangle(buf, r.Value())
buf = append(buf, '.')
}
if v.Len() > 0 {
buf = buf[:len(buf)-1]
}
return buf
}
}
// iface_value returns the raw value ptr for input boxed within interface{} type.
func iface_value(a any) unsafe.Pointer {
type eface struct {
Type unsafe.Pointer
Value unsafe.Pointer
}
return (*eface)(unsafe.Pointer(&a)).Value
}

354
vendor/codeberg.org/gruf/go-mangler/load.go generated vendored Normal file
View File

@@ -0,0 +1,354 @@
package mangler
import (
"encoding"
"net/url"
"reflect"
"time"
)
// loadMangler is the top-most Mangler load function. It guarantees that a Mangler
// function will be returned for given value interface{} and reflected type. Else panics.
func loadMangler(a any, t reflect.Type) Mangler {
// Load mangler function
mng, rmng := load(a, t)
if rmng != nil {
// Wrap reflect mangler to handle iface
return func(buf []byte, a any) []byte {
return rmng(buf, reflect.ValueOf(a))
}
}
if mng == nil {
// No mangler function could be determined
panic("cannot mangle type: " + t.String())
}
return mng
}
// load will load a Mangler or reflect Mangler for given type and iface 'a'.
// Note: allocates new interface value if nil provided, i.e. if coming via reflection.
func load(a any, t reflect.Type) (Mangler, rMangler) {
if t == nil {
// There is no reflect type to search by
panic("cannot mangle nil interface{} type")
}
if a == nil {
// Alloc new iface instance
v := reflect.New(t).Elem()
a = v.Interface()
}
// Check in fast iface type switch
if mng := loadIface(a); mng != nil {
return mng, nil
}
// Search by reflection
return loadReflect(t)
}
// loadIface is used as a first-resort interface{} type switcher loader
// for types implementing Mangled and providing performant alternative
// Mangler functions for standard library types to avoid reflection.
func loadIface(a any) Mangler {
switch a.(type) {
case Mangled:
return mangle_mangled
case time.Time:
return mangle_time
case *time.Time:
return mangle_time_ptr
case *url.URL:
return mangle_stringer
case encoding.BinaryMarshaler:
return mangle_binary
// NOTE:
// we don't just handle ALL fmt.Stringer types as often
// the output is large and unwieldy and this interface
// switch is for types it would be faster to avoid reflection.
// If they want better performance they can implement Mangled{}.
default:
return nil
}
}
// loadReflect will load a Mangler (or rMangler) function for the given reflected type info.
// NOTE: this is used as the top level load function for nested reflective searches.
func loadReflect(t reflect.Type) (Mangler, rMangler) {
switch t.Kind() {
case reflect.Pointer:
return loadReflectPtr(t.Elem())
case reflect.String:
return mangle_string, nil
case reflect.Array:
return nil, loadReflectArray(t.Elem())
case reflect.Slice:
// Element type
et := t.Elem()
// Preferably look for known slice mangler func
if mng := loadReflectKnownSlice(et); mng != nil {
return mng, nil
}
// Else handle as array elements
return nil, loadReflectArray(et)
case reflect.Map:
return nil, loadReflectMap(t.Key(), t.Elem())
case reflect.Bool:
return mangle_bool, nil
case reflect.Int,
reflect.Uint,
reflect.Uintptr:
return mangle_platform_int, nil
case reflect.Int8,
reflect.Uint8:
return mangle_8bit, nil
case reflect.Int16,
reflect.Uint16:
return mangle_16bit, nil
case reflect.Int32,
reflect.Uint32:
return mangle_32bit, nil
case reflect.Int64,
reflect.Uint64:
return mangle_64bit, nil
case reflect.Float32:
return mangle_32bit, nil
case reflect.Float64:
return mangle_64bit, nil
case reflect.Complex64:
return mangle_64bit, nil
case reflect.Complex128:
return mangle_128bit, nil
default:
return nil, nil
}
}
// loadReflectPtr loads a Mangler (or rMangler) function for a ptr's element type.
// This also handles further dereferencing of any further ptr indrections (e.g. ***int).
func loadReflectPtr(et reflect.Type) (Mangler, rMangler) {
count := 1
// Iteratively dereference ptrs
for et.Kind() == reflect.Pointer {
et = et.Elem()
count++
}
if et.Kind() == reflect.Array {
// Special case of addressable (sliceable) array
if mng := loadReflectKnownSlice(et); mng != nil {
if count == 1 {
return mng, nil
}
return nil, deref_ptr_mangler(mng, count-1)
}
// Look for an array mangler function, this will
// access elements by index using reflect.Value and
// pass each one to a separate mangler function.
if rmng := loadReflectArray(et); rmng != nil {
return nil, deref_ptr_rmangler(rmng, count)
}
return nil, nil
}
// Try remove a layer of derefs by loading a mangler
// for a known ptr kind. The less reflection the better!
if mng := loadReflectKnownPtr(et); mng != nil {
if count == 1 {
return mng, nil
}
return nil, deref_ptr_mangler(mng, count-1)
}
// Search for ptr elemn type mangler
if mng, rmng := load(nil, et); mng != nil {
return nil, deref_ptr_mangler(mng, count)
} else if rmng != nil {
return nil, deref_ptr_rmangler(rmng, count)
}
return nil, nil
}
// loadReflectKnownPtr loads a Mangler function for a known ptr-of-element type (in this case, primtive ptrs).
func loadReflectKnownPtr(et reflect.Type) Mangler {
switch et.Kind() {
case reflect.String:
return mangle_string_ptr
case reflect.Bool:
return mangle_bool_ptr
case reflect.Int,
reflect.Uint,
reflect.Uintptr:
return mangle_platform_int_ptr
case reflect.Int8,
reflect.Uint8:
return mangle_8bit_ptr
case reflect.Int16,
reflect.Uint16:
return mangle_16bit_ptr
case reflect.Int32,
reflect.Uint32:
return mangle_32bit_ptr
case reflect.Int64,
reflect.Uint64:
return mangle_64bit_ptr
case reflect.Float32:
return mangle_32bit_ptr
case reflect.Float64:
return mangle_64bit_ptr
case reflect.Complex64:
return mangle_64bit_ptr
case reflect.Complex128:
return mangle_128bit_ptr
default:
return nil
}
}
// loadReflectKnownSlice loads a Mangler function for a known slice-of-element type (in this case, primtives).
func loadReflectKnownSlice(et reflect.Type) Mangler {
switch et.Kind() {
case reflect.String:
return mangle_string_slice
case reflect.Bool:
return mangle_bool_slice
case reflect.Int,
reflect.Uint,
reflect.Uintptr:
return mangle_platform_int_slice
case reflect.Int8,
reflect.Uint8:
return mangle_8bit_slice
case reflect.Int16,
reflect.Uint16:
return mangle_16bit_slice
case reflect.Int32,
reflect.Uint32:
return mangle_32bit_slice
case reflect.Int64,
reflect.Uint64:
return mangle_64bit_slice
case reflect.Float32:
return mangle_32bit_slice
case reflect.Float64:
return mangle_64bit_slice
case reflect.Complex64:
return mangle_64bit_slice
case reflect.Complex128:
return mangle_128bit_slice
default:
return nil
}
}
// loadReflectArray loads an rMangler function for an array (or slice) or given element type.
func loadReflectArray(et reflect.Type) rMangler {
// Search via reflected array element type
if mng, rmng := load(nil, et); mng != nil {
return iter_array_mangler(mng)
} else if rmng != nil {
return iter_array_rmangler(rmng)
}
return nil
}
// loadReflectMap ...
func loadReflectMap(kt, vt reflect.Type) rMangler {
var kmng, vmng rMangler
// Search for mangler for key type
mng, rmng := load(nil, kt)
switch {
// Wrap key mangler to reflect
case mng != nil:
mng := mng // take our own ptr
kmng = func(buf []byte, v reflect.Value) []byte {
return mng(buf, v.Interface())
}
// Use reflect key mangler as-is
case rmng != nil:
kmng = rmng
// No mangler found
default:
return nil
}
// Search for mangler for value type
mng, rmng = load(nil, vt)
switch {
// Wrap key mangler to reflect
case mng != nil:
mng := mng // take our own ptr
vmng = func(buf []byte, v reflect.Value) []byte {
return mng(buf, v.Interface())
}
// Use reflect key mangler as-is
case rmng != nil:
vmng = rmng
// No mangler found
default:
return nil
}
// Wrap key/value manglers in map iter
return iter_map_rmangler(kmng, vmng)
}

132
vendor/codeberg.org/gruf/go-mangler/mangle.go generated vendored Normal file
View File

@@ -0,0 +1,132 @@
package mangler
import (
"encoding/binary"
"reflect"
"unsafe"
"github.com/cespare/xxhash"
"github.com/cornelk/hashmap"
)
var (
// manglers is a map of runtime type ptrs => Mangler functions.
manglers = hashmap.New[uintptr, Mangler]()
// bin is a short-hand for our chosen byteorder encoding.
bin = binary.LittleEndian
)
// Mangled is an interface that allows any type to implement a custom
// Mangler function to improve performance when mangling this type.
type Mangled interface {
Mangle(buf []byte) []byte
}
// Mangler is a function that will take an input interface value of known
// type, and append it in mangled serialized form to the given byte buffer.
// While the value type is an interface, the Mangler functions are accessed
// by the value's runtime type pointer, allowing the input value type to be known.
type Mangler func(buf []byte, value any) []byte
// rMangler is functionally the same as a Mangler function, but it
// takes the value input in reflected form. By specifying these differences
// in mangler function types, it allows us to cut back on new calls to
// `reflect.ValueOf()` and instead pass by existing reflected values.
type rMangler func(buf []byte, value reflect.Value) []byte
// Get will fetch the Mangler function for given runtime type.
func Get(t reflect.Type) (Mangler, bool) {
if t == nil {
return nil, false
}
uptr := uintptr(iface_value(t))
return manglers.Get(uptr)
}
// Register will register the given Mangler function for use with vars of given runtime type. This allows
// registering performant manglers for existing types not implementing Mangled (e.g. std library types).
// NOTE: panics if there already exists a Mangler function for given type. Register on init().
func Register(t reflect.Type, m Mangler) {
if t == nil {
// Nil interface{} types cannot be searched by, do not accept
panic("cannot register mangler for nil interface{} type")
}
// Get raw runtime type ptr
uptr := uintptr(iface_value(t))
// Ensure this is a unique encoder
if _, ok := manglers.Get(uptr); ok {
panic("already registered mangler for type: " + t.String())
}
// Cache this encoder func
manglers.Set(uptr, m)
}
// Append will append the mangled form of input value 'a' to buffer 'b'.
// See mangler.String() for more information on mangled output.
func Append(b []byte, a any) []byte {
// Get reflect type of 'a'
t := reflect.TypeOf(a)
// Get raw runtime type ptr
uptr := uintptr(iface_value(t))
// Look for a cached mangler
mng, ok := manglers.Get(uptr)
if !ok {
// Load mangler into cache
mng = loadMangler(a, t)
manglers.Set(uptr, mng)
}
// First write the type ptr (this adds
// a unique prefix for each runtime type).
b = mangle_platform_int(b, uptr)
// Finally, mangle value
return mng(b, a)
}
// String will return the mangled format of input value 'a'. This
// mangled output will be unique for all default supported input types
// during a single runtime instance. Uniqueness cannot be guaranteed
// between separate runtime instances (whether running concurrently, or
// the same application running at different times).
//
// The exact formatting of the output data should not be relied upon,
// only that it is unique given the above constraints. Generally though,
// the mangled output is the binary formatted text of given input data.
//
// Uniqueness is guaranteed for similar input data of differing types
// (e.g. string("hello world") vs. []byte("hello world")) by prefixing
// mangled output with the input data's runtime type pointer.
//
// Default supported types include:
// - string
// - bool
// - int,int8,int16,int32,int64
// - uint,uint8,uint16,uint32,uint64,uintptr
// - float32,float64
// - complex64,complex128
// - all type aliases of above
// - time.Time{}, *url.URL{}
// - mangler.Mangled{}
// - encoding.BinaryMarshaler{}
// - all pointers to the above
// - all slices / arrays of the above
// - all map keys / values of the above
func String(a any) string {
b := Append(make([]byte, 0, 32), a)
return *(*string)(unsafe.Pointer(&b))
}
// Hash returns the xxHash digest of the result of mangler.Append(nil, 'a').
func Hash(a any) uint64 {
b := make([]byte, 0, 32)
b = Append(b, a)
return xxhash.Sum64(b)
}

264
vendor/codeberg.org/gruf/go-mangler/manglers.go generated vendored Normal file
View File

@@ -0,0 +1,264 @@
package mangler
import (
"encoding"
"fmt"
"math/bits"
"time"
_ "unsafe"
)
// Notes:
// the use of unsafe conversion from the direct interface values to
// the chosen types in each of the below functions allows us to convert
// not only those types directly, but anything type-aliased to those
// types. e.g. `time.Duration` directly as int64.
func mangle_string(buf []byte, a any) []byte {
return append(buf, *(*string)(iface_value(a))...)
}
func mangle_string_ptr(buf []byte, a any) []byte {
if ptr := (*string)(iface_value(a)); ptr != nil {
buf = append(buf, '1')
return append(buf, *ptr...)
}
buf = append(buf, '0')
return buf
}
func mangle_string_slice(buf []byte, a any) []byte {
s := *(*[]string)(iface_value(a))
for _, s := range s {
buf = append(buf, s...)
buf = append(buf, ',')
}
if len(s) > 0 {
buf = buf[:len(buf)-1]
}
return buf
}
func mangle_bool(buf []byte, a any) []byte {
if *(*bool)(iface_value(a)) {
return append(buf, '1')
}
return append(buf, '0')
}
func mangle_bool_ptr(buf []byte, a any) []byte {
if ptr := (*bool)(iface_value(a)); ptr != nil {
buf = append(buf, '1')
if *ptr {
return append(buf, '1')
}
return append(buf, '0')
}
buf = append(buf, '0')
return buf
}
func mangle_bool_slice(buf []byte, a any) []byte {
for _, b := range *(*[]bool)(iface_value(a)) {
if b {
buf = append(buf, '1')
} else {
buf = append(buf, '0')
}
}
return buf
}
func mangle_8bit(buf []byte, a any) []byte {
return append(buf, *(*uint8)(iface_value(a)))
}
func mangle_8bit_ptr(buf []byte, a any) []byte {
if ptr := (*uint8)(iface_value(a)); ptr != nil {
buf = append(buf, '1')
return append(buf, *ptr)
}
buf = append(buf, '0')
return buf
}
func mangle_8bit_slice(buf []byte, a any) []byte {
return append(buf, *(*[]uint8)(iface_value(a))...)
}
func mangle_16bit(buf []byte, a any) []byte {
return bin.AppendUint16(buf, *(*uint16)(iface_value(a)))
}
func mangle_16bit_ptr(buf []byte, a any) []byte {
if ptr := (*uint16)(iface_value(a)); ptr != nil {
buf = append(buf, '1')
return bin.AppendUint16(buf, *ptr)
}
buf = append(buf, '0')
return buf
}
func mangle_16bit_slice(buf []byte, a any) []byte {
for _, u := range *(*[]uint16)(iface_value(a)) {
buf = bin.AppendUint16(buf, u)
}
return buf
}
func mangle_32bit(buf []byte, a any) []byte {
return bin.AppendUint32(buf, *(*uint32)(iface_value(a)))
}
func mangle_32bit_ptr(buf []byte, a any) []byte {
if ptr := (*uint32)(iface_value(a)); ptr != nil {
buf = append(buf, '1')
return bin.AppendUint32(buf, *ptr)
}
buf = append(buf, '0')
return buf
}
func mangle_32bit_slice(buf []byte, a any) []byte {
for _, u := range *(*[]uint32)(iface_value(a)) {
buf = bin.AppendUint32(buf, u)
}
return buf
}
func mangle_64bit(buf []byte, a any) []byte {
return bin.AppendUint64(buf, *(*uint64)(iface_value(a)))
}
func mangle_64bit_ptr(buf []byte, a any) []byte {
if ptr := (*uint64)(iface_value(a)); ptr != nil {
buf = append(buf, '1')
return bin.AppendUint64(buf, *ptr)
}
buf = append(buf, '0')
return buf
}
func mangle_64bit_slice(buf []byte, a any) []byte {
for _, u := range *(*[]uint64)(iface_value(a)) {
buf = bin.AppendUint64(buf, u)
}
return buf
}
// mangle_platform_int contains the correct iface mangler on runtime for platform int size.
var mangle_platform_int = func() Mangler {
switch bits.UintSize {
case 32:
return mangle_32bit
case 64:
return mangle_64bit
default:
panic("unexpected platform int size")
}
}()
// mangle_platform_int_ptr contains the correct iface mangler on runtime for platform int size.
var mangle_platform_int_ptr = func() Mangler {
switch bits.UintSize {
case 32:
return mangle_32bit_ptr
case 64:
return mangle_64bit_ptr
default:
panic("unexpected platform int size")
}
}()
// mangle_platform_int_slice contains the correct iface mangler on runtime for platform int size.
var mangle_platform_int_slice = func() Mangler {
switch bits.UintSize {
case 32:
return mangle_32bit_slice
case 64:
return mangle_64bit_slice
default:
panic("unexpected platform int size")
}
}()
// uint128 provides an easily mangleable data type for 128bit data types to be cast into.
type uint128 [2]uint64
func mangle_128bit(buf []byte, a any) []byte {
u2 := *(*uint128)(iface_value(a))
buf = bin.AppendUint64(buf, u2[0])
buf = bin.AppendUint64(buf, u2[1])
return buf
}
func mangle_128bit_ptr(buf []byte, a any) []byte {
if ptr := (*uint128)(iface_value(a)); ptr != nil {
buf = append(buf, '1')
buf = bin.AppendUint64(buf, (*ptr)[0])
buf = bin.AppendUint64(buf, (*ptr)[1])
}
buf = append(buf, '0')
return buf
}
func mangle_128bit_slice(buf []byte, a any) []byte {
for _, u2 := range *(*[]uint128)(iface_value(a)) {
buf = bin.AppendUint64(buf, u2[0])
buf = bin.AppendUint64(buf, u2[1])
}
return buf
}
func mangle_time(buf []byte, a any) []byte {
t := *(*time.Time)(iface_value(a))
b, err := t.MarshalBinary()
if err != nil {
panic("marshal_time: " + err.Error())
}
return append(buf, b...)
}
func mangle_time_ptr(buf []byte, a any) []byte {
if ptr := (*time.Time)(iface_value(a)); ptr != nil {
b, err := ptr.MarshalBinary()
if err != nil {
panic("marshal_time: " + err.Error())
}
buf = append(buf, '1')
return append(buf, b...)
}
buf = append(buf, '0')
return buf
}
func mangle_mangled(buf []byte, a any) []byte {
if v := a.(Mangled); v != nil {
buf = append(buf, '1')
return v.Mangle(buf)
}
buf = append(buf, '0')
return buf
}
func mangle_binary(buf []byte, a any) []byte {
if v := a.(encoding.BinaryMarshaler); v != nil {
b, err := v.MarshalBinary()
if err != nil {
panic("mangle_binary: " + err.Error())
}
buf = append(buf, '1')
return append(buf, b...)
}
buf = append(buf, '0')
return buf
}
func mangle_stringer(buf []byte, a any) []byte {
if v := a.(fmt.Stringer); v != nil {
buf = append(buf, '1')
return append(buf, v.String()...)
}
buf = append(buf, '0')
return buf
}

9
vendor/codeberg.org/gruf/go-maps/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) 2022 gruf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

7
vendor/codeberg.org/gruf/go-maps/README.md generated vendored Normal file
View File

@@ -0,0 +1,7 @@
# go-maps
Provides a selection of hashmaps (or, "dictionaries") with features exceeding that of the default Go runtime hashmap.
Includes:
- OrderedMap
- LRUMap

289
vendor/codeberg.org/gruf/go-maps/common.go generated vendored Normal file
View File

@@ -0,0 +1,289 @@
package maps
import (
"fmt"
"reflect"
"codeberg.org/gruf/go-byteutil"
"codeberg.org/gruf/go-kv"
)
// ordered provides a common ordered hashmap base, storing order in a doubly-linked list.
type ordered[K comparable, V any] struct {
hmap map[K]*elem[K, V]
list list[K, V]
pool []*elem[K, V]
rnly bool
}
// write_check panics if map is not in a safe-state to write to.
func (m ordered[K, V]) write_check() {
if m.rnly {
panic("map write during read loop")
}
}
// Has returns whether key exists in map.
func (m *ordered[K, V]) Has(key K) bool {
_, ok := m.hmap[key]
return ok
}
// Delete will delete given key from map, returns false if not found.
func (m *ordered[K, V]) Delete(key K) bool {
// Ensure safe
m.write_check()
// Look for existing elem
elem, ok := m.hmap[key]
if !ok {
return false
}
// Drop from list
m.list.Unlink(elem)
// Delete from map
delete(m.hmap, key)
// Return to pool
m.free(elem)
return true
}
// Range passes given function over the requested range of the map.
func (m *ordered[K, V]) Range(start, length int, fn func(int, K, V)) {
// Disallow writes
m.rnly = true
defer func() {
m.rnly = false
}()
// Nil check
_ = fn
switch end := start + length; {
// No loop to iterate
case length == 0:
if start < 0 || (m.list.len > 0 && start >= m.list.len) {
panic("index out of bounds")
}
// Step backwards
case length < 0:
// Check loop indices are within map bounds
if end < -1 || start >= m.list.len || m.list.len == 0 {
panic("index out of bounds")
}
// Get starting index elem
elem := m.list.Index(start)
for i := start; i > end; i-- {
fn(i, elem.K, elem.V)
elem = elem.prev
}
// Step forwards
case length > 0:
// Check loop indices are within map bounds
if start < 0 || end > m.list.len || m.list.len == 0 {
panic("index out of bounds")
}
// Get starting index elem
elem := m.list.Index(start)
for i := start; i < end; i++ {
fn(i, elem.K, elem.V)
elem = elem.next
}
}
}
// RangeIf passes given function over the requested range of the map. Returns early on 'fn' -> false.
func (m *ordered[K, V]) RangeIf(start, length int, fn func(int, K, V) bool) {
// Disallow writes
m.rnly = true
defer func() {
m.rnly = false
}()
// Nil check
_ = fn
switch end := start + length; {
// No loop to iterate
case length == 0:
if start < 0 || (m.list.len > 0 && start >= m.list.len) {
panic("index out of bounds")
}
// Step backwards
case length < 0:
// Check loop indices are within map bounds
if end < -1 || start >= m.list.len || m.list.len == 0 {
panic("index out of bounds")
}
// Get starting index elem
elem := m.list.Index(start)
for i := start; i > end; i-- {
if !fn(i, elem.K, elem.V) {
return
}
elem = elem.prev
}
// Step forwards
case length > 0:
// Check loop indices are within map bounds
if start < 0 || end > m.list.len || m.list.len == 0 {
panic("index out of bounds")
}
// Get starting index elem
elem := m.list.Index(start)
for i := start; i < end; i++ {
if !fn(i, elem.K, elem.V) {
return
}
elem = elem.next
}
}
}
// Truncate will truncate the map from the back by given amount, passing dropped elements to given function.
func (m *ordered[K, V]) Truncate(sz int, fn func(K, V)) {
// Check size withing bounds
if sz > m.list.len {
panic("index out of bounds")
}
if fn == nil {
// move nil check out of loop
fn = func(K, V) {}
}
// Disallow writes
m.rnly = true
defer func() {
m.rnly = false
}()
for i := 0; i < sz; i++ {
// Pop current tail
elem := m.list.tail
m.list.Unlink(elem)
// Delete from map
delete(m.hmap, elem.K)
// Pass dropped to func
fn(elem.K, elem.V)
// Release to pool
m.free(elem)
}
}
// Len returns the current length of the map.
func (m *ordered[K, V]) Len() int {
return m.list.len
}
// format implements fmt.Formatter, allowing performant string formatting of map.
func (m *ordered[K, V]) format(rtype reflect.Type, state fmt.State, verb rune) {
var (
kvbuf byteutil.Buffer
field kv.Field
vbose bool
)
switch {
// Only handle 'v' verb
case verb != 'v':
panic("invalid verb '" + string(verb) + "' for map")
// Prefix with type when verbose
case state.Flag('#'):
state.Write([]byte(rtype.String()))
}
// Disallow writes
m.rnly = true
defer func() {
m.rnly = false
}()
// Write map opening brace
state.Write([]byte{'{'})
if m.list.len > 0 {
// Preallocate buffer
kvbuf.Guarantee(64)
// Start at index 0
elem := m.list.head
for i := 0; i < m.list.len-1; i++ {
// Append formatted key-val pair to state
field.K = fmt.Sprint(elem.K)
field.V = elem.V
field.AppendFormat(&kvbuf, vbose)
_, _ = state.Write(kvbuf.B)
kvbuf.Reset()
// Prepare buffer with comma separator
kvbuf.B = append(kvbuf.B, `, `...)
// Jump to next in list
elem = elem.next
}
// Append formatted key-val pair to state
field.K = fmt.Sprint(elem.K)
field.V = elem.V
field.AppendFormat(&kvbuf, vbose)
_, _ = state.Write(kvbuf.B)
}
// Write map closing brace
state.Write([]byte{'}'})
}
// Std returns a clone of map's data in the standard library equivalent map type.
func (m *ordered[K, V]) Std() map[K]V {
std := make(map[K]V, m.list.len)
for _, elem := range m.hmap {
std[elem.K] = elem.V
}
return std
}
// alloc will acquire list element from pool, or allocate new.
func (m *ordered[K, V]) alloc() *elem[K, V] {
if len(m.pool) == 0 {
return &elem[K, V]{}
}
idx := len(m.pool) - 1
elem := m.pool[idx]
m.pool = m.pool[:idx]
return elem
}
// free will reset elem fields and place back in pool.
func (m *ordered[K, V]) free(elem *elem[K, V]) {
var (
zk K
zv V
)
elem.K = zk
elem.V = zv
elem.next = nil
elem.prev = nil
m.pool = append(m.pool, elem)
}

154
vendor/codeberg.org/gruf/go-maps/list.go generated vendored Normal file
View File

@@ -0,0 +1,154 @@
package maps
// list is a doubly-linked list containing elemnts with key-value pairs of given generic parameter types.
type list[K comparable, V any] struct {
head *elem[K, V]
tail *elem[K, V]
len int
}
// Index returns the element at index from list.
func (l *list[K, V]) Index(idx int) *elem[K, V] {
switch {
// Idx in first half
case idx < l.len/2:
elem := l.head
for i := 0; i < idx; i++ {
elem = elem.next
}
return elem
// Index in last half
default:
elem := l.tail
for i := l.len - 1; i > idx; i-- {
elem = elem.prev
}
return elem
}
}
// PushFront will push the given element to the front of the list.
func (l *list[K, V]) PushFront(elem *elem[K, V]) {
if l.len == 0 {
// Set new tail + head
l.head = elem
l.tail = elem
// Link elem to itself
elem.next = elem
elem.prev = elem
} else {
oldHead := l.head
// Link to old head
elem.next = oldHead
oldHead.prev = elem
// Link up to tail
elem.prev = l.tail
l.tail.next = elem
// Set new head
l.head = elem
}
// Incr count
l.len++
}
// PushBack will push the given element to the back of the list.
func (l *list[K, V]) PushBack(elem *elem[K, V]) {
if l.len == 0 {
// Set new tail + head
l.head = elem
l.tail = elem
// Link elem to itself
elem.next = elem
elem.prev = elem
} else {
oldTail := l.tail
// Link up to head
elem.next = l.head
l.head.prev = elem
// Link to old tail
elem.prev = oldTail
oldTail.next = elem
// Set new tail
l.tail = elem
}
// Incr count
l.len++
}
// PopTail will pop the current tail of the list, returns nil if empty.
func (l *list[K, V]) PopTail() *elem[K, V] {
if l.len == 0 {
return nil
}
elem := l.tail
l.Unlink(elem)
return elem
}
// Unlink will unlink the given element from the doubly-linked list chain.
func (l *list[K, V]) Unlink(elem *elem[K, V]) {
if l.len <= 1 {
// Drop elem's links
elem.next = nil
elem.prev = nil
// Only elem in list
l.head = nil
l.tail = nil
l.len = 0
return
}
// Get surrounding elems
next := elem.next
prev := elem.prev
// Relink chain
next.prev = prev
prev.next = next
switch elem {
// Set new head
case l.head:
l.head = next
// Set new tail
case l.tail:
l.tail = prev
}
// Drop elem's links
elem.next = nil
elem.prev = nil
// Decr count
l.len--
}
// elem represents an element in a doubly-linked list.
type elem[K comparable, V any] struct {
next *elem[K, V]
prev *elem[K, V]
K K
V V
}
// allocElems will allocate a slice of empty elements of length.
func allocElems[K comparable, V any](i int) []*elem[K, V] {
s := make([]*elem[K, V], i)
for i := range s {
s[i] = &elem[K, V]{}
}
return s
}

153
vendor/codeberg.org/gruf/go-maps/lru.go generated vendored Normal file
View File

@@ -0,0 +1,153 @@
package maps
import (
"fmt"
"reflect"
)
// LRU provides an ordered hashmap implementation that keeps elements ordered according to last recently used (hence, LRU).
type LRUMap[K comparable, V any] struct {
ordered[K, V]
size int
}
// NewLRU returns a new instance of LRUMap with given initializing length and maximum capacity.
func NewLRU[K comparable, V any](len, cap int) *LRUMap[K, V] {
m := new(LRUMap[K, V])
m.Init(len, cap)
return m
}
// Init will initialize this map with initial length and maximum capacity.
func (m *LRUMap[K, V]) Init(len, cap int) {
if cap <= 0 {
panic("lru cap must be greater than zero")
} else if m.pool != nil {
panic("lru map already initialized")
}
m.ordered.hmap = make(map[K]*elem[K, V], len)
m.ordered.pool = allocElems[K, V](len)
m.size = cap
}
// Get will fetch value for given key from map, in the process pushing it to the front of the map. Returns false if not found.
func (m *LRUMap[K, V]) Get(key K) (V, bool) {
if elem, ok := m.hmap[key]; ok {
// Ensure safe
m.write_check()
// Unlink elem from list
m.list.Unlink(elem)
// Push to front of list
m.list.PushFront(elem)
return elem.V, true
}
var z V // zero value
return z, false
}
// Add will add the given key-value pair to the map, pushing them to the front of the map. Returns false if already exists. Evicts old at maximum capacity.
func (m *LRUMap[K, V]) Add(key K, value V) bool {
return m.AddWithHook(key, value, nil)
}
// AddWithHook performs .Add() but passing any evicted entry to given hook function.
func (m *LRUMap[K, V]) AddWithHook(key K, value V, evict func(K, V)) bool {
// Ensure safe
m.write_check()
// Look for existing elem
elem, ok := m.hmap[key]
if ok {
return false
}
if m.list.len >= m.size {
// We're at capacity, sir!
// Pop current tail elem
elem = m.list.PopTail()
if evict != nil {
// Pass to evict hook
evict(elem.K, elem.V)
}
// Delete key from map
delete(m.hmap, elem.K)
} else {
// Allocate elem
elem = m.alloc()
}
// Set elem
elem.K = key
elem.V = value
// Add element map entry
m.hmap[key] = elem
// Push to front of list
m.list.PushFront(elem)
return true
}
// Set will ensure that given key-value pair exists in the map, by either adding new or updating existing, pushing them to the front of the map. Evicts old at maximum capacity.
func (m *LRUMap[K, V]) Set(key K, value V) {
m.SetWithHook(key, value, nil)
}
// SetWithHook performs .Set() but passing any evicted entry to given hook function.
func (m *LRUMap[K, V]) SetWithHook(key K, value V, evict func(K, V)) {
// Ensure safe
m.write_check()
// Look for existing elem
elem, ok := m.hmap[key]
if ok {
// Unlink elem from list
m.list.Unlink(elem)
// Update existing
elem.V = value
} else {
if m.list.len >= m.size {
// We're at capacity, sir!
// Pop current tail elem
elem = m.list.PopTail()
if evict != nil {
// Pass to evict hook
evict(elem.K, elem.V)
}
// Delete key from map
delete(m.hmap, elem.K)
} else {
// Allocate elem
elem = m.alloc()
}
// Set elem
elem.K = key
elem.V = value
// Add element map entry
m.hmap[key] = elem
}
// Push to front of list
m.list.PushFront(elem)
}
// Cap returns the maximum capacity of this LRU map.
func (m *LRUMap[K, V]) Cap() int {
return m.size
}
// Format implements fmt.Formatter, allowing performant string formatting of map.
func (m *LRUMap[K, V]) Format(state fmt.State, verb rune) {
m.format(reflect.TypeOf(m), state, verb)
}

159
vendor/codeberg.org/gruf/go-maps/ordered.go generated vendored Normal file
View File

@@ -0,0 +1,159 @@
package maps
import (
"fmt"
"reflect"
)
// OrderedMap provides a hashmap implementation that tracks the order in which keys are added.
type OrderedMap[K comparable, V any] struct {
ordered[K, V]
}
// NewOrdered returns a new instance of LRUMap with given initializing length and maximum capacity.
func NewOrdered[K comparable, V any](len int) *OrderedMap[K, V] {
m := new(OrderedMap[K, V])
m.Init(len)
return m
}
// Init will initialize this map with initial length.
func (m *OrderedMap[K, V]) Init(len int) {
if m.pool != nil {
panic("ordered map already initialized")
}
m.ordered.hmap = make(map[K]*elem[K, V], len)
m.ordered.pool = allocElems[K, V](len)
}
// Get will fetch value for given key from map. Returns false if not found.
func (m *OrderedMap[K, V]) Get(key K) (V, bool) {
if elem, ok := m.hmap[key]; ok {
return elem.V, true
}
var z V // zero value
return z, false
}
// Add will add the given key-value pair to the map, returns false if already exists.
func (m *OrderedMap[K, V]) Add(key K, value V) bool {
// Ensure safe
m.write_check()
// Look for existing elem
elem, ok := m.hmap[key]
if ok {
return false
}
// Allocate elem
elem = m.alloc()
elem.K = key
elem.V = value
// Add element map entry
m.hmap[key] = elem
// Push to back of list
m.list.PushBack(elem)
return true
}
// Set will ensure that given key-value pair exists in the map, by either adding new or updating existing.
func (m *OrderedMap[K, V]) Set(key K, value V) {
// Ensure safe
m.write_check()
// Look for existing elem
elem, ok := m.hmap[key]
if ok {
// Update existing
elem.V = value
} else {
// Allocate elem
elem = m.alloc()
elem.K = key
elem.V = value
// Add element map entry
m.hmap[key] = elem
// Push to back of list
m.list.PushBack(elem)
}
}
// Index returns the key-value pair at index from map. Returns false if index out of range.
func (m *OrderedMap[K, V]) Index(idx int) (K, V, bool) {
if idx < 0 || idx >= m.list.len {
var (
zk K
zv V
) // zero values
return zk, zv, false
}
elem := m.list.Index(idx)
return elem.K, elem.V, true
}
// Push will insert the given key-value pair at index in the map. Panics if index out of range.
func (m *OrderedMap[K, V]) Push(idx int, key K, value V) {
// Check index within bounds of map
if idx < 0 || idx >= m.list.len {
panic("index out of bounds")
}
// Ensure safe
m.write_check()
// Get element at index
next := m.list.Index(idx)
// Allocate new elem
elem := m.alloc()
elem.K = key
elem.V = value
// Add element map entry
m.hmap[key] = elem
// Move next forward
elem.next = next
elem.prev = next.prev
// Link up elem in chain
next.prev.next = elem
next.prev = elem
}
// Pop will remove and return the key-value pair at index in the map. Panics if index out of range.
func (m *OrderedMap[K, V]) Pop(idx int) (K, V) {
// Check index within bounds of map
if idx < 0 || idx >= m.list.len {
panic("index out of bounds")
}
// Ensure safe
m.write_check()
// Get element at index
elem := m.list.Index(idx)
// Unlink elem from list
m.list.Unlink(elem)
// Get elem values
k := elem.K
v := elem.V
// Release to pool
m.free(elem)
return k, v
}
// Format implements fmt.Formatter, allowing performant string formatting of map.
func (m *OrderedMap[K, V]) Format(state fmt.State, verb rune) {
m.format(reflect.TypeOf(m), state, verb)
}