Files
ziti/common/event_cache.go
T
2024-04-23 11:56:10 -04:00

241 lines
6.4 KiB
Go

package common
import (
"fmt"
"github.com/openziti/ziti/common/pb/edge_ctrl_pb"
"sync"
)
type OnStoreSuccess func(index uint64, event *edge_ctrl_pb.DataState_ChangeSet)
type EventCache interface {
// Store allows storage of an event and execution of an onSuccess callback while the event cache remains locked.
// onSuccess may be nil. This function is blocking.
Store(event *edge_ctrl_pb.DataState_ChangeSet, onSuccess OnStoreSuccess) error
// CurrentIndex returns the latest event index applied. This function is blocking.
CurrentIndex() (uint64, bool)
// ReplayFrom returns an array of events from startIndex and true if the replay may be facilitated.
// An empty slice and true is returned in cases where the requested startIndex is the current index.
// An empty slice and false is returned in cases where the replay cannot be facilitated.
// This function is blocking.
ReplayFrom(startIndex uint64) ([]*edge_ctrl_pb.DataState_ChangeSet, bool)
// WhileLocked allows the execution of arbitrary functionality while the event cache is locked. This function
// is blocking.
WhileLocked(func(uint64, bool))
// SetCurrentIndex sets the current index to the supplied value. All event log history may be lost.
SetCurrentIndex(uint64)
}
// ForgetfulEventCache does not store events or support replaying. It tracks
// the event index and that is it. It is a stand in for LoggingEventCache
// when replaying events is not expected (i.e. in routers)
type ForgetfulEventCache struct {
lock sync.Mutex
index *uint64
}
func NewForgetfulEventCache() *ForgetfulEventCache {
return &ForgetfulEventCache{}
}
func (cache *ForgetfulEventCache) SetCurrentIndex(index uint64) {
cache.lock.Lock()
defer cache.lock.Unlock()
cache.index = &index
}
func (cache *ForgetfulEventCache) WhileLocked(callback func(uint64, bool)) {
cache.lock.Lock()
defer cache.lock.Unlock()
callback(cache.currentIndex())
}
func (cache *ForgetfulEventCache) Store(event *edge_ctrl_pb.DataState_ChangeSet, onSuccess OnStoreSuccess) error {
cache.lock.Lock()
defer cache.lock.Unlock()
// Synthetic events are not backed by any kind of data store that provides and index. They are not stored and
// trigger the on success callback immediately.
if event.IsSynthetic {
onSuccess(event.Index, event)
return nil
}
if cache.index != nil {
if *cache.index >= event.Index {
return fmt.Errorf("out of order event detected, currentIndex: %d, recievedIndex: %d, type :%T", *cache.index, event.Index, cache)
}
}
cache.index = &event.Index
if onSuccess != nil {
onSuccess(*cache.index, event)
}
return nil
}
func (cache *ForgetfulEventCache) ReplayFrom(_ uint64) ([]*edge_ctrl_pb.DataState_ChangeSet, bool) {
return nil, false
}
func (cache *ForgetfulEventCache) CurrentIndex() (uint64, bool) {
cache.lock.Lock()
defer cache.lock.Unlock()
return cache.currentIndex()
}
func (cache *ForgetfulEventCache) currentIndex() (uint64, bool) {
if cache.index == nil {
return 0, false
}
return *cache.index, true
}
// LoggingEventCache stores events in order to support replaying (i.e. in controllers).
type LoggingEventCache struct {
lock sync.Mutex
HeadLogIndex uint64 `json:"-"`
LogSize uint64 `json:"-"`
Log []uint64 `json:"-"`
Events map[uint64]*edge_ctrl_pb.DataState_ChangeSet `json:"-"`
}
func NewLoggingEventCache(logSize uint64) *LoggingEventCache {
return &LoggingEventCache{
HeadLogIndex: 0,
LogSize: logSize,
Log: make([]uint64, logSize),
Events: map[uint64]*edge_ctrl_pb.DataState_ChangeSet{},
}
}
func (cache *LoggingEventCache) SetCurrentIndex(index uint64) {
cache.lock.Lock()
defer cache.lock.Unlock()
cache.HeadLogIndex = 0
cache.Log = make([]uint64, cache.LogSize)
cache.Log[0] = index
cache.Events = map[uint64]*edge_ctrl_pb.DataState_ChangeSet{}
}
func (cache *LoggingEventCache) WhileLocked(callback func(uint64, bool)) {
cache.lock.Lock()
defer cache.lock.Unlock()
callback(cache.currentIndex())
}
func (cache *LoggingEventCache) Store(event *edge_ctrl_pb.DataState_ChangeSet, onSuccess OnStoreSuccess) error {
cache.lock.Lock()
defer cache.lock.Unlock()
// Synthetic events are not backed by any kind of data store that provides and index. They are not stored and
// trigger the on success callback immediately.
if event.IsSynthetic {
onSuccess(event.Index, event)
return nil
}
currentIndex, ok := cache.currentIndex()
if ok && currentIndex >= event.Index {
return fmt.Errorf("out of order event detected, currentIndex: %d, receivedIndex: %d, type :%T", currentIndex, event.Index, cache)
}
targetLogIndex := uint64(0)
targetLogIndex = (cache.HeadLogIndex + 1) % cache.LogSize
// delete old value if we have looped
prevKey := cache.Log[targetLogIndex]
if prevKey != 0 {
delete(cache.Events, prevKey)
}
// add new values
cache.Log[targetLogIndex] = event.Index
cache.Events[event.Index] = event
//update head
cache.HeadLogIndex = targetLogIndex
onSuccess(event.Index, event)
return nil
}
func (cache *LoggingEventCache) CurrentIndex() (uint64, bool) {
cache.lock.Lock()
defer cache.lock.Unlock()
return cache.currentIndex()
}
func (cache *LoggingEventCache) currentIndex() (uint64, bool) {
if len(cache.Log) == 0 {
return 0, false
}
return cache.Log[cache.HeadLogIndex], true
}
func (cache *LoggingEventCache) ReplayFrom(startIndex uint64) ([]*edge_ctrl_pb.DataState_ChangeSet, bool) {
cache.lock.Lock()
defer cache.lock.Unlock()
_, eventFound := cache.Events[startIndex]
if !eventFound {
return nil, false
}
var startLogIndex *uint64
for logIndex, eventIndex := range cache.Log {
if eventIndex == startIndex {
tmp := uint64(logIndex)
startLogIndex = &tmp
break
}
}
if startLogIndex == nil {
return nil, false
}
// no replay
if *startLogIndex == cache.HeadLogIndex {
return nil, true
}
// ez replay
if *startLogIndex < cache.HeadLogIndex {
var result []*edge_ctrl_pb.DataState_ChangeSet
for _, key := range cache.Log[*startLogIndex:cache.HeadLogIndex] {
result = append(result, cache.Events[key])
}
return result, true
}
//looping replay
var result []*edge_ctrl_pb.DataState_ChangeSet
for _, key := range cache.Log[*startLogIndex:] {
result = append(result, cache.Events[key])
}
for _, key := range cache.Log[0:cache.HeadLogIndex] {
result = append(result, cache.Events[key])
}
return result, true
}