From 5f6fa437b2c2954092b6c3f8af5b83aef7dcb874 Mon Sep 17 00:00:00 2001 From: Benoit Tigeot Date: Mon, 27 Oct 2025 15:19:09 +0100 Subject: [PATCH] Prevent surprising failure with SDK when timeout is not set While testing SDK features for v4. I was surprised with the error: "reporter failed to start: event funnel closed: context deadline exceeded" This occurs when no timeout is set: ``` upgradeClient := action.NewUpgrade(actionConfig) upgradeClient.WaitStrategy = kube.StatusWatcherStrategy // When Timeout is zero, the status wait uses a context with zero timeout which // immediately expires causing "context deadline exceeded" errors. upgradeClient.Timeout = 2 * time.Minute ``` With this patch it will work without specifying. Signed-off-by: Benoit Tigeot --- pkg/kube/statuswait.go | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index cd9722eda..31d4f4444 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -47,6 +47,13 @@ type statusWaiter struct { ctx context.Context } +// DefaultStatusWatcherTimeout is the timeout used by the status waiter when a +// zero timeout is provided. This prevents callers from accidentally passing a +// zero value (which would immediately cancel the context) and getting +// "context deadline exceeded" errors. SDK callers can rely on this default +// when they don't set a timeout. +var DefaultStatusWatcherTimeout = 30 * time.Second + func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) { return &status.Result{ Status: status.CurrentStatus, @@ -55,7 +62,10 @@ func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) { } func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error { - ctx, cancel := w.contextWithTimeout(timeout) + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -76,7 +86,16 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D } func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) error { +<<<<<<< HEAD ctx, cancel := w.contextWithTimeout(timeout) +||||||| parent of 86f98f870 (Prevent surprising failure with SDK when timeout is not set) + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +======= + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +>>>>>>> 86f98f870 (Prevent surprising failure with SDK when timeout is not set) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -84,7 +103,16 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er } func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Duration) error { +<<<<<<< HEAD ctx, cancel := w.contextWithTimeout(timeout) +||||||| parent of 86f98f870 (Prevent surprising failure with SDK when timeout is not set) + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +======= + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +>>>>>>> 86f98f870 (Prevent surprising failure with SDK when timeout is not set) defer cancel() slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) @@ -95,7 +123,16 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura } func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Duration) error { +<<<<<<< HEAD ctx, cancel := w.contextWithTimeout(timeout) +||||||| parent of 86f98f870 (Prevent surprising failure with SDK when timeout is not set) + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +======= + if timeout == 0 { + timeout = DefaultStatusWatcherTimeout + } + ctx, cancel := context.WithTimeout(context.TODO(), timeout) +>>>>>>> 86f98f870 (Prevent surprising failure with SDK when timeout is not set) defer cancel() slog.Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper)