Skip to content

Commit 4e6290c

Browse files
committed
add possibility to inject context for cancellation into GroupVersionKindForObject and IsObjectNamespaced
1 parent f33a982 commit 4e6290c

File tree

2 files changed

+49
-2
lines changed

2 files changed

+49
-2
lines changed

pkg/retry/retry.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type Client struct {
1717
backoffMultiplier float64
1818
maxAttempts int
1919
timeout time.Duration
20+
context context.Context
2021
}
2122

2223
// NewRetryingClient returns a retry.Client that implements client.Client, but retries each operation that can fail with the specified parameters.
@@ -37,6 +38,7 @@ func NewRetryingClient(c client.Client) *Client {
3738
backoffMultiplier: 1.0, // default backoff multiplier
3839
maxAttempts: 0, // default max retries
3940
timeout: 1 * time.Second, // default timeout for retries
41+
context: context.Background(), // default context
4042
}
4143
}
4244

@@ -118,6 +120,25 @@ func (rc *Client) WithTimeout(timeout time.Duration) *Client {
118120
return rc
119121
}
120122

123+
// WithContext sets the context for the next call of either GroupVersionKindFor or IsObjectNamespaced.
124+
// Since the signature of these methods does not allow passing a context, and the retrying can not be cancelled without one,
125+
// this method is required to inject the context to be used for the aforementioned methods.
126+
// Note that any function of this Client that actually retries an operation will reset this context, but only for GroupVersionKindFor and IsObjectNamespaced it will actually be used.
127+
// The intended use of this method is something like this:
128+
//
129+
// c.WithContext(ctx).GroupVersionKindFor(obj)
130+
// c.WithContext(ctx).IsObjectNamespaced(obj)
131+
//
132+
// If no context is injected via this method, both GroupVersionKindFor and IsObjectNamespaced will use the default context.Background().
133+
// It returns the Client for chaining.
134+
func (rc *Client) WithContext(ctx context.Context) *Client {
135+
if ctx == nil {
136+
ctx = context.Background() // default to background context if nil
137+
}
138+
rc.context = ctx
139+
return rc
140+
}
141+
121142
///////////////////////////
122143
// CLIENT IMPLEMENTATION //
123144
///////////////////////////
@@ -172,6 +193,7 @@ func (op *operation) try(ctx context.Context) (bool, time.Duration) {
172193

173194
// retry executes the given method with the provided arguments, retrying on failure.
174195
func (rc *Client) retry(ctx context.Context, cfn callbackFn) {
196+
rc.WithContext(context.Background()) // reset context
175197
op := rc.newOperation(cfn)
176198
if rc.Timeout() > 0 {
177199
var cancel context.CancelFunc
@@ -276,7 +298,7 @@ func (rc *Client) Update(ctx context.Context, obj client.Object, opts ...client.
276298

277299
// GroupVersionKindFor wraps the client's GroupVersionKindFor method and retries it on failure.
278300
func (rc *Client) GroupVersionKindFor(obj runtime.Object) (gvk schema.GroupVersionKind, err error) {
279-
rc.retry(context.Background(), func(ctx context.Context) error {
301+
rc.retry(rc.context, func(ctx context.Context) error {
280302
gvk, err = rc.internal.GroupVersionKindFor(obj)
281303
return err
282304
})
@@ -285,7 +307,7 @@ func (rc *Client) GroupVersionKindFor(obj runtime.Object) (gvk schema.GroupVersi
285307

286308
// IsObjectNamespaced wraps the client's IsObjectNamespaced method and retries it on failure.
287309
func (rc *Client) IsObjectNamespaced(obj runtime.Object) (namespaced bool, err error) {
288-
rc.retry(context.Background(), func(ctx context.Context) error {
310+
rc.retry(rc.context, func(ctx context.Context) error {
289311
namespaced, err = rc.internal.IsObjectNamespaced(obj)
290312
return err
291313
})

pkg/retry/retry_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,31 @@ var _ = Describe("Client", func() {
305305
Expect(mc.attempts).To(BeNumerically("<=", 3))
306306
})
307307

308+
It("should handle WithContext correctly", func() {
309+
env, mc := defaultTestSetup()
310+
c := retry.NewRetryingClient(env.Client()).WithMaxAttempts(0).WithTimeout(500 * time.Millisecond)
311+
312+
type dummy struct {
313+
corev1.Namespace
314+
}
315+
316+
mc.reset(-1)
317+
now := time.Now()
318+
timeoutCtx, cancel := context.WithTimeout(env.Ctx, 200*time.Millisecond)
319+
defer cancel()
320+
_, err := c.WithContext(timeoutCtx).GroupVersionKindFor(&dummy{})
321+
Expect(err).To(HaveOccurred())
322+
after := time.Now()
323+
Expect(after.Sub(now)).To(BeNumerically("<", 300*time.Millisecond))
324+
325+
// should not use the same context again, it should have been reset
326+
now = time.Now()
327+
_, err = c.GroupVersionKindFor(&dummy{})
328+
Expect(err).To(HaveOccurred())
329+
after = time.Now()
330+
Expect(after.Sub(now)).To(BeNumerically(">", 300*time.Millisecond))
331+
})
332+
308333
It("should pass the arguments through correctly", func() {
309334
env, mc := defaultTestSetup()
310335
c := retry.NewRetryingClient(env.Client())

0 commit comments

Comments
 (0)