package stdio import ( "fmt" "sort" "sync" "time" ) // WatchedStates is a copy of the states a module reads, each filled by its own watch and kept by its changes (novox/hq // ADR 0580 clause 1): a read of a whole state is one watch's current values, never a get per key, which took a // composition past 30 seconds and a compare past its call bound. A state is watched on its first read, or on Follow, // and the watch hands every current value over before the read is answered, so the copy is complete from the first // answer. A key whose value never changes once written (a manifest, by its digest) is read once with Once and kept. // A state helper, no rule: what a module reads is its own. type WatchedStates struct { watch func(state string, handle func(StateChange) error) error get func(state, key string) ([]byte, bool, error) mu sync.Mutex states map[string]*watchedState // once holds each value read with Once, by state and key, for the process's life: a few kilobytes per manifest // digest. Past onceAtMost keys it is emptied, which costs only a read again. once map[string][]byte } // onceAtMost bounds the values Once keeps. const onceAtMost = 4096 type watchedState struct { ready chan struct{} // closed once the watch's current values are all in the copy, or it was refused err error mu sync.Mutex values map[string][]byte then []func(StateChange) error started bool // the current values are in, so a change runs then at once pending []StateChange // the current values, run through then once all are in thenMu sync.Mutex // then runs one change at a time } // NewWatchedStates is the copy over this bundle's runtime: each state by KeptState.Watch, Once by KeptState.Get. func NewWatchedStates() *WatchedStates { return WatchedStatesOver( func(state string, handle func(StateChange) error) error { return State(state).Watch("", handle) }, func(state, key string) ([]byte, bool, error) { e, err := State(state).Get(key) if err != nil || e == nil { return nil, false, err } return e.Value, true, nil }) } // WatchedStatesOver is the copy over a watch and a get of one's own (a process's bus, a test's): watch hands handle // every current value and returns once they are handled, then hands every change; get reads one key. func WatchedStatesOver(watch func(state string, handle func(StateChange) error) error, get func(state, key string) ([]byte, bool, error)) *WatchedStates { return &WatchedStates{watch: watch, get: get, states: map[string]*watchedState{}, once: map[string][]byte{}} } // watched is the state's copy, its watch started now when it has none, the caller waiting until its current values // are in. then, when given, runs after the copy took each change: the current values once they are all in (a then // that reads the copy reads it whole), each later change as it comes. No lock is held while the watch starts, so a // then may read any state of the copy. func (w *WatchedStates) watched(state string, then func(StateChange) error) (*watchedState, error) { w.mu.Lock() s, ok := w.states[state] if ok { w.mu.Unlock() <-s.ready if s.err != nil { return nil, s.err } if then != nil { s.join(state, then) } return s, nil } s = &watchedState{ready: make(chan struct{}), values: map[string][]byte{}} if then != nil { s.then = []func(StateChange) error{then} } w.states[state] = s w.mu.Unlock() err := w.watch(state, func(c StateChange) error { s.mu.Lock() if c.Op == OpPut { s.values[c.Key] = append([]byte(nil), c.Value...) } else { delete(s.values, c.Key) } if !s.started { s.pending = append(s.pending, c) s.mu.Unlock() return nil } s.mu.Unlock() return s.run(state, c) }) if err != nil { s.err = fmt.Errorf("the state %s is not watched: %w", state, err) w.mu.Lock() delete(w.states, state) // asked again on the next read w.mu.Unlock() close(s.ready) return nil, s.err } s.mu.Lock() s.started = true pending := s.pending s.pending = nil s.mu.Unlock() close(s.ready) for _, c := range pending { if err := s.run(state, c); err != nil { sayf("%v", err) } } return s, nil } // join adds then to a state already watched (a read started its watch first) and runs it over the copy's current values // before any later change, so no change that landed before it joined is lost to it (review of ADR 0580 ยง2: a read // beat the follower to the watch at start-up). Under thenMu, so a later change waits for these; a change the copy took // just before the snapshot may also be handed again, which a then that wakes work takes as a second wake. func (s *watchedState) join(state string, then func(StateChange) error) { s.thenMu.Lock() defer s.thenMu.Unlock() s.mu.Lock() s.then = append(s.then, then) current := make([]StateChange, 0, len(s.values)) for k, v := range s.values { current = append(current, StateChange{State: state, Key: k, Op: OpPut, Value: v, Current: true}) } s.mu.Unlock() sort.Slice(current, func(i, j int) bool { return current[i].Key < current[j].Key }) for _, c := range current { if err := then(c); err != nil { sayf("%s %s: %v", state, c.Key, err) } } } // run hands one change, already in the copy, to every then. func (s *watchedState) run(state string, c StateChange) error { s.thenMu.Lock() defer s.thenMu.Unlock() s.mu.Lock() then := append([]func(StateChange) error(nil), s.then...) s.mu.Unlock() var errs []error for _, f := range then { if err := f(c); err != nil { errs = append(errs, err) } } if len(errs) > 0 { return fmt.Errorf("%s %s: %v", state, c.Key, errs) } return nil } // Follow watches the state into the copy and runs then on each change after the copy took it, so a reader then wakes // never reads a copy older than its change; it asks again until the watch is taken (2 s, doubled to a minute), as // KeptState.Follow does. On a state already watched then hears the changes from now on. func (w *WatchedStates) Follow(state string, then func(StateChange) error) { for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) { if _, err := w.watched(state, then); err == nil { return } else { sayf("%v; asking again in %s", err, wait) } time.Sleep(wait) } } // Get is a key's value in the copy, false when it has none. func (w *WatchedStates) Get(state, key string) ([]byte, bool, error) { s, err := w.watched(state, nil) if err != nil { return nil, false, err } s.mu.Lock() defer s.mu.Unlock() v, ok := s.values[key] return v, ok, nil } // Keys are the state's keys in the copy, sorted. func (w *WatchedStates) Keys(state string) ([]string, error) { s, err := w.watched(state, nil) if err != nil { return nil, err } s.mu.Lock() defer s.mu.Unlock() keys := make([]string, 0, len(s.values)) for k := range s.values { keys = append(keys, k) } sort.Strings(keys) return keys, nil } // Values is every key's value in the copy. func (w *WatchedStates) Values(state string) (map[string][]byte, error) { s, err := w.watched(state, nil) if err != nil { return nil, err } s.mu.Lock() defer s.mu.Unlock() out := make(map[string][]byte, len(s.values)) for k, v := range s.values { out[k] = v } return out, nil } // Once is a key whose value never changes once written, read by key the first time it is found and kept after: a // manifest by its digest. A key not found is not kept, so it is read again on the next ask. func (w *WatchedStates) Once(state, key string) ([]byte, bool, error) { w.mu.Lock() v, ok := w.once[state+" "+key] w.mu.Unlock() if ok { return v, true, nil } v, found, err := w.get(state, key) if err != nil || !found { return nil, false, err } w.mu.Lock() if len(w.once) >= onceAtMost { clear(w.once) } w.once[state+" "+key] = v w.mu.Unlock() return v, true, nil } // Direct is a key read by key now, not from the copy: a value that changes and is read one at a time (a build). func (w *WatchedStates) Direct(state, key string) ([]byte, bool, error) { return w.get(state, key) }