70 lines
3.0 KiB
Go
70 lines
3.0 KiB
Go
package buildstep
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Passing is a fault of the network between a build node and what it reaches (a name lookup, a timeout, a reset
|
|
// connection, a forge's sshd refusing a burst, a store that does not answer), not of the source or the toolchain. A
|
|
// build module wraps it, and the shared runner then writes the step again and settles its ask with a delayed nak, up to
|
|
// AskRuns runs (ADR 0664 §1, §2). Any other error ends the step failed at once.
|
|
type Passing struct{ Err error }
|
|
|
|
func (p *Passing) Error() string { return p.Err.Error() }
|
|
func (p *Passing) Unwrap() error { return p.Err }
|
|
|
|
// IsPassing says err is, or wraps, a Passing.
|
|
func IsPassing(err error) bool {
|
|
var p *Passing
|
|
return errors.As(err, &p)
|
|
}
|
|
|
|
// HolderSilentWords opens the words of a HolderSilent, so PassingCause reads them from a step's reason too.
|
|
const HolderSilentWords = "the holder is silent"
|
|
|
|
// HolderSilent is a seat verb that answered "no responders" while the assigner names a holder of the seat: its tool
|
|
// runner is being replaced, a moment later it answers. It is a Passing fault (ADR 0664 §1); a seat nothing holds is not.
|
|
type HolderSilent struct{ Seat, Holder, Node string }
|
|
|
|
func (h *HolderSilent) Error() string {
|
|
return fmt.Sprintf("%s: seat %s, held by %s on %s, answered no responders", HolderSilentWords, h.Seat, h.Holder, h.Node)
|
|
}
|
|
|
|
// Unwrap makes a HolderSilent a Passing.
|
|
func (h *HolderSilent) Unwrap() error { return &Passing{Err: errors.New(h.Error())} }
|
|
|
|
// passingWords are the words of a network that did not answer, lower-cased: go's and git's (a lookup, a timeout, a refused or
|
|
// reset connection, ssh's key exchange cut by a forge's per-source penalty) and npm's error codes. "no responders" alone is not
|
|
// one: it is the same for a seat nothing holds (HolderSilent is the case a holder is named).
|
|
var passingWords = []string{"i/o timeout", "no such host", "temporary failure in name resolution", "connection refused",
|
|
"connection reset", "tls handshake timeout", "lookup ", "kex_exchange_identification", "connection closed by",
|
|
"could not resolve host", "client.timeout", "operation timed out", "connection timed out", "eai_again", "etimedout", "econnreset", "econnrefused", "enotfound", strings.ToLower(HolderSilentWords)}
|
|
|
|
// PassingCause says words (a tool's output or a step's reason) are those of a transport fault.
|
|
func PassingCause(words string) bool {
|
|
words = strings.ToLower(words)
|
|
for _, w := range passingWords {
|
|
if strings.Contains(words, w) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// DropKey is the key of an ask's drop in a build seat's pauses state: "dropped.<id>" (the part build-step).
|
|
func DropKey(askID string) string { return "dropped." + askID }
|
|
|
|
// DroppedPrefix opens every key DropKey makes.
|
|
const DroppedPrefix = "dropped."
|
|
|
|
// Drop is the value of the key dropped.<ask id> of a build seat's pauses state: an ask its caller no longer waits for.
|
|
type Drop struct {
|
|
V int `json:"v,omitempty"`
|
|
By string `json:"by,omitempty"`
|
|
Why string `json:"why"`
|
|
At time.Time `json:"at,omitzero"`
|
|
}
|