github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/topic/retriable_error_test.go (about)

     1  package topic
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/stretchr/testify/require"
    10  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb"
    11  	grpcCodes "google.golang.org/grpc/codes"
    12  	grpcStatus "google.golang.org/grpc/status"
    13  
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/backoff"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/value"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    17  )
    18  
    19  func TestCheckRetryMode(t *testing.T) {
    20  	fastError := xerrors.Transport(grpcStatus.Error(grpcCodes.Unavailable, ""))
    21  	slowError := xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_OVERLOADED))
    22  	unretriable := xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAUTHORIZED))
    23  
    24  	table := []struct {
    25  		name         string
    26  		err          error
    27  		settings     RetrySettings
    28  		duration     time.Duration
    29  		resBackoff   backoff.Backoff
    30  		resRetriable bool
    31  	}{
    32  		{
    33  			name:         "OK",
    34  			err:          nil,
    35  			settings:     RetrySettings{},
    36  			duration:     0,
    37  			resBackoff:   nil,
    38  			resRetriable: false,
    39  		},
    40  		{
    41  			name:         "RetryRetriableErrorFast",
    42  			err:          fastError,
    43  			settings:     RetrySettings{},
    44  			duration:     0,
    45  			resBackoff:   backoff.Fast,
    46  			resRetriable: true,
    47  		},
    48  		{
    49  			name: "RetryRetriableErrorFastWithTimeout",
    50  			err:  fastError,
    51  			settings: RetrySettings{
    52  				StartTimeout: time.Second,
    53  			},
    54  			duration:     time.Second * 2,
    55  			resBackoff:   nil,
    56  			resRetriable: false,
    57  		},
    58  		{
    59  			name:         "RetryRetriableErrorSlow",
    60  			err:          slowError,
    61  			settings:     RetrySettings{},
    62  			duration:     0,
    63  			resBackoff:   backoff.Slow,
    64  			resRetriable: true,
    65  		},
    66  		{
    67  			name: "RetryRetriableErrorSlowWithTimeout",
    68  			err:  slowError,
    69  			settings: RetrySettings{
    70  				StartTimeout: time.Second,
    71  			},
    72  			duration:     time.Second * 2,
    73  			resBackoff:   nil,
    74  			resRetriable: false,
    75  		},
    76  		{
    77  			name:         "UnretriableError",
    78  			err:          unretriable,
    79  			settings:     RetrySettings{},
    80  			duration:     0,
    81  			resBackoff:   nil,
    82  			resRetriable: false,
    83  		},
    84  		{
    85  			name: "UserOverrideFastErrorDefault",
    86  			err:  fastError,
    87  			settings: RetrySettings{
    88  				CheckError: func(errInfo PublicCheckErrorRetryArgs) PublicCheckRetryResult {
    89  					return PublicRetryDecisionDefault
    90  				},
    91  			},
    92  			duration:     0,
    93  			resBackoff:   backoff.Fast,
    94  			resRetriable: true,
    95  		},
    96  		{
    97  			name: "UserOverrideFastErrorRetry",
    98  			err:  fastError,
    99  			settings: RetrySettings{
   100  				CheckError: func(errInfo PublicCheckErrorRetryArgs) PublicCheckRetryResult {
   101  					return PublicRetryDecisionRetry
   102  				},
   103  			},
   104  			duration:     0,
   105  			resBackoff:   backoff.Fast,
   106  			resRetriable: true,
   107  		},
   108  		{
   109  			name: "UserOverrideFastErrorStop",
   110  			err:  fastError,
   111  			settings: RetrySettings{
   112  				CheckError: func(errInfo PublicCheckErrorRetryArgs) PublicCheckRetryResult {
   113  					return PublicRetryDecisionStop
   114  				},
   115  			},
   116  			duration:     0,
   117  			resBackoff:   nil,
   118  			resRetriable: false,
   119  		},
   120  		{
   121  			name: "UserOverrideSlowErrorDefault",
   122  			err:  slowError,
   123  			settings: RetrySettings{
   124  				CheckError: func(errInfo PublicCheckErrorRetryArgs) PublicCheckRetryResult {
   125  					return PublicRetryDecisionDefault
   126  				},
   127  			},
   128  			duration:     0,
   129  			resBackoff:   backoff.Slow,
   130  			resRetriable: true,
   131  		},
   132  		{
   133  			name: "UserOverrideSlowErrorRetry",
   134  			err:  slowError,
   135  			settings: RetrySettings{
   136  				CheckError: func(errInfo PublicCheckErrorRetryArgs) PublicCheckRetryResult {
   137  					return PublicRetryDecisionRetry
   138  				},
   139  			},
   140  			duration:     0,
   141  			resBackoff:   backoff.Slow,
   142  			resRetriable: true,
   143  		},
   144  		{
   145  			name: "UserOverrideSlowErrorStop",
   146  			err:  slowError,
   147  			settings: RetrySettings{
   148  				CheckError: func(errInfo PublicCheckErrorRetryArgs) PublicCheckRetryResult {
   149  					return PublicRetryDecisionStop
   150  				},
   151  			},
   152  			duration:     0,
   153  			resBackoff:   nil,
   154  			resRetriable: false,
   155  		},
   156  		{
   157  			name: "UserOverrideUnretriableErrorDefault",
   158  			err:  xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAUTHORIZED)),
   159  			settings: RetrySettings{
   160  				CheckError: func(errInfo PublicCheckErrorRetryArgs) PublicCheckRetryResult {
   161  					return PublicRetryDecisionDefault
   162  				},
   163  			},
   164  			duration:     0,
   165  			resBackoff:   nil,
   166  			resRetriable: false,
   167  		},
   168  		{
   169  			name: "UserOverrideUnretriableErrorRetry",
   170  			err:  xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAUTHORIZED)),
   171  			settings: RetrySettings{
   172  				CheckError: func(errInfo PublicCheckErrorRetryArgs) PublicCheckRetryResult {
   173  					return PublicRetryDecisionRetry
   174  				},
   175  			},
   176  			duration:     0,
   177  			resBackoff:   backoff.Slow,
   178  			resRetriable: true,
   179  		},
   180  		{
   181  			name: "UserOverrideUnretriableErrorStop",
   182  			err:  xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAUTHORIZED)),
   183  			settings: RetrySettings{
   184  				CheckError: func(errInfo PublicCheckErrorRetryArgs) PublicCheckRetryResult {
   185  					return PublicRetryDecisionStop
   186  				},
   187  			},
   188  			duration:     0,
   189  			resBackoff:   nil,
   190  			resRetriable: false,
   191  		},
   192  		{
   193  			name: "UserOverrideFastErrorRetryWithTimeout",
   194  			err:  xerrors.Operation(xerrors.WithStatusCode(Ydb.StatusIds_UNAUTHORIZED)),
   195  			settings: RetrySettings{
   196  				StartTimeout: time.Second,
   197  				CheckError: func(errInfo PublicCheckErrorRetryArgs) PublicCheckRetryResult {
   198  					return PublicRetryDecisionRetry
   199  				},
   200  			},
   201  			duration:     time.Second * 2,
   202  			resBackoff:   nil,
   203  			resRetriable: false,
   204  		},
   205  		{
   206  			name: "NotCallForNil",
   207  			err:  nil,
   208  			settings: RetrySettings{
   209  				StartTimeout: time.Second,
   210  				CheckError: func(errInfo PublicCheckErrorRetryArgs) PublicCheckRetryResult {
   211  					panic("must not call for nil err")
   212  				},
   213  			},
   214  			duration:     0,
   215  			resBackoff:   nil,
   216  			resRetriable: false,
   217  		},
   218  		{
   219  			name:         "EOF", // Issue https://github.com/ydb-platform/ydb-go-sdk/issues/754
   220  			err:          fmt.Errorf("test wrap: %w", io.EOF),
   221  			settings:     RetrySettings{},
   222  			duration:     0,
   223  			resBackoff:   backoff.Slow,
   224  			resRetriable: true,
   225  		},
   226  	}
   227  
   228  	for _, test := range table {
   229  		t.Run(test.name, func(t *testing.T) {
   230  			resBackoff, retriable := CheckRetryMode(test.err, test.settings, test.duration)
   231  			require.Equal(t, test.resBackoff, resBackoff)
   232  			require.Equal(t, test.resRetriable, retriable)
   233  		})
   234  	}
   235  }
   236  
   237  func TestCheckResetReconnectionCounters(t *testing.T) {
   238  	now := time.Now()
   239  	table := []struct {
   240  		name              string
   241  		lastTry           time.Time
   242  		connectionTimeout time.Duration
   243  		shouldReset       bool
   244  	}{
   245  		{
   246  			name:              "RecentLastTryWithInfiniteConnectionTimeout",
   247  			lastTry:           now.Add(-30 * time.Second),
   248  			connectionTimeout: value.InfiniteDuration,
   249  			shouldReset:       false,
   250  		},
   251  		{
   252  			name:              "OldLastTryWithInfiniteConnectionTimeout",
   253  			lastTry:           now.Add(-30 * time.Minute),
   254  			connectionTimeout: value.InfiniteDuration,
   255  			shouldReset:       true,
   256  		},
   257  		{
   258  			name:              "LastTryLessThanConnectionTimeout",
   259  			lastTry:           now.Add(-30 * time.Second),
   260  			connectionTimeout: time.Minute,
   261  			shouldReset:       false,
   262  		},
   263  		{
   264  			name:              "LastTryGreaterThanConnectionTimeout",
   265  			lastTry:           now.Add(-time.Hour),
   266  			connectionTimeout: time.Minute,
   267  			shouldReset:       true,
   268  		},
   269  	}
   270  
   271  	for _, test := range table {
   272  		t.Run(test.name, func(t *testing.T) {
   273  			shouldReset := CheckResetReconnectionCounters(test.lastTry, now, test.connectionTimeout)
   274  			require.Equal(t, test.shouldReset, shouldReset)
   275  		})
   276  	}
   277  }