@@ -256,12 +256,43 @@ func WithPanicCallback(panicCallback func(e interface{})) panicCallbackOption {
256256//
257257// Warning: if context without deadline or cancellation func was passed, Retry will work infinitely.
258258//
259+ // # If you need to retry your op func on some logic errors - you must return RetryableError() from retryOperation
260+ //
261+ // Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental
262+ func Retry (ctx context.Context , op retryOperation , opts ... Option ) (finalErr error ) {
263+ _ , err := RetryWithResult [struct {}](ctx , func (ctx context.Context ) (* struct {}, error ) {
264+ err := op (ctx )
265+ if err != nil {
266+ return nil , xerrors .WithStackTrace (err )
267+ }
268+
269+ return nil , nil //nolint:nilnil
270+ }, opts ... )
271+ if err != nil {
272+ return xerrors .WithStackTrace (err )
273+ }
274+
275+ return nil
276+ }
277+
278+ // RetryWithResult provide the best effort fo retrying operation which will return value or error
279+ //
280+ // RetryWithResult implements internal busy loop until one of the following conditions is met:
281+ //
282+ // - context was canceled or deadlined
283+ //
284+ // - retry operation returned nil as error
285+ //
286+ // Warning: if context without deadline or cancellation func was passed, RetryWithResult will work infinitely.
287+ //
259288// If you need to retry your op func on some logic errors - you must return RetryableError() from retryOperation
260289//
261290//nolint:funlen
262- func Retry (ctx context.Context , op retryOperation , opts ... Option ) (finalErr error ) {
291+ func RetryWithResult [T any ](ctx context.Context , //nolint:revive
292+ op func (context.Context ) (* T , error ), opts ... Option ,
293+ ) (v * T , finalErr error ) {
263294 options := & retryOptions {
264- call : stack .FunctionID ("github.com/ydb-platform/ydb-go-sdk/3/retry.Retry " ),
295+ call : stack .FunctionID ("github.com/ydb-platform/ydb-go-sdk/3/retry.RetryWithResult " ),
265296 trace : & trace.Retry {},
266297 budget : budget .Limited (- 1 ),
267298 fastBackoff : backoff .Fast ,
@@ -301,15 +332,16 @@ func Retry(ctx context.Context, op retryOperation, opts ...Option) (finalErr err
301332 attempts ++
302333 select {
303334 case <- ctx .Done ():
304- return xerrors .WithStackTrace (
335+ return nil , xerrors .WithStackTrace (
305336 fmt .Errorf ("retry failed on attempt No.%d: %w" , attempts , ctx .Err ()),
306337 )
307338
308339 default :
309- err := opWithRecover (ctx , options , op )
340+ var err error
341+ v , err = opWithRecover (ctx , options , op )
310342
311343 if err == nil {
312- return nil
344+ return v , nil
313345 }
314346
315347 m := Check (err )
@@ -321,7 +353,7 @@ func Retry(ctx context.Context, op retryOperation, opts ...Option) (finalErr err
321353 code = m .StatusCode ()
322354
323355 if ! m .MustRetry (options .idempotent ) {
324- return xerrors .WithStackTrace (
356+ return nil , xerrors .WithStackTrace (
325357 fmt .Errorf ("non-retryable error occurred on attempt No.%d (idempotent=%v): %w" ,
326358 attempts , options .idempotent , err ),
327359 )
@@ -336,7 +368,7 @@ func Retry(ctx context.Context, op retryOperation, opts ...Option) (finalErr err
336368 case <- ctx .Done ():
337369 t .Stop ()
338370
339- return xerrors .WithStackTrace (
371+ return nil , xerrors .WithStackTrace (
340372 xerrors .Join (
341373 fmt .Errorf ("attempt No.%d: %w" , attempts , ctx .Err ()),
342374 err ,
@@ -346,7 +378,7 @@ func Retry(ctx context.Context, op retryOperation, opts ...Option) (finalErr err
346378 t .Stop ()
347379
348380 if acquireErr := options .budget .Acquire (ctx ); acquireErr != nil {
349- return xerrors .WithStackTrace (
381+ return nil , xerrors .WithStackTrace (
350382 xerrors .Join (
351383 fmt .Errorf ("attempt No.%d: %w" , attempts , budget .ErrNoQuota ),
352384 acquireErr ,
@@ -359,7 +391,9 @@ func Retry(ctx context.Context, op retryOperation, opts ...Option) (finalErr err
359391 }
360392}
361393
362- func opWithRecover (ctx context.Context , options * retryOptions , op retryOperation ) (err error ) {
394+ func opWithRecover [T any ](ctx context.Context ,
395+ options * retryOptions , op func (context.Context ) (* T , error ),
396+ ) (_ * T , err error ) {
363397 if options .panicCallback != nil {
364398 defer func () {
365399 if e := recover (); e != nil {
0 commit comments