From dba641035b3251bf5a8ed9cbd3a99a5dd9ea9103 Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Sun, 7 Jun 2026 09:27:23 -0700 Subject: [PATCH] Guard macOS Namespace fallback retries --- ...nfrastructure-and-store-upload-pipeline.md | 5 ++++ .../forgejo-nsc/internal/nsc/macos_nsc.go | 28 ++++++++++++------- .../internal/nsc/macos_nsc_test.go | 26 +++++++++++++++++ 3 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 services/forgejo-nsc/internal/nsc/macos_nsc_test.go diff --git a/evolution/proposals/BEP-0009-release-infrastructure-and-store-upload-pipeline.md b/evolution/proposals/BEP-0009-release-infrastructure-and-store-upload-pipeline.md index bd96f2e..e76b570 100644 --- a/evolution/proposals/BEP-0009-release-infrastructure-and-store-upload-pipeline.md +++ b/evolution/proposals/BEP-0009-release-infrastructure-and-store-upload-pipeline.md @@ -112,6 +112,11 @@ The same change also makes the forge itself the release authority: release tags - Configure macOS Namespace targets with the Compute API CPU-by-memory shape format. `12x28` is the Burrow large macOS baseline; platform-prefixed values such as `macos/arm64:8x16` are not accepted by the macOS launcher. +- Treat macOS shape fallback as part of the release lane rather than as a + best-effort debug path. When large shapes are quota-constrained, the + dispatcher may try smaller accepted shapes, but every fallback must have a + bounded create timeout and additional shapes must reuse the final timeout + policy instead of crashing the dispatcher. - Keep the shared Linux runner work directory out of macOS bootstraps. The dispatcher normalizes macOS runners to `/tmp/forgejo-runner` when the shared config points at Linux state paths such as `/var/lib/forgejo-runner`. diff --git a/services/forgejo-nsc/internal/nsc/macos_nsc.go b/services/forgejo-nsc/internal/nsc/macos_nsc.go index 0f83b3c..b9b9a1b 100644 --- a/services/forgejo-nsc/internal/nsc/macos_nsc.go +++ b/services/forgejo-nsc/internal/nsc/macos_nsc.go @@ -52,6 +52,21 @@ func normalizeMacOSNSCMachineType(machineType string) (normalized string, change return normalized, changed, nil } +type macosNSCAttemptConfig struct { + waitTimeout time.Duration + createTimeout time.Duration +} + +func macosNSCAttemptConfigFor(index int, attempts []macosNSCAttemptConfig) macosNSCAttemptConfig { + if len(attempts) == 0 { + return macosNSCAttemptConfig{} + } + if index < len(attempts) { + return attempts[index] + } + return attempts[len(attempts)-1] +} + func (d *Dispatcher) launchMacOSRunnerViaNSC(ctx context.Context, runnerName string, req LaunchRequest, ttl time.Duration, machineType string) error { if machineType == "" { return errors.New("machine_type is required for macos runners") @@ -92,17 +107,13 @@ func (d *Dispatcher) launchMacOSRunnerViaNSC(ctx context.Context, runnerName str } candidates = uniq - type attemptCfg struct { - waitTimeout time.Duration - createTimeout time.Duration - } - attempts := []attemptCfg{ + attempts := []macosNSCAttemptConfig{ {waitTimeout: 6 * time.Minute, createTimeout: 8 * time.Minute}, {waitTimeout: 4 * time.Minute, createTimeout: 6 * time.Minute}, {waitTimeout: 3 * time.Minute, createTimeout: 5 * time.Minute}, } - createInstance := func(mt string, a attemptCfg) (instanceID string, out string, err error) { + createInstance := func(mt string, a macosNSCAttemptConfig) (instanceID string, out string, err error) { tmpDir, err := os.MkdirTemp("", "forgejo-nsc-macos-*") if err != nil { return "", "", fmt.Errorf("mktemp: %w", err) @@ -173,10 +184,7 @@ func (d *Dispatcher) launchMacOSRunnerViaNSC(ctx context.Context, runnerName str lastErr error ) for i, mt := range candidates { - a := attempts[i] - if i >= len(attempts) { - a = attempts[len(attempts)-1] - } + a := macosNSCAttemptConfigFor(i, attempts) d.log.Info("launching Namespace macos runner via nsc", "runner", runnerName, diff --git a/services/forgejo-nsc/internal/nsc/macos_nsc_test.go b/services/forgejo-nsc/internal/nsc/macos_nsc_test.go new file mode 100644 index 0000000..025b52c --- /dev/null +++ b/services/forgejo-nsc/internal/nsc/macos_nsc_test.go @@ -0,0 +1,26 @@ +package nsc + +import ( + "testing" + "time" +) + +func TestMacOSNSCAttemptConfigForReusesLastFallback(t *testing.T) { + attempts := []macosNSCAttemptConfig{ + {waitTimeout: time.Minute, createTimeout: 2 * time.Minute}, + {waitTimeout: 30 * time.Second, createTimeout: time.Minute}, + } + + got := macosNSCAttemptConfigFor(3, attempts) + want := attempts[len(attempts)-1] + if got != want { + t.Fatalf("macosNSCAttemptConfigFor(3) = %+v, want %+v", got, want) + } +} + +func TestMacOSNSCAttemptConfigForEmpty(t *testing.T) { + got := macosNSCAttemptConfigFor(0, nil) + if got != (macosNSCAttemptConfig{}) { + t.Fatalf("macosNSCAttemptConfigFor(0, nil) = %+v, want zero value", got) + } +}