Keep builds on the bus under their module, so no lookup reads the bucket whole (issue 1121, ADR 0427)
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
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.
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -33,6 +34,9 @@ type Backend interface {
|
||||
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)
|
||||
}
|
||||
@@ -58,6 +62,7 @@ type Memory struct {
|
||||
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.
|
||||
@@ -155,7 +160,9 @@ func (m *Memory) write(key string, value []byte) uint64 {
|
||||
}
|
||||
m.values[key] = h
|
||||
for _, w := range m.watchers {
|
||||
w.push(clone(e))
|
||||
if w.match(key) {
|
||||
w.push(clone(e))
|
||||
}
|
||||
}
|
||||
return e.Revision
|
||||
}
|
||||
@@ -178,9 +185,10 @@ func (m *Memory) Keys(_ context.Context) ([]string, error) {
|
||||
|
||||
// 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 {
|
||||
mu sync.Mutex
|
||||
q []Entry
|
||||
wake chan struct{}
|
||||
match func(string) bool
|
||||
mu sync.Mutex
|
||||
q []Entry
|
||||
wake chan struct{}
|
||||
}
|
||||
|
||||
func (w *watcher) push(e Entry) {
|
||||
@@ -205,18 +213,54 @@ func (w *watcher) pop() (Entry, bool) {
|
||||
}
|
||||
|
||||
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)}
|
||||
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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user