go-gun/gun/storage.go
2019-02-25 14:09:47 -06:00

103 lines
2.6 KiB
Go

package gun
import (
"context"
"errors"
"sync"
"time"
)
var ErrStorageNotFound = errors.New("Not found")
type Storage interface {
Get(ctx context.Context, parentSoul, field string) (Value, State, error)
Put(ctx context.Context, parentSoul, field string, val Value, state State, onlyIfExists bool) (ConflictResolution, error)
Close() error
}
type storageInMem struct {
values map[parentSoulAndField]*valueWithState
valueLock sync.RWMutex
purgeCancelFn context.CancelFunc
}
type parentSoulAndField struct{ parentSoul, field string }
type valueWithState struct {
val Value
state State
}
func NewStorageInMem(oldestAllowed time.Duration) Storage {
s := &storageInMem{values: map[parentSoulAndField]*valueWithState{}}
// Start the purger
if oldestAllowed > 0 {
var ctx context.Context
ctx, s.purgeCancelFn = context.WithCancel(context.Background())
go func() {
tick := time.NewTicker(5 * time.Second)
defer tick.Stop()
for {
select {
case <-ctx.Done():
return
case t := <-tick.C:
oldestStateAllowed := StateFromTime(t.Add(-oldestAllowed))
s.valueLock.Lock()
s.valueLock.Unlock()
for k, v := range s.values {
if v.state < oldestStateAllowed {
delete(s.values, k)
}
}
}
}
}()
}
return s
}
func (s *storageInMem) Get(ctx context.Context, parentSoul, field string) (Value, State, error) {
s.valueLock.RLock()
defer s.valueLock.RUnlock()
if vs := s.values[parentSoulAndField{parentSoul, field}]; vs == nil {
return nil, 0, ErrStorageNotFound
} else {
return vs.val, vs.state, nil
}
}
func (s *storageInMem) Put(
ctx context.Context, parentSoul, field string, val Value, state State, onlyIfExists bool,
) (confRes ConflictResolution, err error) {
s.valueLock.Lock()
defer s.valueLock.Unlock()
key, newVs := parentSoulAndField{parentSoul, field}, &valueWithState{val, state}
sysState := StateNow()
if existingVs := s.values[key]; existingVs == nil && onlyIfExists {
return 0, ErrStorageNotFound
} else if existingVs == nil {
confRes = ConflictResolutionNeverSeenUpdate
} else {
confRes = ConflictResolve(existingVs.val, existingVs.state, val, state, sysState)
}
if confRes == ConflictResolutionTooFutureDeferred {
// Schedule for 100ms past when it's deferred to
time.AfterFunc(time.Duration(state-sysState)*time.Millisecond+100, func() {
// TODO: should I check whether closed?
// TODO: what to do w/ error?
s.Put(ctx, parentSoul, field, val, state, onlyIfExists)
})
} else if confRes.IsImmediateUpdate() {
s.values[key] = newVs
}
return
}
func (s *storageInMem) Close() error {
if s.purgeCancelFn != nil {
s.purgeCancelFn()
}
return nil
}