internal/chain holds typed Build, Rollout and Assignment and a Backend with no delete or purge, in memory and on the bus. The builds and rollouts buckets are asserted with history 64, no age and values up to just under a mebibyte; the assignments bucket gets the same, keeping its marker age because a stream cannot be made to disallow message ages. Migration 0104 makes the one-row chain_moved switch. The controller keeps no consolidated schema, so 0104 is the whole schema change. Nothing reads the buckets yet.
231 lines
6.0 KiB
Go
231 lines
6.0 KiB
Go
package chain
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Entry is one key's value at a revision. Caught is set on the entry a watch sends once, after the values the bucket
|
|
// held when it began, and carries no key.
|
|
type Entry struct {
|
|
Key string
|
|
Value []byte
|
|
Revision uint64
|
|
At time.Time
|
|
Caught bool
|
|
}
|
|
|
|
// Backend is one bucket of the chain. **It has no delete and no purge**: no key of the chain is ever removed (ADR
|
|
// 0408 §4), so nothing that holds a Backend can remove one.
|
|
type Backend interface {
|
|
// Get is one key's value and revision; false when it holds none.
|
|
Get(ctx context.Context, key string) (Entry, bool, error)
|
|
// Create writes a key that holds nothing and fails with ErrExists when it holds something.
|
|
Create(ctx context.Context, key string, value []byte) (uint64, error)
|
|
// Update writes a key at the revision it was read at and fails with ErrMoved when it moved.
|
|
Update(ctx context.Context, key string, value []byte, revision uint64) (uint64, error)
|
|
// Keys is every key. An error is an error: never an empty bucket.
|
|
Keys(ctx context.Context) ([]string, error)
|
|
// Watch sends every key's current value, then a Caught entry, then each later write, until the context ends.
|
|
Watch(ctx context.Context) (<-chan Entry, error)
|
|
// History is a key's earlier values and its current one, oldest first, at most the bucket keeps.
|
|
History(ctx context.Context, key string) ([]Entry, error)
|
|
}
|
|
|
|
var (
|
|
// ErrMoved is a compare-and-set that lost: somebody wrote the key since it was read.
|
|
ErrMoved = errors.New("the key was written by somebody else since it was read")
|
|
// ErrExists is a create of a key that holds a value.
|
|
ErrExists = errors.New("the key already holds a value")
|
|
// ErrNotHeld is a write refused because this controller does not hold the lease.
|
|
ErrNotHeld = errors.New("this controller does not hold the lease, so it writes nothing of the chain")
|
|
// ErrTooLarge is a value over the largest the bus holds: never cut (ADR 0408 §6), so refused.
|
|
ErrTooLarge = errors.New("the value is larger than the bus holds, and a value of the chain is never cut")
|
|
)
|
|
|
|
// Memory is a Backend held in this process, for tests and for nothing else. It keeps what the bucket keeps: every
|
|
// earlier value, up to History of them, and a revision that rises with every write of the bucket.
|
|
type Memory struct {
|
|
// Keeps is how many values of a key it keeps; zero keeps sixty-four.
|
|
Keeps int
|
|
|
|
mu sync.Mutex
|
|
values map[string][]Entry
|
|
revision uint64
|
|
watchers []chan Entry
|
|
// Now is the clock the entries are dated by; nil is time.Now.
|
|
Now func() time.Time
|
|
// writes counts the creates and updates taken.
|
|
writes int
|
|
// fail is what every call answers, when set.
|
|
fail error
|
|
}
|
|
|
|
// NewMemory is an empty bucket.
|
|
func NewMemory() *Memory { return &Memory{values: map[string][]Entry{}} }
|
|
|
|
// SetFail makes every call answer err from now on, or none when err is nil: a bus that is away.
|
|
func (m *Memory) SetFail(err error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.fail = err
|
|
}
|
|
|
|
// Writes is how many writes the bucket took.
|
|
func (m *Memory) Writes() int {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.writes
|
|
}
|
|
|
|
func (m *Memory) keep() int {
|
|
if m.Keeps > 0 {
|
|
return m.Keeps
|
|
}
|
|
return 64
|
|
}
|
|
|
|
func (m *Memory) now() time.Time {
|
|
if m.Now != nil {
|
|
return m.Now()
|
|
}
|
|
return time.Now()
|
|
}
|
|
|
|
func (m *Memory) Get(_ context.Context, key string) (Entry, bool, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.fail != nil {
|
|
return Entry{}, false, m.fail
|
|
}
|
|
h := m.values[key]
|
|
if len(h) == 0 {
|
|
return Entry{}, false, nil
|
|
}
|
|
return clone(h[len(h)-1]), true, nil
|
|
}
|
|
|
|
func (m *Memory) Create(_ context.Context, key string, value []byte) (uint64, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.fail != nil {
|
|
return 0, m.fail
|
|
}
|
|
if len(m.values[key]) > 0 {
|
|
return 0, ErrExists
|
|
}
|
|
return m.write(key, value), nil
|
|
}
|
|
|
|
func (m *Memory) Update(_ context.Context, key string, value []byte, revision uint64) (uint64, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.fail != nil {
|
|
return 0, m.fail
|
|
}
|
|
h := m.values[key]
|
|
if len(h) == 0 || h[len(h)-1].Revision != revision {
|
|
return 0, ErrMoved
|
|
}
|
|
return m.write(key, value), nil
|
|
}
|
|
|
|
// write is called with the lock held.
|
|
func (m *Memory) write(key string, value []byte) uint64 {
|
|
m.revision++
|
|
m.writes++
|
|
e := Entry{Key: key, Value: append([]byte(nil), value...), Revision: m.revision, At: m.now()}
|
|
h := append(m.values[key], e)
|
|
if len(h) > m.keep() {
|
|
h = h[len(h)-m.keep():]
|
|
}
|
|
m.values[key] = h
|
|
for _, w := range m.watchers {
|
|
select {
|
|
case w <- clone(e):
|
|
default:
|
|
}
|
|
}
|
|
return e.Revision
|
|
}
|
|
|
|
func (m *Memory) Keys(_ context.Context) ([]string, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.fail != nil {
|
|
return nil, m.fail
|
|
}
|
|
out := make([]string, 0, len(m.values))
|
|
for k := range m.values {
|
|
out = append(out, k)
|
|
}
|
|
sort.Strings(out)
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) Watch(ctx context.Context) (<-chan Entry, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.fail != nil {
|
|
return nil, m.fail
|
|
}
|
|
// Room for the current values, the caught mark, and a burst of later writes.
|
|
w := make(chan Entry, len(m.values)+1+1024)
|
|
keys := make([]string, 0, len(m.values))
|
|
for k := range m.values {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
h := m.values[k]
|
|
w <- clone(h[len(h)-1])
|
|
}
|
|
w <- Entry{Caught: true}
|
|
m.watchers = append(m.watchers, w)
|
|
out := make(chan Entry)
|
|
go func() {
|
|
defer close(out)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
m.mu.Lock()
|
|
for i, x := range m.watchers {
|
|
if x == w {
|
|
m.watchers = append(m.watchers[:i], m.watchers[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
m.mu.Unlock()
|
|
return
|
|
case e := <-w:
|
|
select {
|
|
case out <- e:
|
|
case <-ctx.Done():
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) History(_ context.Context, key string) ([]Entry, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.fail != nil {
|
|
return nil, m.fail
|
|
}
|
|
out := make([]Entry, 0, len(m.values[key]))
|
|
for _, e := range m.values[key] {
|
|
out = append(out, clone(e))
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func clone(e Entry) Entry {
|
|
e.Value = append([]byte(nil), e.Value...)
|
|
return e
|
|
}
|