Issue 1585, S19: the strict parser, every manifest problem function and the judge's tables (192 declarations, about 5,200 lines) and their 244 tests leave; composition keeps its own refusals and the helpers it uses. The catalogue's tests decode the manifests they build. ParseManifest, ParseManifestWith, ParseManifestForInputs, CatalogueProblems and the rest are gone; mesh-delivery-indexer/judge holds them. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
97 lines
3.2 KiB
Go
97 lines
3.2 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// A long-running container may say it is busy (novox/hq ADR 0335 §1).
|
|
//
|
|
// **Beside its health, on the container**: a command run inside it, by its shell, as an exec health check
|
|
// is, that exits 0 while the container holds work a replacement would lose and non-zero when it is idle;
|
|
// and how long one ask may take. Being asked is also its notice that it is about to be replaced: from the
|
|
// first ask it takes no new work. The node-engine asks before it replaces the container, leaves it running
|
|
// while it says busy, and replaces it on the first pass it says idle, or at 15 minutes — the engine's bound,
|
|
// never the module's.
|
|
//
|
|
// Sent only to a node-engine that reads it (link.BusyContract): an older, strict engine refuses a field it
|
|
// does not know, whole, and to it the container is replaced at once, as before.
|
|
|
|
// BusyField is the resource field.
|
|
const BusyField = "busy"
|
|
|
|
// The bounds of one ask (ADR 0335), the node-engine's own, held here near the author.
|
|
const (
|
|
BusyTimeoutDefault = 10 * time.Second
|
|
BusyTimeoutCeiling = time.Minute
|
|
)
|
|
|
|
// Busy is one container's busy, read.
|
|
type Busy struct {
|
|
Command string
|
|
Timeout time.Duration
|
|
}
|
|
|
|
// ReadBusy reads a resource's `busy` field, its default applied. False when it carries none.
|
|
func ReadBusy(r map[string]any) (Busy, bool, []string) {
|
|
raw, present := r[BusyField]
|
|
if !present {
|
|
return Busy{}, false, nil
|
|
}
|
|
id := fmt.Sprint(r["id"])
|
|
fields, ok := raw.(map[string]any)
|
|
if !ok {
|
|
return Busy{}, true, []string{fmt.Sprintf("%s: busy is %T; it is an object with a command", id, raw)}
|
|
}
|
|
var problems []string
|
|
keys := make([]string, 0, len(fields))
|
|
for k := range fields {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
if k != "command" && k != "timeout" {
|
|
problems = append(problems, fmt.Sprintf("%s: its busy says %q; busy has a command and a timeout", id, k))
|
|
}
|
|
}
|
|
b := Busy{Timeout: BusyTimeoutDefault}
|
|
if v, has := fields["command"]; has {
|
|
s, ok := v.(string)
|
|
if !ok {
|
|
problems = append(problems, fmt.Sprintf("%s: its busy's command is %T; it is text", id, v))
|
|
}
|
|
b.Command = strings.TrimSpace(s)
|
|
}
|
|
if v, has := fields["timeout"]; has {
|
|
s, _ := v.(string)
|
|
d, err := time.ParseDuration(strings.TrimSpace(s))
|
|
switch {
|
|
case err != nil:
|
|
problems = append(problems, fmt.Sprintf("%s: its busy's timeout is %v; it is a duration such as \"10s\"", id, v))
|
|
case d <= 0 || d > BusyTimeoutCeiling:
|
|
problems = append(problems, fmt.Sprintf("%s: an ask of its busy takes more than nothing and at most %s, not %s",
|
|
id, BusyTimeoutCeiling, d))
|
|
default:
|
|
b.Timeout = d
|
|
}
|
|
}
|
|
return b, true, problems
|
|
}
|
|
|
|
// busyInto composes a container's `busy` into the node-engine's words, or takes it away: for an engine that
|
|
// reads it, the command and its timeout written out; for one that does not, the field is not sent, and the
|
|
// container is replaced at once, as before.
|
|
func busyInto(resource map[string]any, with Rendering) {
|
|
if _, has := resource[BusyField]; !has {
|
|
return
|
|
}
|
|
if !with.ReadsBusy {
|
|
delete(resource, BusyField)
|
|
return
|
|
}
|
|
b, _, _ := ReadBusy(resource)
|
|
resource[BusyField] = map[string]any{"command": b.Command, "timeout": b.Timeout.String()}
|
|
}
|