A read the bus did not answer is tried again after a bound, never kept as an answer: the shadow's answer, the node's composition and the own-secret step (issue 1126)

Met twice on 2026-10-28: shanks kept 'want.shanks.brother could not be read ... no
responders' as the own-secret step's last word, and ace kept 'the vault's own: not yet
... no responders' as its answer to seed-7, each until another input moved. Every bus
read of the composer (manifests, builds, archives, served values, the vault's own and
credentials, the have of what runs) is noted (BusFailed); an answer or a pass that met
one is made again after a growing bound (5 s to 5 min), and a pass is not handed when
the have of its sealed values did not answer. The own-secret step tells a read or an
ask that did not answer (Retry, taken again) from a key absent or the vault's word.
This commit is contained in:
2026-10-28 12:11:08 +01:00
parent 4182f8fbfb
commit cd1d2ff447
8 changed files with 306 additions and 66 deletions
+29 -2
View File
@@ -46,6 +46,10 @@ type Shadow struct {
// haves whose health words changed at every look).
inputs uint64
answered map[string]answeredAt
// owed says an answer met a read the bus did not answer: it is no answer to keep, and its group is answered again
// after a bound (laterAgain), whether or not an input moves (issue 1126, 2026-10-28: ace kept "the vault's own: not
// yet ... no responders" in its answer to seed-7).
owed bool
}
// answeredAt is what one group's answer was composed at: its keys' revisions, by node, and the count of input changes.
@@ -154,10 +158,15 @@ func (s *Shadow) Run(ctx context.Context) error {
// knows: a shadow that waits is never silent (issue 1126: ace, g14 and shanks answered nothing to seed #531).
waiting := time.NewTimer(notReadyAfter)
defer waiting.Stop()
var again <-chan time.Time
tries := 0
for {
select {
case <-ctx.Done():
return nil
case <-again:
again = nil
dirty = true
case <-waiting.C:
if !ready {
s.notReady(ctx, ins, caught)
@@ -205,6 +214,14 @@ func (s *Shadow) Run(ctx context.Context) error {
if dirty {
dirty = false
s.answerAll(ctx)
if s.owed {
wait := LaterWait(tries)
tries++
c.env.Say(fmt.Sprintf("this node answers again in %s the placement candidates whose answer met a bus that did not answer", wait))
again = time.After(wait)
} else {
tries, again = 0, nil
}
}
}
}
@@ -390,6 +407,7 @@ func (s *Shadow) answerAll(ctx context.Context) {
next[l.group] = a
}
}
s.owed = false
s.mu.Unlock()
sort.Slice(groups, func(i, j int) bool {
if groups[i].rev != groups[j].rev {
@@ -449,8 +467,10 @@ func (s *Shadow) answerAll(ctx context.Context) {
}
}
}()
missed := c.missed.Load()
a, ok := s.answerGroup(ctx, group)
close(stop)
provisional := c.missed.Load() > missed && ctx.Err() == nil
took := time.Since(began)
if took > slowAfter {
c.env.Say(fmt.Sprintf("the answer to %s took %s", group, took.Round(time.Second)))
@@ -468,7 +488,14 @@ func (s *Shadow) answerAll(ctx context.Context) {
c.env.Say(fmt.Sprintf("placement candidate %s does not place this node: nothing to answer", group))
}
s.mu.Lock()
s.answered[group] = l.at
if provisional {
// What the bus did not answer is no answer: handed as it stands, and composed again after a bound.
s.owed = true
delete(s.answered, group)
c.env.Say(fmt.Sprintf("the answer to %s met a read on the bus that did not answer: it is answered again", group))
} else {
s.answered[group] = l.at
}
s.mu.Unlock()
hand()
}
@@ -522,7 +549,7 @@ func (s *Shadow) answerGroup(ctx context.Context, group string) (delivery.Candid
wants[m] = w
}
s.mu.Unlock()
return returning(ctx, c.env.Get, node, wants, running, head, rev), true
return returning(ctx, c.get, node, wants, running, head, rev), true
}
placement := given[node]