[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-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)
}