mesh/merge-gate head bbb42d704ce0 merged into trunk 5b09d1ccb52d — pass: builds mesh-delivery-checker → novox; no bus step; every machine composes with the…
mesh/repo-check head bbb42d704ce0 merged into trunk 5b09d1ccb52d — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/check pass bbb42d704ce0 on 5b09d1ccb52d | unit 354 run 0 kept | int 392 run 0 kept | clone 8.7s unit 199ms int 7.1s+44s | composition not yet run
102 lines
3.3 KiB
Go
102 lines
3.3 KiB
Go
package check
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
)
|
|
|
|
// Unheard is the error of a call that reached nobody: the bus had no responder for it, or none answered within the call's
|
|
// bound. The step it asked for never began, so the same step may be asked of another node (issue 1820). The program wraps
|
|
// the errors of its calls in it where the bus gives a timeout; a no-responders error is known as it is.
|
|
type Unheard struct{ Err error }
|
|
|
|
func (u *Unheard) Error() string { return u.Err.Error() }
|
|
func (u *Unheard) Unwrap() error { return u.Err }
|
|
|
|
// isUnheard says whether err is a call that reached nobody. A step that began and failed, or ended failed, is not.
|
|
func isUnheard(err error) bool {
|
|
var u *Unheard
|
|
return errors.As(err, &u) || errors.Is(err, nats.ErrNoResponders)
|
|
}
|
|
|
|
// deafNode is a node that did not answer a build module's call during this judging.
|
|
type deafNode struct{ node, why string }
|
|
|
|
// markDeaf takes a node out of the choice for a build module for the rest of the judging.
|
|
func (st *state) markDeaf(bm, node string, err error) {
|
|
if st.deaf == nil {
|
|
st.deaf = map[string][]deafNode{}
|
|
}
|
|
st.deaf[bm] = append(st.deaf[bm], deafNode{node, err.Error()})
|
|
}
|
|
|
|
func (st *state) isDeaf(bm, node string) bool {
|
|
for _, d := range st.deaf[bm] {
|
|
if d.node == node {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// triedWords names every node that did not answer a build module, with what the bus said.
|
|
func (st *state) triedWords(bm string) string {
|
|
var parts []string
|
|
for _, d := range st.deaf[bm] {
|
|
parts = append(parts, fmt.Sprintf("%s (%s)", d.node, d.why))
|
|
}
|
|
return strings.Join(parts, "; ")
|
|
}
|
|
|
|
// moveStep is called when the target's node did not answer: the node is left out of the choice, and another build node of the
|
|
// same build module takes the target's place. It answers an error naming every node tried when none is left (the words hold
|
|
// NodesWords, so the verdict is judged again when the nodes change).
|
|
func (st *state) moveStep(t *target, err error) error {
|
|
old := t.node
|
|
st.markDeaf(t.module, old, err)
|
|
node, _, cerr := st.choose(t.module)
|
|
if cerr != nil {
|
|
return fmt.Errorf("%s; tried: %s", cerr, st.triedWords(t.module))
|
|
}
|
|
st.j.logf("%s#%d: %s did not answer (%v), the step goes to %s", t.pull.Repository, t.pull.Number, old, err, node)
|
|
t.node = node
|
|
other := false
|
|
for _, o := range st.targets {
|
|
if o != t && o.node == old {
|
|
other = true
|
|
}
|
|
}
|
|
if !other {
|
|
delete(st.nodes, old)
|
|
}
|
|
st.nodes[node] = true
|
|
return nil
|
|
}
|
|
|
|
// refetch makes the target's workspace again on its new node: the workspace of a step that was lost is on the node that did
|
|
// not answer.
|
|
func (st *state) refetch(ctx context.Context, t *target) error {
|
|
raw, err := st.step(ctx, t, "fetch", fmt.Sprintf("fetch %s#%d on %s", t.pull.Repository, t.pull.Number, t.node), t.fetchArgs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var f Fetched
|
|
if err := json.Unmarshal(raw, &f); err != nil || f.Workspace == "" {
|
|
return fmt.Errorf("the fetch of %s#%d on %s answered no workspace: %s", t.pull.Repository, t.pull.Number, t.node, raw)
|
|
}
|
|
t.fetched = f
|
|
return nil
|
|
}
|
|
|
|
// stepOnWorkspace sets the workspace a step reads to the target's current one.
|
|
func stepOnWorkspace(t *target, args map[string]any) {
|
|
if _, ok := args["workspace"]; ok {
|
|
args["workspace"] = t.fetched.Workspace
|
|
}
|
|
}
|