Build seats yield to one shared slice per node; the composer half waits for the engines (ADR 0553, issue 1806)

Review round 1: the decoder type ships before any composer can emit it, so a
routine SDK bump never sends yields to an engine that refuses them; the
composer half moves to a later change. Every yielding process of a node goes
into one top-level slice, meshyield.slice, so the three build modules share one
quota instead of taking one each; the share is one node value, node-tools'
build-cpu-share, and each step's threads divide it by every build slot of the
node (words from the composer, a conservative fallback without them).
This commit is contained in:
2026-10-26 14:20:05 +01:00
parent 76deab6c67
commit 852ba81c5a
11 changed files with 205 additions and 318 deletions
+18 -10
View File
@@ -917,22 +917,30 @@ type Process struct {
// timing, judged by the node-engine beside liveness. Absent: judged alive or not, and nothing more.
Health *Health `json:"health,omitempty"`
// Yields says this process gives way to everything else on its machine (novox/hq issue 1806): the
// runtime of a build seat's holder, whose builds must never slow the operator's desktop. Absent: the
// machine's defaults. For a process that stays up.
// Yields says this process gives way to everything else on its machine (novox/hq ADR 0553, issue 1806): the
// runtime of a build seat's holder, whose builds must never slow the operator's desktop. Absent: the machine's
// defaults. For a process that stays up.
Yields *Yields `json:"yields,omitempty"`
}
// YieldIdle is the one priority a process yields at: the lowest CPU and IO priority there is.
// YieldIdle is the one priority a process yields at: the lowest there is.
const YieldIdle = "idle"
// Yields is how a process gives way, written into its unit by the node-engine (novox/hq issue 1806).
// YieldSlice is the one slice the node-engine places every yielding process of its node in (novox/hq ADR 0553 §2): a
// slice at the top of the hierarchy, beside the operator's user slice and the system's, so its idle weight and its
// quota bind against everything else on the machine. No dash in its name: a dash makes a slice a child of the slice
// named before it, where its weight would count only against its siblings.
const YieldSlice = "meshyield.slice"
// Yields is how a process gives way, written by the node-engine (novox/hq ADR 0553).
//
// **Priority** "idle": CPUWeight=idle (its cgroup runs only on cycles nothing else wants), IOWeight=1,
// Nice=19 for every process it starts, and the best-effort IO class at its lowest level (never the idle IO
// class, which a busy disk starves for good). **CPUShare** is the most of the machine's processors it may
// take, in percent: the node-engine writes CPUQuota for the processors the machine has, so the bound is
// the kernel's. 0 is no cap.
// **Priority** "idle": the process runs in YieldSlice, whose CPUWeight is idle (it runs only on cycles nothing else
// wants) and whose IOWeight is 1, with Nice=19 and the lowest best-effort IO level for every process it starts. The IO
// controls bind only where the disk's scheduler honours them (bfq, or io.cost for the weight); under `none`, the
// usual one for NVMe, they do nothing, and the CPU bound is what holds. **CPUShare** is the most of the machine's
// processors the slice may take, in percent, shared by every yielding process of the node together: the node-engine
// writes it as the slice's CPUQuota for the processors the machine has, so the bound is the kernel's. Every yielding
// process of one node carries the same share: the composer gives the node's one value (ADR 0553 §3). 0 is no cap.
type Yields struct {
Priority string `json:"priority,omitempty"`
CPUShare int `json:"cpu-share,omitempty"`