Skip to content

Commit 88ed0c0

Browse files
try approach without reflection
1 parent 3f84379 commit 88ed0c0

File tree

1 file changed

+82
-93
lines changed

1 file changed

+82
-93
lines changed

pkg/retry/retry.go

Lines changed: 82 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package retry
22

33
import (
44
"context"
5-
"reflect"
65
"time"
76

87
"k8s.io/apimachinery/pkg/api/meta"
@@ -123,42 +122,24 @@ func (rc *Client) WithTimeout(timeout time.Duration) *Client {
123122
// CLIENT IMPLEMENTATION //
124123
///////////////////////////
125124

125+
type callbackFn func(ctx context.Context) error
126+
126127
type operation struct {
127128
parent *Client
128129
interval time.Duration
129130
attempts int
130131
startTime time.Time
131-
method reflect.Value
132-
args []reflect.Value
132+
cfn callbackFn
133133
}
134134

135-
func (rc *Client) newOperation(method reflect.Value, args ...any) *operation {
136-
op := &operation{
135+
func (rc *Client) newOperation(cfn callbackFn) *operation {
136+
return &operation{
137137
parent: rc,
138138
interval: rc.interval,
139139
attempts: 0,
140140
startTime: time.Now(),
141-
method: method,
141+
cfn: cfn,
142142
}
143-
if method.Type().IsVariadic() {
144-
argCountWithoutVariadic := len(args) - 1
145-
last := args[argCountWithoutVariadic]
146-
lastVal := reflect.ValueOf(last)
147-
argCountVariadic := lastVal.Len()
148-
op.args = make([]reflect.Value, argCountWithoutVariadic+argCountVariadic)
149-
for i, arg := range args[:argCountWithoutVariadic] {
150-
op.args[i] = reflect.ValueOf(arg)
151-
}
152-
for i := range argCountVariadic {
153-
op.args[argCountWithoutVariadic+i] = lastVal.Index(i)
154-
}
155-
} else {
156-
op.args = make([]reflect.Value, len(args))
157-
for i, arg := range args {
158-
op.args[i] = reflect.ValueOf(arg)
159-
}
160-
}
161-
return op
162143
}
163144

164145
// try attempts the operation.
@@ -169,20 +150,12 @@ func (rc *Client) newOperation(method reflect.Value, args ...any) *operation {
169150
// This can be because the operation succeeded, or because the timeout or retry limit was reached.
170151
//
171152
// The third return value contains the return values of the operation.
172-
func (op *operation) try() (bool, time.Duration, []reflect.Value) {
173-
res := op.method.Call(op.args)
174-
175-
// check for success by converting the last return value to an error
176-
success := true
177-
if len(res) > 0 {
178-
if err, ok := res[len(res)-1].Interface().(error); ok && err != nil {
179-
success = false
180-
}
181-
}
153+
func (op *operation) try(ctx context.Context) (bool, time.Duration) {
154+
err := op.cfn(ctx)
182155

183156
// if the operation succeeded, return true and no retry
184-
if success {
185-
return true, 0, res
157+
if err == nil {
158+
return true, 0
186159
}
187160

188161
// if the operation failed, check if we should retry
@@ -191,116 +164,132 @@ func (op *operation) try() (bool, time.Duration, []reflect.Value) {
191164
op.interval = time.Duration(float64(op.interval) * op.parent.backoffMultiplier)
192165
if (op.parent.maxAttempts > 0 && op.attempts >= op.parent.maxAttempts) || (op.parent.timeout > 0 && time.Now().Add(retryAfter).After(op.startTime.Add(op.parent.timeout))) {
193166
// if we reached the maximum number of retries or the next retry would exceed the timeout, return false and no retry
194-
return false, 0, res
167+
return false, 0
195168
}
196169

197-
return false, retryAfter, res
170+
return false, retryAfter
198171
}
199172

200173
// retry executes the given method with the provided arguments, retrying on failure.
201-
func (rc *Client) retry(method reflect.Value, args ...any) []reflect.Value {
202-
op := rc.newOperation(method, args...)
203-
var ctx context.Context
204-
if len(args) > 0 {
205-
if ctxArg, ok := args[0].(context.Context); ok {
206-
ctx = ctxArg
207-
}
208-
}
209-
if ctx == nil {
210-
ctx = context.Background()
211-
}
174+
func (rc *Client) retry(ctx context.Context, cfn callbackFn) {
175+
op := rc.newOperation(cfn)
212176
if rc.Timeout() > 0 {
213177
var cancel context.CancelFunc
214178
ctx, cancel = context.WithDeadline(ctx, op.startTime.Add(rc.timeout))
215179
defer cancel()
216180
}
217181
interruptedOrTimeouted := ctx.Done()
218-
success, retryAfter, res := op.try()
182+
success, retryAfter := op.try(ctx)
219183
for !success && retryAfter > 0 {
220184
opCtx, opCancel := context.WithTimeout(ctx, retryAfter)
221185
expired := opCtx.Done()
222186
select {
223187
case <-interruptedOrTimeouted:
224188
retryAfter = 0 // stop retrying if the context was cancelled
225189
case <-expired:
226-
success, retryAfter, res = op.try()
190+
success, retryAfter = op.try(ctx)
227191
}
228192
opCancel()
229193
}
230-
return res
231-
}
232-
233-
func errOrNil(val reflect.Value) error {
234-
if val.IsNil() {
235-
return nil
236-
}
237-
return val.Interface().(error)
238194
}
239195

240196
// CreateOrUpdate wraps the controllerutil.CreateOrUpdate function and retries it on failure.
241-
func (rc *Client) CreateOrUpdate(ctx context.Context, obj client.Object, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
242-
res := rc.retry(reflect.ValueOf(controllerutil.CreateOrUpdate), ctx, rc.internal, obj, f)
243-
return res[0].Interface().(controllerutil.OperationResult), errOrNil(res[1])
197+
func (rc *Client) CreateOrUpdate(ctx context.Context, obj client.Object, f controllerutil.MutateFn) (res controllerutil.OperationResult, err error) {
198+
rc.retry(ctx, func(ctx context.Context) error {
199+
res, err = controllerutil.CreateOrUpdate(ctx, rc.internal, obj, f)
200+
return err
201+
})
202+
return
244203
}
245204

246205
// CreateOrPatch wraps the controllerutil.CreateOrPatch function and retries it on failure.
247-
func (rc *Client) CreateOrPatch(ctx context.Context, obj client.Object, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
248-
res := rc.retry(reflect.ValueOf(controllerutil.CreateOrPatch), ctx, rc.internal, obj, f)
249-
return res[0].Interface().(controllerutil.OperationResult), errOrNil(res[1])
206+
func (rc *Client) CreateOrPatch(ctx context.Context, obj client.Object, f controllerutil.MutateFn) (res controllerutil.OperationResult, err error) {
207+
rc.retry(ctx, func(ctx context.Context) error {
208+
res, err = controllerutil.CreateOrPatch(ctx, rc.internal, obj, f)
209+
return err
210+
})
211+
return
250212
}
251213

252214
// Create wraps the client's Create method and retries it on failure.
253-
func (rc *Client) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
254-
res := rc.retry(reflect.ValueOf(rc.internal.Create), ctx, obj, opts)
255-
return errOrNil(res[0])
215+
func (rc *Client) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) (err error) {
216+
rc.retry(ctx, func(ctx context.Context) error {
217+
err = rc.internal.Create(ctx, obj, opts...)
218+
return err
219+
})
220+
return
256221
}
257222

258223
// Delete wraps the client's Delete method and retries it on failure.
259-
func (rc *Client) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
260-
res := rc.retry(reflect.ValueOf(rc.internal.Delete), ctx, obj, opts)
261-
return errOrNil(res[0])
224+
func (rc *Client) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) (err error) {
225+
rc.retry(ctx, func(ctx context.Context) error {
226+
err = rc.internal.Delete(ctx, obj, opts...)
227+
return err
228+
})
229+
return
262230
}
263231

264232
// DeleteAllOf wraps the client's DeleteAllOf method and retries it on failure.
265-
func (rc *Client) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
266-
res := rc.retry(reflect.ValueOf(rc.internal.DeleteAllOf), ctx, obj, opts)
267-
return errOrNil(res[0])
233+
func (rc *Client) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) (err error) {
234+
rc.retry(ctx, func(ctx context.Context) error {
235+
err = rc.internal.DeleteAllOf(ctx, obj, opts...)
236+
return err
237+
})
238+
return
268239
}
269240

270241
// Get wraps the client's Get method and retries it on failure.
271-
func (rc *Client) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
272-
res := rc.retry(reflect.ValueOf(rc.internal.Get), ctx, key, obj, opts)
273-
return errOrNil(res[0])
242+
func (rc *Client) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) (err error) {
243+
rc.retry(ctx, func(ctx context.Context) error {
244+
err = rc.internal.Get(ctx, key, obj, opts...)
245+
return err
246+
})
247+
return
274248
}
275249

276250
// List wraps the client's List method and retries it on failure.
277-
func (rc *Client) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
278-
res := rc.retry(reflect.ValueOf(rc.internal.List), ctx, list, opts)
279-
return errOrNil(res[0])
251+
func (rc *Client) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) (err error) {
252+
rc.retry(ctx, func(ctx context.Context) error {
253+
err = rc.internal.List(ctx, list, opts...)
254+
return err
255+
})
256+
return
280257
}
281258

282259
// Patch wraps the client's Patch method and retries it on failure.
283-
func (rc *Client) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
284-
res := rc.retry(reflect.ValueOf(rc.internal.Patch), ctx, obj, patch, opts)
285-
return errOrNil(res[0])
260+
func (rc *Client) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) (err error) {
261+
rc.retry(ctx, func(ctx context.Context) error {
262+
err = rc.internal.Patch(ctx, obj, patch, opts...)
263+
return err
264+
})
265+
return
286266
}
287267

288268
// Update wraps the client's Update method and retries it on failure.
289-
func (rc *Client) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
290-
res := rc.retry(reflect.ValueOf(rc.internal.Update), ctx, obj, opts)
291-
return errOrNil(res[0])
269+
func (rc *Client) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) (err error) {
270+
rc.retry(ctx, func(ctx context.Context) error {
271+
err = rc.internal.Update(ctx, obj, opts...)
272+
return err
273+
})
274+
return
292275
}
293276

294277
// GroupVersionKindFor wraps the client's GroupVersionKindFor method and retries it on failure.
295-
func (rc *Client) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
296-
res := rc.retry(reflect.ValueOf(rc.internal.GroupVersionKindFor), obj)
297-
return res[0].Interface().(schema.GroupVersionKind), errOrNil(res[1])
278+
func (rc *Client) GroupVersionKindFor(obj runtime.Object) (gvk schema.GroupVersionKind, err error) {
279+
rc.retry(context.Background(), func(ctx context.Context) error {
280+
gvk, err = rc.internal.GroupVersionKindFor(obj)
281+
return err
282+
})
283+
return
298284
}
299285

300286
// IsObjectNamespaced wraps the client's IsObjectNamespaced method and retries it on failure.
301-
func (rc *Client) IsObjectNamespaced(obj runtime.Object) (bool, error) {
302-
res := rc.retry(reflect.ValueOf(rc.internal.IsObjectNamespaced), obj)
303-
return res[0].Interface().(bool), errOrNil(res[1])
287+
func (rc *Client) IsObjectNamespaced(obj runtime.Object) (namespaced bool, err error) {
288+
rc.retry(context.Background(), func(ctx context.Context) error {
289+
namespaced, err = rc.internal.IsObjectNamespaced(obj)
290+
return err
291+
})
292+
return
304293
}
305294

306295
// RESTMapper calls the internal client's RESTMapper method.

0 commit comments

Comments
 (0)