@@ -2,7 +2,6 @@ package retry
22
33import (
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+
126127type 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