diff --git a/CHANGELOG.md b/CHANGELOG.md index be5f826c6..8d5b2b856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ request validation and AWS FIPS endpoint signing fixes. - Bundle verified mcli 20261109 archives in source container builds and use that release by default in the integration-test client installer. +- Prevent replication worker metrics from panicking when the replication + pool is absent, while retaining historical averages and peak counts. ## RELEASE.2026-10-26T00-00-00Z diff --git a/cmd/bucket-replication-metrics.go b/cmd/bucket-replication-metrics.go index aa4cfb963..a9b9e8bcd 100644 --- a/cmd/bucket-replication-metrics.go +++ b/cmd/bucket-replication-metrics.go @@ -119,7 +119,10 @@ func (a *ActiveWorkerStat) update() { if a == nil { return } - a.Curr = globalReplicationPool.Get().ActiveWorkers() + a.Curr = 0 + if pool := globalReplicationPool.Get(); pool != nil { + a.Curr = pool.ActiveWorkers() + } a.hist.Update(int64(a.Curr)) a.Avg = float32(a.hist.Mean()) a.Max = int(a.hist.Max()) diff --git a/cmd/bucket-replication-metrics_test.go b/cmd/bucket-replication-metrics_test.go new file mode 100644 index 000000000..1e1f64d2d --- /dev/null +++ b/cmd/bucket-replication-metrics_test.go @@ -0,0 +1,32 @@ +// Copyright (c) 2026 PGSTY +// SPDX-License-Identifier: AGPL-3.0-only + +package cmd + +import ( + "testing" + + "github.com/minio/minio/internal/once" + "github.com/rcrowley/go-metrics" +) + +func TestReplicationWorkerMetricsWithoutPool(t *testing.T) { + previous := globalReplicationPool + t.Cleanup(func() { globalReplicationPool = previous }) + + globalReplicationPool = once.NewSingleton[ReplicationPool]() + globalReplicationPool.Set(&ReplicationPool{activeWorkers: 8}) + stat := newActiveWorkerStat(metrics.NewRegistry()) + stat.update() + if got := stat.get(); got.Curr != 8 || got.Avg != 8 || got.Max != 8 { + t.Fatalf("active pool metrics = %+v, want current/average/maximum 8", got) + } + + // A collector may still run after replication has been disabled. + globalReplicationPool = once.NewSingleton[ReplicationPool]() + globalReplicationPool.Set(nil) + stat.update() + if got := stat.get(); got.Curr != 0 || got.Avg != 4 || got.Max != 8 { + t.Fatalf("disabled pool metrics = %+v, want current 0, average 4, maximum 8", got) + } +}