From 9e2af8a4159208673834cb5decd861abc88ef101 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Fri, 14 Aug 2026 16:32:15 +0200 Subject: [PATCH] Add a `Pusher.PushWithTimeout` method, resetting the context timeout When a job fails due to a higher-level context being canceled (for example, a global timeout) it is easy to reuse that context to push metrics, which fails immediately and metrics pertaining to the original failure will be lost. Use this method for an additional grace period beyond the original context's deadline. Signed-off-by: Joe Wreschnig --- prometheus/push/push.go | 14 ++++++++++++++ prometheus/push/push_test.go | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/prometheus/push/push.go b/prometheus/push/push.go index c65b6a665..9ea32a96d 100644 --- a/prometheus/push/push.go +++ b/prometheus/push/push.go @@ -44,6 +44,7 @@ import ( "net/http" "net/url" "strings" + "time" "github.com/prometheus/common/expfmt" "github.com/prometheus/common/model" @@ -133,6 +134,19 @@ func (p *Pusher) PushContext(ctx context.Context) error { return p.push(ctx, http.MethodPut) } +// PushWithTimeout is like PushContext but overrides the the provided +// context with a separate timeout. When a job fails due to a +// higher-level context being canceled (for example, a global timeout) +// it is easy to reuse that context to push metrics, which fails +// immediately and metrics pertaining to the original failure will be +// lost. Use this method for an additional grace period beyond the +// original context's deadline. +func (p *Pusher) PushWithTimeout(ctx context.Context, grace time.Duration) error { + ctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), grace) + defer cancel() + return p.PushContext(ctx) +} + // Add works like push, but only previously pushed metrics with the same name // (and the same job and other grouping labels) will be replaced. (It uses HTTP // method “POST” to push to the Pushgateway.) diff --git a/prometheus/push/push_test.go b/prometheus/push/push_test.go index 80147b69c..58fbc423d 100644 --- a/prometheus/push/push_test.go +++ b/prometheus/push/push_test.go @@ -15,11 +15,13 @@ package push import ( "bytes" + "context" "errors" "io" "net/http" "net/http/httptest" "testing" + "time" "github.com/prometheus/common/expfmt" @@ -306,4 +308,16 @@ func TestPush(t *testing.T) { if lastHeader == nil || lastHeader.Get("Authorization") == "" { t.Error("empty Authorization header") } + + // Make sure a canceled context fails, and a reset cancelation + // timeout works. + canceled, cancel := context.WithCancel(t.Context()) + cancel() + if err := New(pgwOK.URL, "test-timeout").PushContext(canceled); !errors.Is(err, context.Canceled) { + t.Errorf("expected canceled push to have failed with %v, not %v", context.Canceled, err) + } + + if err := New(pgwOK.URL, "test-timeout").PushWithTimeout(canceled, time.Hour); err != nil { + t.Errorf("expected uncanceled push to have succeeded, not %v", err) + } }