mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 02:32:25 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			146 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			146 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package backoff
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| // An OperationWithData is executing by RetryWithData() or RetryNotifyWithData().
 | |
| // The operation will be retried using a backoff policy if it returns an error.
 | |
| type OperationWithData[T any] func() (T, error)
 | |
| 
 | |
| // An Operation is executing by Retry() or RetryNotify().
 | |
| // The operation will be retried using a backoff policy if it returns an error.
 | |
| type Operation func() error
 | |
| 
 | |
| func (o Operation) withEmptyData() OperationWithData[struct{}] {
 | |
| 	return func() (struct{}, error) {
 | |
| 		return struct{}{}, o()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Notify is a notify-on-error function. It receives an operation error and
 | |
| // backoff delay if the operation failed (with an error).
 | |
| //
 | |
| // NOTE that if the backoff policy stated to stop retrying,
 | |
| // the notify function isn't called.
 | |
| type Notify func(error, time.Duration)
 | |
| 
 | |
| // Retry the operation o until it does not return error or BackOff stops.
 | |
| // o is guaranteed to be run at least once.
 | |
| //
 | |
| // If o returns a *PermanentError, the operation is not retried, and the
 | |
| // wrapped error is returned.
 | |
| //
 | |
| // Retry sleeps the goroutine for the duration returned by BackOff after a
 | |
| // failed operation returns.
 | |
| func Retry(o Operation, b BackOff) error {
 | |
| 	return RetryNotify(o, b, nil)
 | |
| }
 | |
| 
 | |
| // RetryWithData is like Retry but returns data in the response too.
 | |
| func RetryWithData[T any](o OperationWithData[T], b BackOff) (T, error) {
 | |
| 	return RetryNotifyWithData(o, b, nil)
 | |
| }
 | |
| 
 | |
| // RetryNotify calls notify function with the error and wait duration
 | |
| // for each failed attempt before sleep.
 | |
| func RetryNotify(operation Operation, b BackOff, notify Notify) error {
 | |
| 	return RetryNotifyWithTimer(operation, b, notify, nil)
 | |
| }
 | |
| 
 | |
| // RetryNotifyWithData is like RetryNotify but returns data in the response too.
 | |
| func RetryNotifyWithData[T any](operation OperationWithData[T], b BackOff, notify Notify) (T, error) {
 | |
| 	return doRetryNotify(operation, b, notify, nil)
 | |
| }
 | |
| 
 | |
| // RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
 | |
| // for each failed attempt before sleep.
 | |
| // A default timer that uses system timer is used when nil is passed.
 | |
| func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
 | |
| 	_, err := doRetryNotify(operation.withEmptyData(), b, notify, t)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // RetryNotifyWithTimerAndData is like RetryNotifyWithTimer but returns data in the response too.
 | |
| func RetryNotifyWithTimerAndData[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
 | |
| 	return doRetryNotify(operation, b, notify, t)
 | |
| }
 | |
| 
 | |
| func doRetryNotify[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
 | |
| 	var (
 | |
| 		err  error
 | |
| 		next time.Duration
 | |
| 		res  T
 | |
| 	)
 | |
| 	if t == nil {
 | |
| 		t = &defaultTimer{}
 | |
| 	}
 | |
| 
 | |
| 	defer func() {
 | |
| 		t.Stop()
 | |
| 	}()
 | |
| 
 | |
| 	ctx := getContext(b)
 | |
| 
 | |
| 	b.Reset()
 | |
| 	for {
 | |
| 		res, err = operation()
 | |
| 		if err == nil {
 | |
| 			return res, nil
 | |
| 		}
 | |
| 
 | |
| 		var permanent *PermanentError
 | |
| 		if errors.As(err, &permanent) {
 | |
| 			return res, permanent.Err
 | |
| 		}
 | |
| 
 | |
| 		if next = b.NextBackOff(); next == Stop {
 | |
| 			if cerr := ctx.Err(); cerr != nil {
 | |
| 				return res, cerr
 | |
| 			}
 | |
| 
 | |
| 			return res, err
 | |
| 		}
 | |
| 
 | |
| 		if notify != nil {
 | |
| 			notify(err, next)
 | |
| 		}
 | |
| 
 | |
| 		t.Start(next)
 | |
| 
 | |
| 		select {
 | |
| 		case <-ctx.Done():
 | |
| 			return res, ctx.Err()
 | |
| 		case <-t.C():
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // PermanentError signals that the operation should not be retried.
 | |
| type PermanentError struct {
 | |
| 	Err error
 | |
| }
 | |
| 
 | |
| func (e *PermanentError) Error() string {
 | |
| 	return e.Err.Error()
 | |
| }
 | |
| 
 | |
| func (e *PermanentError) Unwrap() error {
 | |
| 	return e.Err
 | |
| }
 | |
| 
 | |
| func (e *PermanentError) Is(target error) bool {
 | |
| 	_, ok := target.(*PermanentError)
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| // Permanent wraps the given err in a *PermanentError.
 | |
| func Permanent(err error) error {
 | |
| 	if err == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return &PermanentError{
 | |
| 		Err: err,
 | |
| 	}
 | |
| }
 |