From ed5d1ea2d10430782854f32c6a9180f739ad9533 Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 15 Oct 2026 09:05:01 +0000 Subject: [PATCH] Test that max_concurrent_processing defaults to half the CPUs An omitted max_concurrent_processing, and New given none, must be half the CPUs Go uses, rounded down, and at least 1. The tests set GOMAXPROCS to 1, 2, 3 and 8 and fail until the default changes (https://git.eeqj.de/sneak/pixa/issues/242). Model: opus-5-5 --- .../concurrency_limits_internal_test.go | 30 +++++++++++++++++++ ...max_concurrent_processing_internal_test.go | 24 +++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/internal/config/concurrency_limits_internal_test.go b/internal/config/concurrency_limits_internal_test.go index 867b041..2d61e03 100644 --- a/internal/config/concurrency_limits_internal_test.go +++ b/internal/config/concurrency_limits_internal_test.go @@ -33,6 +33,36 @@ func TestOmittedConcurrencyLimitsUseDefaults(t *testing.T) { } } +// TestOmittedMaxConcurrentProcessingIsHalfTheCPUsAndAtLeastOne checks +// that an omitted max_concurrent_processing is half the CPUs Go uses, +// rounded down, and 1 when Go uses one CPU. +// +//nolint:paralleltest // it sets GOMAXPROCS, which other tests read +func TestOmittedMaxConcurrentProcessingIsHalfTheCPUsAndAtLeastOne(t *testing.T) { + cpusBefore := runtime.GOMAXPROCS(0) + + t.Cleanup(func() { runtime.GOMAXPROCS(cpusBefore) }) + + for _, tc := range []struct{ cpus, want int }{ + {1, 1}, {2, 1}, {3, 1}, {8, 4}, + } { + runtime.GOMAXPROCS(tc.cpus) + + c, err := configFromYAML(t, signingKeyLine) + if err != nil { + t.Fatalf("minimal config should be valid, got error: %v", err) + } + + t.Logf("GOMAXPROCS %d: MaxConcurrentProcessing %d", + tc.cpus, c.MaxConcurrentProcessing) + + if c.MaxConcurrentProcessing != tc.want { + t.Errorf("GOMAXPROCS %d: MaxConcurrentProcessing = %d, want %d", + tc.cpus, c.MaxConcurrentProcessing, tc.want) + } + } +} + // TestExplicitConcurrencyLimitsAreUsed checks that valid values for the // two limits are used as given. func TestExplicitConcurrencyLimitsAreUsed(t *testing.T) { diff --git a/internal/imageprocessor/max_concurrent_processing_internal_test.go b/internal/imageprocessor/max_concurrent_processing_internal_test.go index ba5388c..30f3fc5 100644 --- a/internal/imageprocessor/max_concurrent_processing_internal_test.go +++ b/internal/imageprocessor/max_concurrent_processing_internal_test.go @@ -132,6 +132,30 @@ func TestNewDefaultsMaxConcurrentProcessingToCPUs(t *testing.T) { } } +// TestNewDefaultIsHalfTheCPUsAndAtLeastOne checks that New, given no +// MaxConcurrentProcessing, makes one slot for every two CPUs Go uses, +// rounded down, and one slot when Go uses one CPU. +// +//nolint:paralleltest // it sets GOMAXPROCS, which other tests read +func TestNewDefaultIsHalfTheCPUsAndAtLeastOne(t *testing.T) { + cpusBefore := runtime.GOMAXPROCS(0) + + t.Cleanup(func() { runtime.GOMAXPROCS(cpusBefore) }) + + for _, tc := range []struct{ cpus, want int }{ + {1, 1}, {2, 1}, {3, 1}, {8, 4}, + } { + runtime.GOMAXPROCS(tc.cpus) + + got := cap(New(Params{}).processingSemaphore) + t.Logf("GOMAXPROCS %d: %d slots", tc.cpus, got) + + if got != tc.want { + t.Errorf("GOMAXPROCS %d: %d slots, want %d", tc.cpus, got, tc.want) + } + } +} + // TestProcessNeverExceedsMaxConcurrentProcessing starts more Process calls // than MaxConcurrentProcessing allows and holds the first ones inside // Process until the test lets them go. No more than the limit may be