From 3def293d947c8073654639d3092ce906d40a6c9e Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 26 Oct 2026 14:41:12 +0100 Subject: [PATCH] Build modules: no thread limit without the composer's share; with it, the share over their own capacity (ADR 0553 round 2) Change A rebuilds the three build modules, so their runtimes restart once; it changes nothing else about their builds until change B gives them the share. --- modules/mesh-build-go/cmd/mesh-build-go/cpu.go | 13 ++++++------- .../cmd/mesh-build-go/installed_toolchain_test.go | 14 ++++++-------- modules/mesh-build-go/go.mod | 2 +- modules/mesh-build-go/go.sum | 4 ++-- .../cmd/mesh-build-image/build_test.go | 3 ++- .../mesh-build-image/cmd/mesh-build-image/cpu.go | 13 ++++++------- modules/mesh-build-image/go.mod | 2 +- modules/mesh-build-image/go.sum | 4 ++-- modules/mesh-build-node/cmd/mesh-build-node/cpu.go | 13 ++++++------- .../cmd/mesh-build-node/fetch_test.go | 3 ++- modules/mesh-build-node/go.mod | 2 +- modules/mesh-build-node/go.sum | 4 ++-- 12 files changed, 37 insertions(+), 40 deletions(-) diff --git a/modules/mesh-build-go/cmd/mesh-build-go/cpu.go b/modules/mesh-build-go/cmd/mesh-build-go/cpu.go index dc7c2a55..24457cb4 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/cpu.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/cpu.go @@ -6,13 +6,12 @@ import ( ) // The processors a step may take (novox/hq ADR 0553, issue 1806). The node-engine holds this module's runtime, with -// every other build module's of the node, in one slice at idle priority under the node's one share of its processors; -// inside that bound a step starts no more threads than its part of it: the share divided by every build slot of the -// node, from the words the composer gives (buildstep.NodeLimits), so the steps running at once do not wait in the -// kernel's throttle. +// every other build module's of the node, in one slice at idle priority under the node's one share of its processors: +// that quota bounds the total. Threads only size each step: the share the composer gives (buildstep.NodeLimits) over +// this module's own capacity, so a step does not wait in the kernel's throttle. Without the composer's share there is +// no slice to size a step to, and no thread limit is set. -// cpuLimits is what one step is held to now. Without the composer's words, it assumes every build seat of the node -// beside it at this module's own capacity. +// cpuLimits is what one step is held to now. func (m *Module) cpuLimits() buildcpu.Limits { slots := 1 switch { @@ -24,7 +23,7 @@ func (m *Module) cpuLimits() buildcpu.Limits { return buildstep.NodeLimits(slots) } -// threadEnv is the environment every go command of a step is given for its threads: GOMAXPROCS sets its -p, the compiler's +// threadEnv is the environment (nothing without a share) every go command of a step is given for its threads: GOMAXPROCS sets its -p, the compiler's // concurrency, a test binary's processors and -parallel. func (m *Module) threadEnv() []string { return buildcpu.Env(m.cpuLimits().Threads) diff --git a/modules/mesh-build-go/cmd/mesh-build-go/installed_toolchain_test.go b/modules/mesh-build-go/cmd/mesh-build-go/installed_toolchain_test.go index a498c044..5684567e 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/installed_toolchain_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/installed_toolchain_test.go @@ -31,6 +31,7 @@ func toolchainValues(env []string) []string { // 1572/AC1 (ADR 0477 §1): every environment a step gives its go commands says GOTOOLCHAIN=local, once, and names no // release. func TestEveryStepEnvironmentSaysGotoolchainLocal(t *testing.T) { + t.Setenv("MESH_BUILD_CPU_SHARE", "50") m := &Module{Cache: t.TempDir()} vend := t.TempDir() os.MkdirAll(filepath.Join(vend, "vendor"), 0o755) @@ -49,18 +50,15 @@ func TestEveryStepEnvironmentSaysGotoolchainLocal(t *testing.T) { t.Errorf("%s: GOMAXPROCS said %d times, want once: %v", name, procs, env) } } - // Three slots of its own and no words from the composer: every build seat beside it at three, at the default - // share; with the composer's words, its part of the node's share (ADR 0553 §4). + // Without the composer's share no thread limit; with it, the share over its own three slots (ADR 0553 §4). t.Setenv("MESH_BUILD_CPU_SHARE", "") - t.Setenv("MESH_BUILD_SLOTS", "") m = &Module{Cache: t.TempDir(), Node: "shanks", Capacity: func() int { return 3 }} - if l := m.cpuLimits(); l.Percent != 50 || l.Slots != 9 || l.Threads != buildcpu.Threads(50, runtime.NumCPU(), 9) { - t.Errorf("without words: %+v", l) + if l := m.cpuLimits(); l.Threads != 0 || strings.Contains(strings.Join(m.goEnv(), " "), "GOMAXPROCS=") { + t.Errorf("without the share: %+v, goEnv %v", l, m.goEnv()) } t.Setenv("MESH_BUILD_CPU_SHARE", "50") - t.Setenv("MESH_BUILD_SLOTS", "4") - want := buildcpu.Threads(50, runtime.NumCPU(), 4) - if l := m.cpuLimits(); l.Slots != 4 || !strings.Contains(strings.Join(m.goEnv(), " "), "GOMAXPROCS="+strconv.Itoa(want)) { + want := buildcpu.Threads(50, runtime.NumCPU(), 3) + if l := m.cpuLimits(); l.Slots != 3 || !strings.Contains(strings.Join(m.goEnv(), " "), "GOMAXPROCS="+strconv.Itoa(want)) { t.Errorf("limits %+v, goEnv %v; want %d threads", l, m.goEnv(), want) } if st := m.cpuStatus(); st["limits"].(buildcpu.Limits) != m.cpuLimits() { diff --git a/modules/mesh-build-go/go.mod b/modules/mesh-build-go/go.mod index cf6a2166..ba9c1c0e 100644 --- a/modules/mesh-build-go/go.mod +++ b/modules/mesh-build-go/go.mod @@ -3,7 +3,7 @@ module meshbuildgo go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132226-36c92861d207 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026133939-527d87613eb2 github.com/distribution/distribution/v3 v3.1.2 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 diff --git a/modules/mesh-build-go/go.sum b/modules/mesh-build-go/go.sum index b6979a51..f87f866a 100644 --- a/modules/mesh-build-go/go.sum +++ b/modules/mesh-build-go/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132226-36c92861d207 h1:up88FwKjL9hfVbwMv9THSLOMzk7Rv1Cr02ZqjUzx4XY= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132226-36c92861d207/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026133939-527d87613eb2 h1:wyOoONFYpcgLr2IueawZHyRM8vogkltXNrjgajGo3hM= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026133939-527d87613eb2/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= diff --git a/modules/mesh-build-image/cmd/mesh-build-image/build_test.go b/modules/mesh-build-image/cmd/mesh-build-image/build_test.go index d146bfab..04fad85a 100644 --- a/modules/mesh-build-image/cmd/mesh-build-image/build_test.go +++ b/modules/mesh-build-image/cmd/mesh-build-image/build_test.go @@ -69,6 +69,7 @@ func setup(t *testing.T, edit func(string) string) (*Module, *fake, *keptStates) // Dockerfile of the module's own directory and that directory as the context, the target, the pinned base as a build // argument; no mount, no credential file, a runtime directory of its own, and it answers the image's digest. func TestBuildRunsBuildahRootlessAndAnswersTheDigest(t *testing.T) { + t.Setenv("MESH_BUILD_CPU_SHARE", "50") // the composer's share (ADR 0553): buildah is given its threads m, f, states := setup(t, same) res, st := runBuild(t, m, states, nil) if st.State != delivery.StepPassed { @@ -111,7 +112,7 @@ func TestBuildRunsBuildahRootlessAndAnswersTheDigest(t *testing.T) { t.Errorf("the environment of %q lacks the module's own runtime directory: %s", c.line, env) } if !strings.Contains(env, "GOMAXPROCS=") { - t.Errorf("the environment of %q gives buildah no thread limit (issue 1806): %s", c.line, env) + t.Errorf("the environment of %q gives buildah no thread limit under a share (ADR 0553): %s", c.line, env) } } // The image is removed from the store once the digest is read, so the store does not grow with every build. diff --git a/modules/mesh-build-image/cmd/mesh-build-image/cpu.go b/modules/mesh-build-image/cmd/mesh-build-image/cpu.go index 1e253ff6..126b319d 100644 --- a/modules/mesh-build-image/cmd/mesh-build-image/cpu.go +++ b/modules/mesh-build-image/cmd/mesh-build-image/cpu.go @@ -6,13 +6,12 @@ import ( ) // The processors a step may take (novox/hq ADR 0553, issue 1806). The node-engine holds this module's runtime, with -// every other build module's of the node, in one slice at idle priority under the node's one share of its processors; -// inside that bound a step starts no more threads than its part of it: the share divided by every build slot of the -// node, from the words the composer gives (buildstep.NodeLimits), so the steps running at once do not wait in the -// kernel's throttle. +// every other build module's of the node, in one slice at idle priority under the node's one share of its processors: +// that quota bounds the total. Threads only size each step: the share the composer gives (buildstep.NodeLimits) over +// this module's own capacity, so a step does not wait in the kernel's throttle. Without the composer's share there is +// no slice to size a step to, and no thread limit is set. -// cpuLimits is what one step is held to now. Without the composer's words, it assumes every build seat of the node -// beside it at this module's own capacity. +// cpuLimits is what one step is held to now. func (m *Module) cpuLimits() buildcpu.Limits { slots := 1 switch { @@ -24,7 +23,7 @@ func (m *Module) cpuLimits() buildcpu.Limits { return buildstep.NodeLimits(slots) } -// threadEnv is the environment buildah is given for its threads (it is a Go program). What a build's RUN lines start +// threadEnv is the environment (nothing without a share) buildah is given for its threads (it is a Go program). What a build's RUN lines start // runs in the container's own environment, inside the runtime's cgroup: the slice's quota and the nice level bind it. func (m *Module) threadEnv() []string { return buildcpu.Env(m.cpuLimits().Threads) diff --git a/modules/mesh-build-image/go.mod b/modules/mesh-build-image/go.mod index e3f3e5e2..1a678ce3 100644 --- a/modules/mesh-build-image/go.mod +++ b/modules/mesh-build-image/go.mod @@ -3,7 +3,7 @@ module meshbuildimage go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132226-36c92861d207 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026133939-527d87613eb2 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-build-image/go.sum b/modules/mesh-build-image/go.sum index b6074ee6..bc2822f4 100644 --- a/modules/mesh-build-image/go.sum +++ b/modules/mesh-build-image/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132226-36c92861d207 h1:up88FwKjL9hfVbwMv9THSLOMzk7Rv1Cr02ZqjUzx4XY= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132226-36c92861d207/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026133939-527d87613eb2 h1:wyOoONFYpcgLr2IueawZHyRM8vogkltXNrjgajGo3hM= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026133939-527d87613eb2/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-build-node/cmd/mesh-build-node/cpu.go b/modules/mesh-build-node/cmd/mesh-build-node/cpu.go index a0b18454..b40ceda4 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/cpu.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/cpu.go @@ -6,13 +6,12 @@ import ( ) // The processors a step may take (novox/hq ADR 0553, issue 1806). The node-engine holds this module's runtime, with -// every other build module's of the node, in one slice at idle priority under the node's one share of its processors; -// inside that bound a step starts no more threads than its part of it: the share divided by every build slot of the -// node, from the words the composer gives (buildstep.NodeLimits), so the steps running at once do not wait in the -// kernel's throttle. +// every other build module's of the node, in one slice at idle priority under the node's one share of its processors: +// that quota bounds the total. Threads only size each step: the share the composer gives (buildstep.NodeLimits) over +// this module's own capacity, so a step does not wait in the kernel's throttle. Without the composer's share there is +// no slice to size a step to, and no thread limit is set. -// cpuLimits is what one step is held to now. Without the composer's words, it assumes every build seat of the node -// beside it at this module's own capacity. +// cpuLimits is what one step is held to now. func (m *Module) cpuLimits() buildcpu.Limits { slots := 1 switch { @@ -24,7 +23,7 @@ func (m *Module) cpuLimits() buildcpu.Limits { return buildstep.NodeLimits(slots) } -// threadEnv is the environment every command of a step is given for its threads: GOMAXPROCS for esbuild and every Go +// threadEnv is the environment (nothing without a share) every command of a step is given for its threads: GOMAXPROCS for esbuild and every Go // program, UV_THREADPOOL_SIZE for Node's worker pool. func (m *Module) threadEnv() []string { return buildcpu.Env(m.cpuLimits().Threads) diff --git a/modules/mesh-build-node/cmd/mesh-build-node/fetch_test.go b/modules/mesh-build-node/cmd/mesh-build-node/fetch_test.go index a976fb92..01f2c376 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/fetch_test.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/fetch_test.go @@ -226,6 +226,7 @@ func mustReadDir(t *testing.T, dir string) []string { // are given PATH, HOME and the locale and their own words; and the real clean runner, run on `env` here, prints only // those. func TestNoCommandOfTheStepSeesATokenOfTheProcess(t *testing.T) { + t.Setenv("MESH_BUILD_CPU_SHARE", "50") t.Setenv("MESH_BUS_CREDENTIAL", "hunter2") t.Setenv("NPM_TOKEN", "hunter3") files := typeScriptModule(`{"dependencies":{"lodash":"^4"}}`) @@ -246,7 +247,7 @@ func TestNoCommandOfTheStepSeesATokenOfTheProcess(t *testing.T) { if len(c.env) < 3 || !strings.HasPrefix(c.env[0], "PATH=") || !strings.HasPrefix(c.env[1], "HOME=") || c.env[2] != "LC_ALL=C" { t.Errorf("%s was given %v, want PATH, HOME and the locale first", c.name, c.env) } - // And its threads, its part of the node's processor cap (issue 1806). + // And its threads, its part of the node's share the composer gave (ADR 0553). if !slices.ContainsFunc(c.env, func(e string) bool { return strings.HasPrefix(e, "GOMAXPROCS=") }) || !slices.ContainsFunc(c.env, func(e string) bool { return strings.HasPrefix(e, "UV_THREADPOOL_SIZE=") }) { t.Errorf("%s was given %v, without its threads", c.name, c.env) diff --git a/modules/mesh-build-node/go.mod b/modules/mesh-build-node/go.mod index 437bb31f..d9b65d03 100644 --- a/modules/mesh-build-node/go.mod +++ b/modules/mesh-build-node/go.mod @@ -3,7 +3,7 @@ module meshbuildnode go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132226-36c92861d207 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026133939-527d87613eb2 github.com/distribution/distribution/v3 v3.1.2 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-build-node/go.sum b/modules/mesh-build-node/go.sum index 3d3f2971..90f01ef5 100644 --- a/modules/mesh-build-node/go.sum +++ b/modules/mesh-build-node/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132226-36c92861d207 h1:up88FwKjL9hfVbwMv9THSLOMzk7Rv1Cr02ZqjUzx4XY= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132226-36c92861d207/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026133939-527d87613eb2 h1:wyOoONFYpcgLr2IueawZHyRM8vogkltXNrjgajGo3hM= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026133939-527d87613eb2/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=