mesh/delivery merged: source.main moved to novox/mesh-delivery@ce7d60cb6ed0
mesh/check pass 41f25d3129fd on 73be3df28cd4 | unit 21 run, 579 kept | integration 21 run, 655 kept | composition passed on shanks
On 2026-10-25 node_haves and assigner_wants timed out at 30 s four times, even narrowed to one node and one module, while assigner_want answered at once. Each listed every key of its bucket and then read every value one after the other, filtering only afterwards; every value is a round trip from the tool through the runtime to the bus, which opens the bucket and gets the key, so a few hundred keys outlasted the call. Now a node and a module read that one key directly, a node or a module alone filters the keys before any value is read, and the values are read sixteen at a time, at most 1000 a call, the answer saying when it was cut. indexer_modules, indexer_candidates and builder_builds read their values the same parallel way. The kind: a read tool that reads a whole bucket key by key; every list tool of the view is covered.
616 lines
20 KiB
Go
616 lines
20 KiB
Go
package view
|
|
|
|
// The read tools of the indexer's modules (novox/hq issue 1562): what the indexer stored under modules.<m>, answered as
|
|
// stored. The new-form fingerprint (FP2, None2, Inputs2), the reason it has none, and the path and toolchains it was computed
|
|
// with are only there. The view reads the bucket through its reads; the reads write nothing.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// The read tools, named as the module is: indexer_ and the verb.
|
|
const (
|
|
ModuleReadTool = "indexer_module"
|
|
ModulesReadTool = "indexer_modules"
|
|
)
|
|
|
|
// StateRead reads the indexer's modules state, as the view's reads grant it: its keys and a key's stored value (nil for a key with none).
|
|
type StateRead struct {
|
|
Keys func() ([]string, error)
|
|
Get func(key string) (json.RawMessage, error)
|
|
// GetEntry, when set, also gives the revision of the value; builder_builds orders by it.
|
|
GetEntry func(key string) (json.RawMessage, uint64, error)
|
|
}
|
|
|
|
// Reader answers the read tools from a modules state.
|
|
type Reader struct {
|
|
State StateRead // the indexer's modules
|
|
Builds StateRead // the builder's builds
|
|
|
|
CandidatesState StateRead // the indexer's candidates
|
|
WantsState StateRead // the assigner's wants and holds
|
|
DifferencesState StateRead // the assigner's differences
|
|
HavesState StateRead // the node-engine's haves
|
|
}
|
|
|
|
// Module is the stored value of modules.<module>, exactly as the indexer wrote it.
|
|
func (r Reader) Module(module string) (json.RawMessage, error) {
|
|
module = strings.TrimSpace(module)
|
|
key, err := delivery.ModulesKey(module)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if r.State.Get == nil {
|
|
return nil, fmt.Errorf("the modules state cannot be read here")
|
|
}
|
|
raw, err := r.State.Get(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(raw) == 0 {
|
|
return nil, fmt.Errorf("no module %s in the modules state", module)
|
|
}
|
|
return raw, nil
|
|
}
|
|
|
|
// Modules is every key of the modules state with its stored value; with none2 only the modules that have no new-form
|
|
// fingerprint and say why (a None2 reason), which is what the fingerprint's check reads live.
|
|
func (r Reader) Modules(none2 bool) (map[string]json.RawMessage, error) {
|
|
if r.State.Keys == nil || r.State.Get == nil {
|
|
return nil, fmt.Errorf("the modules state cannot be read here")
|
|
}
|
|
keys, err := r.State.Keys()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sort.Strings(keys)
|
|
values, err := r.State.values(keys)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := map[string]json.RawMessage{}
|
|
for i, k := range keys {
|
|
raw := values[i]
|
|
if len(raw) == 0 {
|
|
continue
|
|
}
|
|
if none2 {
|
|
var m struct {
|
|
None2 string `json:"none2"`
|
|
}
|
|
if json.Unmarshal(raw, &m) != nil || m.None2 == "" {
|
|
continue
|
|
}
|
|
}
|
|
out[k] = raw
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// The read tools of the indexer's candidates (issue 1681): what the indexer stored under candidates.<group>, as stored, so the
|
|
// new-form fingerprint of each touched module of an open pull request (pulls[].fps) can be read live.
|
|
const (
|
|
CandidateReadTool = "indexer_candidate"
|
|
CandidatesReadTool = "indexer_candidates"
|
|
)
|
|
|
|
// Candidate is the stored value of the candidates key of a group, exactly as the indexer wrote it.
|
|
func (r Reader) Candidate(group string) (json.RawMessage, error) {
|
|
key, err := delivery.CandidatesKey(strings.TrimSpace(group))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if r.CandidatesState.Get == nil {
|
|
return nil, fmt.Errorf("the candidates state cannot be read here")
|
|
}
|
|
raw, err := r.CandidatesState.Get(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(raw) == 0 {
|
|
return nil, fmt.Errorf("no candidate %s in the candidates state", group)
|
|
}
|
|
return raw, nil
|
|
}
|
|
|
|
// Candidates is every candidate, by group, each as stored; with a module only the candidates with a pull request touching it.
|
|
// A value that is not readable is listed under unreadable with its key, not skipped.
|
|
func (r Reader) Candidates(module string) (map[string]json.RawMessage, []string, error) {
|
|
if r.CandidatesState.Keys == nil || r.CandidatesState.Get == nil {
|
|
return nil, nil, fmt.Errorf("the candidates state cannot be read here")
|
|
}
|
|
keys, err := r.CandidatesState.Keys()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
sort.Strings(keys)
|
|
values, err := r.CandidatesState.values(keys)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
out := map[string]json.RawMessage{}
|
|
var unreadable []string
|
|
for i, k := range keys {
|
|
raw := values[i]
|
|
if len(raw) == 0 {
|
|
continue
|
|
}
|
|
group, perr := delivery.ParseCandidatesKey(k)
|
|
var c delivery.Candidate
|
|
if perr != nil || json.Unmarshal(raw, &c) != nil {
|
|
unreadable = append(unreadable, k)
|
|
continue
|
|
}
|
|
if module != "" && !slices.ContainsFunc(c.Pulls, func(p delivery.CandidatePull) bool { return slices.Contains(p.Modules, module) }) {
|
|
continue
|
|
}
|
|
out[group] = raw
|
|
}
|
|
return out, unreadable, nil
|
|
}
|
|
|
|
// The read tools of the builder's builds (novox/hq issue 1723): what the builder stored under `<module>.<fingerprint hex>`
|
|
// in mesh-delivery-builder_builds, answered as stored, so its states, nodes (in the step) and digests can be checked live.
|
|
const (
|
|
BuilderBuildReadTool = "builder_build"
|
|
BuilderBuildsReadTool = "builder_builds"
|
|
)
|
|
|
|
// BuilderRow is one key of the builder's builds: the key, the module and the fingerprint it parses to, the revision of its
|
|
// stored value (when the read gives one) and the stored value as the builder wrote it.
|
|
type BuilderRow struct {
|
|
Key string `json:"key"`
|
|
Module string `json:"module"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
Revision uint64 `json:"revision,omitempty"`
|
|
Build json.RawMessage `json:"build"`
|
|
}
|
|
|
|
// The bounds of builder_builds (issue 1827): the rows it returns when no limit is given, and the values it reads in one call.
|
|
// Reading a value is a bus round trip, so the answer must not read the whole bucket (it timed out at 30 s on the live one).
|
|
const (
|
|
DefaultBuildsLimit = 50
|
|
MaxBuildReads = 200
|
|
)
|
|
|
|
// BuildsAnswer is the answer of builder_builds: the rows, newest revision first, whether the answer was cut and why, and
|
|
// the keys that are no build key (Unreadable), which are listed rather than silently skipped (issue 1818).
|
|
type BuildsAnswer struct {
|
|
Builds []BuilderRow `json:"builds"`
|
|
Cut bool `json:"cut,omitempty"`
|
|
Note string `json:"note,omitempty"`
|
|
Unreadable []string `json:"unreadable,omitempty"`
|
|
}
|
|
|
|
func (r Reader) get(key string) (json.RawMessage, uint64, error) {
|
|
if r.Builds.GetEntry != nil {
|
|
return r.Builds.GetEntry(key)
|
|
}
|
|
raw, err := r.Builds.Get(key)
|
|
return raw, 0, err
|
|
}
|
|
|
|
func (r Reader) builderRow(key string) (BuilderRow, bool, error) {
|
|
module, fp, err := delivery.ParseBuilderBuildKey(key)
|
|
if err != nil {
|
|
return BuilderRow{}, false, nil // a key that is no build key is not a build
|
|
}
|
|
raw, rev, err := r.get(key)
|
|
if err != nil {
|
|
return BuilderRow{}, false, err
|
|
}
|
|
if len(raw) == 0 {
|
|
return BuilderRow{}, false, nil
|
|
}
|
|
return BuilderRow{Key: key, Module: module, Fingerprint: fp, Revision: rev, Build: raw}, true, nil
|
|
}
|
|
|
|
// BuilderBuilds lists the builder's keys, each as stored, newest revision first. With a module it considers only the keys
|
|
// that begin `<module>.`, before any value is read; with a state it keeps the builds in that state. It returns at most limit
|
|
// rows (DefaultBuildsLimit when limit is not above zero) and reads at most MaxBuildReads values, and says when it cut the
|
|
// answer. Keys that are no build key are listed as unreadable. Without a module the keys are walked in key order, so the
|
|
// answer is the first rows found, not the newest of the bucket: give a module to get its newest.
|
|
func (r Reader) BuilderBuilds(module, state string, limit int) (BuildsAnswer, error) {
|
|
if r.Builds.Keys == nil || r.Builds.Get == nil {
|
|
return BuildsAnswer{}, fmt.Errorf("the builder's builds cannot be read here")
|
|
}
|
|
if limit <= 0 {
|
|
limit = DefaultBuildsLimit
|
|
}
|
|
keys, err := r.Builds.Keys()
|
|
if err != nil {
|
|
return BuildsAnswer{}, err
|
|
}
|
|
sort.Strings(keys)
|
|
prefix := ""
|
|
if module = strings.TrimSpace(module); module != "" {
|
|
prefix = module + "."
|
|
}
|
|
out := BuildsAnswer{Builds: []BuilderRow{}}
|
|
var candidates []string
|
|
for _, k := range keys {
|
|
if prefix != "" && !strings.HasPrefix(k, prefix) {
|
|
continue
|
|
}
|
|
if _, _, err := delivery.ParseBuilderBuildKey(k); err != nil {
|
|
out.Unreadable = append(out.Unreadable, k)
|
|
continue
|
|
}
|
|
candidates = append(candidates, k)
|
|
}
|
|
// The values are read in batches, each batch in parallel and taken in key order: without a module or a state a batch
|
|
// is only the rows still missing, so no more is read than the answer needs; otherwise up to readWorkers*4.
|
|
reads := 0
|
|
walk:
|
|
for reads < len(candidates) {
|
|
if reads >= MaxBuildReads {
|
|
out.Cut, out.Note = true, fmt.Sprintf("read %d values and stopped; give a module (and a state) to narrow the answer", MaxBuildReads)
|
|
break
|
|
}
|
|
size := readWorkers * 4
|
|
if prefix == "" && state == "" {
|
|
size = limit - len(out.Builds)
|
|
}
|
|
size = min(size, MaxBuildReads-reads, len(candidates)-reads)
|
|
batch := candidates[reads : reads+size]
|
|
rows := make([]BuilderRow, len(batch))
|
|
found := make([]bool, len(batch))
|
|
if err := parallel(len(batch), func(i int) error {
|
|
var err error
|
|
rows[i], found[i], err = r.builderRow(batch[i])
|
|
return err
|
|
}); err != nil {
|
|
return BuildsAnswer{}, err
|
|
}
|
|
reads += len(batch)
|
|
for i, row := range rows {
|
|
if !found[i] {
|
|
continue
|
|
}
|
|
if state != "" {
|
|
var b struct {
|
|
State string `json:"state"`
|
|
}
|
|
if json.Unmarshal(row.Build, &b) != nil || b.State != state {
|
|
continue
|
|
}
|
|
}
|
|
out.Builds = append(out.Builds, row)
|
|
// Without a module the walk is in key order and stops at the limit; with one, all its (few) keys are read to order them.
|
|
if prefix == "" && len(out.Builds) >= limit {
|
|
if reads-len(batch)+i+1 < len(candidates) {
|
|
out.Cut, out.Note = true, fmt.Sprintf("stopped at the limit of %d builds in key order, not newest first; give a module for its newest", limit)
|
|
}
|
|
break walk
|
|
}
|
|
}
|
|
}
|
|
sort.SliceStable(out.Builds, func(i, j int) bool {
|
|
if out.Builds[i].Revision != out.Builds[j].Revision {
|
|
return out.Builds[i].Revision > out.Builds[j].Revision
|
|
}
|
|
return out.Builds[i].Key < out.Builds[j].Key
|
|
})
|
|
if len(out.Builds) > limit {
|
|
out.Builds = out.Builds[:limit]
|
|
out.Cut, out.Note = true, fmt.Sprintf("cut to the newest %d builds of %s", limit, module)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// BuilderBuild is the build of module at the new-form fingerprint fp2; with an empty fp2 it is the build at the fingerprint
|
|
// the indexer holds for the module now (its fp2), joined through modules.<module>; commit, when given, must be the commit
|
|
// the indexer holds that fp2 for, or the answer says which commit it holds (the fingerprint of another commit is not guessed).
|
|
func (r Reader) BuilderBuild(module, fp2, commit string) (BuilderRow, error) {
|
|
module = strings.TrimSpace(module)
|
|
if r.Builds.Get == nil {
|
|
return BuilderRow{}, fmt.Errorf("the builder's builds cannot be read here")
|
|
}
|
|
if fp2 != "" && commit != "" {
|
|
return BuilderRow{}, fmt.Errorf("give fp2 or commit, not both: fp2 %s names a build directly, commit %s asks for the build at the indexer's fingerprint of that commit", fp2, commit)
|
|
}
|
|
if fp2 == "" {
|
|
raw, err := r.Module(module)
|
|
if err != nil {
|
|
return BuilderRow{}, err
|
|
}
|
|
var m struct {
|
|
FP2 string `json:"fp2"`
|
|
None2 string `json:"none2"`
|
|
Commit string `json:"commit"`
|
|
}
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
return BuilderRow{}, fmt.Errorf("the indexer's value of %s is not readable: %w", module, err)
|
|
}
|
|
if commit != "" && m.Commit != commit {
|
|
// The value names the commit its fingerprint first appeared at, and a later merge that left the module alone
|
|
// writes none (the operator, 2026-10-25): another commit may well have the same fingerprint, which is not guessed.
|
|
return BuilderRow{}, fmt.Errorf("the indexer holds %s at commit %s, the commit its fingerprint first appeared at, not %s; "+
|
|
"a later trunk commit that left the module alone keeps that fingerprint: ask without commit for the build at it", module, m.Commit, commit)
|
|
}
|
|
if m.FP2 == "" {
|
|
return BuilderRow{}, fmt.Errorf("the indexer holds no new-form fingerprint of %s: %s", module, m.None2)
|
|
}
|
|
fp2 = m.FP2
|
|
}
|
|
key, err := delivery.BuilderBuildKey(module, fp2)
|
|
if err != nil {
|
|
return BuilderRow{}, err
|
|
}
|
|
row, ok, err := r.builderRow(key)
|
|
if err != nil {
|
|
return BuilderRow{}, err
|
|
}
|
|
if !ok {
|
|
return BuilderRow{}, fmt.Errorf("the builder holds no build %s", key)
|
|
}
|
|
return row, nil
|
|
}
|
|
|
|
// The read tools of the assigner's wants and differences and of the node-engine's haves (issue 1906, D1): the keys of those
|
|
// buckets answered as stored, so what the assigner wants, what it found different and what each node has can be read live.
|
|
// They write nothing.
|
|
const (
|
|
WantsReadTool = "assigner_wants"
|
|
WantReadTool = "assigner_want"
|
|
DifferencesReadTool = "assigner_differences"
|
|
HavesReadTool = "node_haves"
|
|
)
|
|
|
|
// WantsAnswer is the answer of assigner_wants: the wants by key (`want.<node>.<module>`, a node's own `want.<node>.node`
|
|
// among them) and the holds by key
|
|
// (`held.<module>.<hex>`), each as stored, and the keys that are neither.
|
|
type WantsAnswer struct {
|
|
Wants map[string]json.RawMessage `json:"wants"`
|
|
Held map[string]json.RawMessage `json:"held"`
|
|
Unreadable []string `json:"unreadable,omitempty"`
|
|
// Cut says the answer stopped at MaxStateReads values, and Note how to narrow it.
|
|
Cut bool `json:"cut,omitempty"`
|
|
Note string `json:"note,omitempty"`
|
|
}
|
|
|
|
// KeptAnswer is the answer of assigner_differences and node_haves: the keys of the bucket as stored, and the keys that
|
|
// are no `<node>.<module>` key.
|
|
type KeptAnswer struct {
|
|
Keys map[string]json.RawMessage `json:"keys"`
|
|
Unreadable []string `json:"unreadable,omitempty"`
|
|
Cut bool `json:"cut,omitempty"`
|
|
Note string `json:"note,omitempty"`
|
|
}
|
|
|
|
// readWorkers is how many values a read tool asks the bus for at once. Each value is a round trip from the tool through the
|
|
// runtime to the bus (the runtime opens the bucket and gets the key: two bus requests), so reading a bucket of a few
|
|
// hundred keys one after the other outlasted the 30 s a tool call is given on 2026-10-25 (node_haves, assigner_wants).
|
|
const readWorkers = 16
|
|
|
|
// MaxStateReads bounds the values one call of assigner_wants, assigner_differences or node_haves reads; past it the
|
|
// answer says it was cut and how to narrow it.
|
|
const MaxStateReads = 1000
|
|
|
|
// parallel runs read(i) for i in [0, n), at most readWorkers at a time, and answers the first error; once one failed no
|
|
// further read starts.
|
|
func parallel(n int, read func(i int) error) error {
|
|
var (
|
|
next atomic.Int64
|
|
failed atomic.Bool
|
|
once sync.Once
|
|
first error
|
|
wg sync.WaitGroup
|
|
)
|
|
workers := min(readWorkers, n)
|
|
for w := 0; w < workers; w++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for !failed.Load() {
|
|
i := int(next.Add(1) - 1)
|
|
if i >= n {
|
|
return
|
|
}
|
|
if err := read(i); err != nil {
|
|
once.Do(func() { first = err })
|
|
failed.Store(true)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
return first
|
|
}
|
|
|
|
// values reads the stored value of each key, in parallel, in the order of keys (nil for a key with none).
|
|
func (s StateRead) values(keys []string) ([]json.RawMessage, error) {
|
|
out := make([]json.RawMessage, len(keys))
|
|
err := parallel(len(keys), func(i int) error {
|
|
raw, err := s.Get(keys[i])
|
|
out[i] = raw
|
|
return err
|
|
})
|
|
return out, err
|
|
}
|
|
|
|
// read reads the values of keys (sorted first) into a map, at most MaxStateReads of them; cut says it stopped there.
|
|
func (s StateRead) read(keys []string) (map[string]json.RawMessage, bool, error) {
|
|
sort.Strings(keys)
|
|
cut := len(keys) > MaxStateReads
|
|
if cut {
|
|
keys = keys[:MaxStateReads]
|
|
}
|
|
values, err := s.values(keys)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
out := map[string]json.RawMessage{}
|
|
for i, k := range keys {
|
|
if len(values[i]) > 0 {
|
|
out[k] = values[i]
|
|
}
|
|
}
|
|
return out, cut, nil
|
|
}
|
|
|
|
func cutNote(cut bool, n int) string {
|
|
if !cut {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("read the first %d of %d matching keys in key order and stopped; give a node and a module to narrow the answer", MaxStateReads, n)
|
|
}
|
|
|
|
// Wants is every want and hold of the assigner, narrowed by node and module (a hold has no node and is kept when the module
|
|
// matches).
|
|
func (r Reader) Wants(node, module string) (WantsAnswer, error) {
|
|
s := r.WantsState
|
|
if s.Get == nil {
|
|
return WantsAnswer{}, fmt.Errorf("the assigner's wants cannot be read here")
|
|
}
|
|
out := WantsAnswer{Wants: map[string]json.RawMessage{}, Held: map[string]json.RawMessage{}}
|
|
if node != "" && module != "" {
|
|
// One key, read directly: no list of the bucket (a hold has no node, so none is shown).
|
|
key, err := delivery.WantKey(node, module)
|
|
if module == "node" {
|
|
key, err = delivery.WantNodeKey(node)
|
|
}
|
|
if err != nil {
|
|
return WantsAnswer{}, err
|
|
}
|
|
raw, err := s.Get(key)
|
|
if err != nil {
|
|
return WantsAnswer{}, err
|
|
}
|
|
if len(raw) > 0 {
|
|
out.Wants[key] = raw
|
|
}
|
|
return out, nil
|
|
}
|
|
if s.Keys == nil {
|
|
return WantsAnswer{}, fmt.Errorf("the assigner's wants cannot be read here")
|
|
}
|
|
keys, err := s.Keys()
|
|
if err != nil {
|
|
return WantsAnswer{}, err
|
|
}
|
|
// Narrowed by the key before any value is read.
|
|
var matching []string
|
|
for _, k := range keys {
|
|
if n, m, err := delivery.ParseWantKey(k); err == nil {
|
|
if (node == "" || n == node) && (module == "" || m == module) {
|
|
matching = append(matching, k)
|
|
}
|
|
continue
|
|
}
|
|
// A node's own want, want.<node>.node (ADR 0539 clause 6): listed with the wants, under the module name "node".
|
|
if n, err := delivery.ParseWantNodeKey(k); err == nil {
|
|
if (node == "" || n == node) && (module == "" || module == "node") {
|
|
matching = append(matching, k)
|
|
}
|
|
continue
|
|
}
|
|
if m, _, err := delivery.ParseHeldKey(k); err == nil {
|
|
if node == "" && (module == "" || m == module) {
|
|
matching = append(matching, k)
|
|
}
|
|
continue
|
|
}
|
|
out.Unreadable = append(out.Unreadable, k)
|
|
}
|
|
sort.Strings(out.Unreadable)
|
|
values, cut, err := s.read(matching)
|
|
if err != nil {
|
|
return WantsAnswer{}, err
|
|
}
|
|
out.Cut, out.Note = cut, cutNote(cut, len(matching))
|
|
for k, raw := range values {
|
|
if _, _, err := delivery.ParseHeldKey(k); err == nil {
|
|
out.Held[k] = raw
|
|
} else {
|
|
out.Wants[k] = raw
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Want is the stored want of module on node.
|
|
func (r Reader) Want(node, module string) (json.RawMessage, error) {
|
|
key, err := delivery.WantKey(strings.TrimSpace(node), strings.TrimSpace(module))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if r.WantsState.Get == nil {
|
|
return nil, fmt.Errorf("the assigner's wants cannot be read here")
|
|
}
|
|
raw, err := r.WantsState.Get(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(raw) == 0 {
|
|
return nil, fmt.Errorf("the assigner has no want %s", key)
|
|
}
|
|
return raw, nil
|
|
}
|
|
|
|
func (r Reader) kept(s StateRead, what, node, module string) (KeptAnswer, error) {
|
|
if s.Get == nil {
|
|
return KeptAnswer{}, fmt.Errorf("%s cannot be read here", what)
|
|
}
|
|
out := KeptAnswer{Keys: map[string]json.RawMessage{}}
|
|
if node != "" && module != "" {
|
|
// One key, read directly: no list of the bucket.
|
|
key, err := delivery.HaveKey(node, module)
|
|
if err != nil {
|
|
return KeptAnswer{}, err
|
|
}
|
|
raw, err := s.Get(key)
|
|
if err != nil {
|
|
return KeptAnswer{}, err
|
|
}
|
|
if len(raw) > 0 {
|
|
out.Keys[key] = raw
|
|
}
|
|
return out, nil
|
|
}
|
|
if s.Keys == nil {
|
|
return KeptAnswer{}, fmt.Errorf("%s cannot be read here", what)
|
|
}
|
|
keys, err := s.Keys()
|
|
if err != nil {
|
|
return KeptAnswer{}, err
|
|
}
|
|
// Narrowed by the key before any value is read.
|
|
var matching []string
|
|
for _, k := range keys {
|
|
n, m, isNode, err := delivery.ParseHaveKey(k)
|
|
if err != nil {
|
|
out.Unreadable = append(out.Unreadable, k)
|
|
continue
|
|
}
|
|
if (node == "" || n == node) && (module == "" || (!isNode && m == module)) {
|
|
matching = append(matching, k)
|
|
}
|
|
}
|
|
sort.Strings(out.Unreadable)
|
|
values, cut, err := s.read(matching)
|
|
if err != nil {
|
|
return KeptAnswer{}, err
|
|
}
|
|
out.Keys, out.Cut, out.Note = values, cut, cutNote(cut, len(matching))
|
|
return out, nil
|
|
}
|
|
|
|
// Differences is the assigner's differences as stored, narrowed by node and module.
|
|
func (r Reader) Differences(node, module string) (KeptAnswer, error) {
|
|
return r.kept(r.DifferencesState, "the assigner's differences", node, module)
|
|
}
|
|
|
|
// Haves is the node-engine's haves (and each node's own key) as stored, narrowed by node and module.
|
|
func (r Reader) Haves(node, module string) (KeptAnswer, error) {
|
|
return r.kept(r.HavesState, "the haves", node, module)
|
|
}
|