From d21f6adfa00a0ecaa00be3aaffd16aa34add25af Mon Sep 17 00:00:00 2001 From: rldyourmnd Date: Wed, 26 Aug 2026 02:59:13 +0500 Subject: [PATCH] fix(diagnostics): separate whole-run budget --- CHANGELOG.md | 4 ++++ cmd/gha-diagnostic-exporter/main.go | 5 +---- config/diagnostic-exporter.yaml | 3 ++- internal/diagnosticexport/config.go | 8 ++++++-- internal/diagnosticexport/config_test.go | 6 ++++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acbc4fc..758eeec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,10 @@ Versioning. ### Fixed +- Decoupled the diagnostic exporter's five-minute whole-run budget from its + twenty-second per-request timeout so natural multi-bundle bursts can finish + within the existing ten-minute systemd deadline instead of repeatedly timing + out during the final source rescan. - Bound every reviewed `curl` download path to two retries after the initial request, making the repository-wide download policy exactly three total attempts while leaving service-readiness polling and provider state retries diff --git a/cmd/gha-diagnostic-exporter/main.go b/cmd/gha-diagnostic-exporter/main.go index d001fa8..4e39200 100644 --- a/cmd/gha-diagnostic-exporter/main.go +++ b/cmd/gha-diagnostic-exporter/main.go @@ -54,10 +54,7 @@ func run(args []string, stdout, stderr io.Writer) error { logger := slog.New(slog.NewJSONHandler(stderr, &slog.HandlerOptions{Level: slog.LevelInfo})) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() - budget := time.Duration(config.RequestTimeoutSeconds*3) * time.Second - if budget > 55*time.Second { - budget = 55 * time.Second - } + budget := time.Duration(config.RunTimeoutSeconds) * time.Second ctx, cancel := context.WithTimeout(ctx, budget) defer cancel() exporter := diagnosticexport.Exporter{ diff --git a/config/diagnostic-exporter.yaml b/config/diagnostic-exporter.yaml index 4226cfb..8d96325 100644 --- a/config/diagnostic-exporter.yaml +++ b/config/diagnostic-exporter.yaml @@ -1,4 +1,4 @@ -schema_version: 4 +schema_version: 5 deployment_stage: canary source_directory: /run/gha-diagnostic-exporter-source source_owner: garm @@ -37,6 +37,7 @@ ca_file: /run/credentials/gha-diagnostic-exporter.service/rustfs-ca.pem access_key_file: /run/credentials/gha-diagnostic-exporter.service/rustfs-access-key secret_key_file: /run/credentials/gha-diagnostic-exporter.service/rustfs-secret-key request_timeout_seconds: 20 +run_timeout_seconds: 300 source_retention_hours: 168 max_bundle_bytes: 16777216 max_decompressed_bytes: 20971520 diff --git a/internal/diagnosticexport/config.go b/internal/diagnosticexport/config.go index b277cb0..ac57b5c 100644 --- a/internal/diagnosticexport/config.go +++ b/internal/diagnosticexport/config.go @@ -53,6 +53,7 @@ type Config struct { AccessKeyFile string `yaml:"access_key_file" json:"access_key_file"` SecretKeyFile string `yaml:"secret_key_file" json:"secret_key_file"` RequestTimeoutSeconds int `yaml:"request_timeout_seconds" json:"request_timeout_seconds"` + RunTimeoutSeconds int `yaml:"run_timeout_seconds" json:"run_timeout_seconds"` SourceRetentionHours int `yaml:"source_retention_hours" json:"source_retention_hours"` MaxBundleBytes int64 `yaml:"max_bundle_bytes" json:"max_bundle_bytes"` MaxDecompressedBytes int64 `yaml:"max_decompressed_bytes" json:"max_decompressed_bytes"` @@ -127,8 +128,8 @@ func ParseConfig(content []byte) (Config, error) { func (c Config) Validate() error { var problems []string add := func(field, message string) { problems = append(problems, field+": "+message) } - if c.SchemaVersion != 4 { - add("schema_version", "must be 4 for exact multi-tenant trust scopes") + if c.SchemaVersion != 5 { + add("schema_version", "must be 5 for an explicit whole-run budget") } if !StageAccepted(c.DeploymentStage) { add("deployment_stage", "must remain "+strings.Join(AcceptedStages(), " or ")+" until RustFS production gates pass") @@ -215,6 +216,9 @@ func (c Config) Validate() error { if c.RequestTimeoutSeconds < 5 || c.RequestTimeoutSeconds > 60 { add("request_timeout_seconds", "must be between 5 and 60") } + if c.RunTimeoutSeconds < 60 || c.RunTimeoutSeconds > 540 || c.RunTimeoutSeconds < c.RequestTimeoutSeconds*3 { + add("run_timeout_seconds", "must be between 60 and 540 and at least three request timeouts") + } if c.SourceRetentionHours != 168 { add("source_retention_hours", "must match the seven-day local spool") } diff --git a/internal/diagnosticexport/config_test.go b/internal/diagnosticexport/config_test.go index 2927e14..5d2c3fa 100644 --- a/internal/diagnosticexport/config_test.go +++ b/internal/diagnosticexport/config_test.go @@ -9,7 +9,7 @@ import ( func validConfig() Config { return Config{ - SchemaVersion: 4, + SchemaVersion: 5, DeploymentStage: "canary", SourceDirectory: "/run/gha-diagnostic-exporter-source", SourceOwner: "garm", @@ -29,6 +29,7 @@ func validConfig() Config { AccessKeyFile: "/run/credentials/gha-diagnostic-exporter.service/rustfs-access-key", SecretKeyFile: "/run/credentials/gha-diagnostic-exporter.service/rustfs-secret-key", RequestTimeoutSeconds: 20, + RunTimeoutSeconds: 300, SourceRetentionHours: 168, MaxBundleBytes: 16 * 1024 * 1024, MaxDecompressedBytes: 20 * 1024 * 1024, @@ -44,7 +45,8 @@ func TestConfigValidate(t *testing.T) { edit func(*Config) want string }{ - {"legacy schema", func(c *Config) { c.SchemaVersion = 3 }, "must be 4"}, + {"legacy schema", func(c *Config) { c.SchemaVersion = 4 }, "must be 5"}, + {"short run timeout", func(c *Config) { c.RunTimeoutSeconds = 59 }, "three request timeouts"}, {"unsorted trusts", func(c *Config) { c.Trusts = []string{"trusted", "release"} }, "strictly sorted"}, {"missing trusts", func(c *Config) { c.Trusts = nil }, "between 1 and 16"}, {"production stage", func(c *Config) { c.DeploymentStage = "production" }, "must remain canary"},