mesh/merge-gate head c79591531575 merged into trunk aa02e7bf4e78 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head c79591531575 merged into trunk aa02e7bf4e78 — pass: its merge-check.sh passed
mesh/delivery delivered
Milestone 5 moved 8,599 build records onto the bus under bare build ids, each carrying its module's whole manifest. Every build lookup then read the whole bucket, and a delivery plan's send did that per module per machine while it held the plans and the machine's declaration: the send of build-agent never finished, and every other act and the status waited behind it. Builds now live under <module>.build.<id> (requests under req.<id>, id.<id> names a build's module), with pointer keys newest, worked, held, commit.<sha> and source.<reuse key> that move only to a newer build and are read as hints, repaired when wrong. Manifests are kept once in the same bucket under manifest.sha256-<hex>, so no grant the running bus lacks is needed. The old keys are laid out once at start, idempotently, and left unread. The calls a plan look makes on the silent clocks while it holds the plans are bounded, since the vendored kv.Keys ignores its context.
320 lines
8.1 KiB
Go
320 lines
8.1 KiB
Go
package chain
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sort"
|
|
"strings"
|
|
"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
|
|
// Deleted marks the end of a key (Remove); it holds no value.
|
|
Deleted bool
|
|
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)
|
|
// WatchFiltered is Watch of the keys a subject pattern matches (`*` one token, `>` the rest), the filter kept by the
|
|
// server: a read of one module's builds reads that module's keys and no other (novox/hq issue 1121).
|
|
WatchFiltered(ctx context.Context, filter string) (<-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 []*watcher
|
|
filters map[string]int
|
|
// 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()
|
|
}
|
|
|
|
// last is a key's newest entry, false when it holds none.
|
|
func (m *Memory) last(key string) (Entry, bool) {
|
|
h := m.values[key]
|
|
if len(h) == 0 {
|
|
return Entry{}, false
|
|
}
|
|
return h[len(h)-1], true
|
|
}
|
|
|
|
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
|
|
}
|
|
e, ok := m.last(key)
|
|
if !ok {
|
|
return Entry{}, false, nil
|
|
}
|
|
return clone(e), 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 _, held := m.last(key); held {
|
|
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
|
|
}
|
|
e, held := m.last(key)
|
|
if !held || e.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 {
|
|
if w.match(key) {
|
|
w.push(clone(e))
|
|
}
|
|
}
|
|
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 {
|
|
if _, held := m.last(k); held {
|
|
out = append(out, k)
|
|
}
|
|
}
|
|
sort.Strings(out)
|
|
return out, nil
|
|
}
|
|
|
|
// watcher is one watch of a Memory: an unbounded queue, so a watch that is slow is behind and never misses a write.
|
|
type watcher struct {
|
|
match func(string) bool
|
|
mu sync.Mutex
|
|
q []Entry
|
|
wake chan struct{}
|
|
}
|
|
|
|
func (w *watcher) push(e Entry) {
|
|
w.mu.Lock()
|
|
w.q = append(w.q, e)
|
|
w.mu.Unlock()
|
|
select {
|
|
case w.wake <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (w *watcher) pop() (Entry, bool) {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
if len(w.q) == 0 {
|
|
return Entry{}, false
|
|
}
|
|
e := w.q[0]
|
|
w.q = w.q[1:]
|
|
return e, true
|
|
}
|
|
|
|
func (m *Memory) Watch(ctx context.Context) (<-chan Entry, error) {
|
|
return m.WatchFiltered(ctx, ">")
|
|
}
|
|
|
|
// SubjectMatches is whether a key matches a subject pattern: `*` stands for one token, `>` for one or more at the end.
|
|
func SubjectMatches(pattern, key string) bool {
|
|
p, k := strings.Split(pattern, "."), strings.Split(key, ".")
|
|
for n, t := range p {
|
|
if t == ">" {
|
|
return len(k) > n
|
|
}
|
|
if n >= len(k) || (t != "*" && t != k[n]) {
|
|
return false
|
|
}
|
|
}
|
|
return len(p) == len(k)
|
|
}
|
|
|
|
// Filtered counts the filtered watches a Memory took, by filter, for a test that says what a read touched.
|
|
func (m *Memory) Filtered() map[string]int {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
out := map[string]int{}
|
|
for k, v := range m.filters {
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (m *Memory) WatchFiltered(ctx context.Context, filter string) (<-chan Entry, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.fail != nil {
|
|
return nil, m.fail
|
|
}
|
|
w := &watcher{wake: make(chan struct{}, 1), match: func(k string) bool { return SubjectMatches(filter, k) }}
|
|
if m.filters == nil {
|
|
m.filters = map[string]int{}
|
|
}
|
|
m.filters[filter]++
|
|
keys := make([]string, 0, len(m.values))
|
|
for k := range m.values {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
if !w.match(k) {
|
|
continue
|
|
}
|
|
if e, held := m.last(k); held {
|
|
w.q = append(w.q, clone(e))
|
|
}
|
|
}
|
|
w.q = append(w.q, Entry{Caught: true})
|
|
m.watchers = append(m.watchers, w)
|
|
out := make(chan Entry)
|
|
go func() {
|
|
defer close(out)
|
|
defer func() {
|
|
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()
|
|
}()
|
|
for {
|
|
e, ok := w.pop()
|
|
if !ok {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-w.wake:
|
|
}
|
|
continue
|
|
}
|
|
select {
|
|
case out <- e:
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
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
|
|
}
|