github.com/qiniu/go-sdk/v7@v7.27.0/internal/clientv2/interceptor_retry_simple_test.go (about)

     1  //go:build unit
     2  // +build unit
     3  
     4  package clientv2
     5  
     6  import (
     7  	"context"
     8  	"math"
     9  	"net"
    10  	"net/http"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/qiniu/go-sdk/v7/storagev2/backoff"
    15  	"github.com/qiniu/go-sdk/v7/storagev2/chooser"
    16  	"github.com/qiniu/go-sdk/v7/storagev2/resolver"
    17  	"github.com/qiniu/go-sdk/v7/storagev2/retrier"
    18  )
    19  
    20  func TestSimpleAlwaysRetryInterceptor(t *testing.T) {
    21  	retryMax := 1
    22  	doCount := 0
    23  	callbackedCount := 0
    24  	rInterceptor := NewSimpleRetryInterceptor(SimpleRetryConfig{
    25  		RetryMax: retryMax,
    26  		RetryInterval: func() time.Duration {
    27  			return time.Second
    28  		},
    29  		ShouldRetry: func(req *http.Request, resp *http.Response, err error) bool {
    30  			return true
    31  		},
    32  		Resolver: resolver.NewDefaultResolver(),
    33  		BeforeResolve: func(req *http.Request) {
    34  			callbackedCount += 1
    35  		},
    36  		AfterResolve: func(req *http.Request, ips []net.IP) {
    37  			callbackedCount += 1
    38  			if len(ips) == 0 {
    39  				t.Fatal("unexpected ips", ips)
    40  			}
    41  		},
    42  		ResolveError: func(req *http.Request, err error) {
    43  			t.Fatal("unexpected error", err)
    44  		},
    45  		Chooser: chooser.NewDirectChooser(),
    46  		BeforeBackoff: func(req *http.Request, options *retrier.RetrierOptions, duration time.Duration) {
    47  			callbackedCount += 1
    48  			if options.Attempts != 0 {
    49  				t.Fatalf("unexpected attempts:%d", options.Attempts)
    50  			}
    51  			if duration != time.Second {
    52  				t.Fatalf("unexpected duration:%v", duration)
    53  			}
    54  		},
    55  		AfterBackoff: func(req *http.Request, options *retrier.RetrierOptions, duration time.Duration) {
    56  			callbackedCount += 1
    57  			if options.Attempts != 0 {
    58  				t.Fatalf("unexpected attempts:%d", options.Attempts)
    59  			}
    60  			if duration != time.Second {
    61  				t.Fatalf("unexpected duration:%v", duration)
    62  			}
    63  		},
    64  		BeforeRequest: func(req *http.Request, options *retrier.RetrierOptions) {
    65  			callbackedCount += 1
    66  			if options.Attempts != doCount {
    67  				t.Fatal("unexpected attempts", options.Attempts)
    68  			}
    69  		},
    70  		AfterResponse: func(resp *http.Response, options *retrier.RetrierOptions, err error) {
    71  			callbackedCount += 1
    72  			if options.Attempts != (doCount - 1) {
    73  				t.Fatal("unexpected attempts", options.Attempts)
    74  			}
    75  			if err != nil {
    76  				t.Fatal("unexpected error", err)
    77  			}
    78  		},
    79  	})
    80  
    81  	interceptor := NewSimpleInterceptor(func(req *http.Request, handler Handler) (*http.Response, error) {
    82  		doCount += 1
    83  
    84  		value := req.Header.Get(headerKey)
    85  		value += " -> request"
    86  		req.Header.Set(headerKey, value)
    87  
    88  		resp, err := handler(req)
    89  
    90  		value = resp.Header.Get(headerKey)
    91  		value += " -> response"
    92  		resp.Header.Set(headerKey, value)
    93  		return resp, err
    94  	})
    95  
    96  	c := NewClient(&testClient{}, rInterceptor, interceptor)
    97  
    98  	start := time.Now()
    99  	resp, _ := Do(c, RequestParams{
   100  		Context: nil,
   101  		Method:  "",
   102  		Url:     "https://aaa.com",
   103  		Header:  nil,
   104  		GetBody: nil,
   105  	})
   106  	duration := float32(time.Now().UnixNano()-start.UnixNano()) / 1e9
   107  
   108  	if duration > float32(doCount-1)+0.3 || duration < float32(doCount-1)-0.3 {
   109  		t.Fatalf("retry interval may be error:%f", duration)
   110  	}
   111  
   112  	if (retryMax + 1) != doCount {
   113  		t.Fatalf("retry count is not 2")
   114  	}
   115  
   116  	value := resp.Header.Get(headerKey)
   117  	if value != " -> request -> Do -> response" {
   118  		t.Fatalf("retry flow error")
   119  	}
   120  	if callbackedCount != 8 {
   121  		t.Fatalf("unexpected callbackedCount: %d", callbackedCount)
   122  	}
   123  }
   124  
   125  func TestSimpleNotRetryInterceptor(t *testing.T) {
   126  	retryMax := 1
   127  	doCount := 0
   128  	callbackedCount := 0
   129  	rInterceptor := NewSimpleRetryInterceptor(SimpleRetryConfig{
   130  		RetryMax: retryMax,
   131  		RetryInterval: func() time.Duration {
   132  			return time.Second
   133  		},
   134  		// 默认状态码是 400,400 不重试
   135  		//ShouldRetry: func(req *http.Request, resp *http.Response, err error) bool {
   136  		//	return true
   137  		//},
   138  		Resolver: resolver.NewDefaultResolver(),
   139  		BeforeResolve: func(req *http.Request) {
   140  			callbackedCount += 1
   141  		},
   142  		AfterResolve: func(req *http.Request, ips []net.IP) {
   143  			callbackedCount += 1
   144  			if len(ips) == 0 {
   145  				t.Fatal("unexpected ips", ips)
   146  			}
   147  		},
   148  		ResolveError: func(req *http.Request, err error) {
   149  			t.Fatal("unexpected error", err)
   150  		},
   151  		Chooser: chooser.NewDirectChooser(),
   152  		BeforeBackoff: func(req *http.Request, options *retrier.RetrierOptions, duration time.Duration) {
   153  			callbackedCount += 1
   154  			if options.Attempts != 0 {
   155  				t.Fatalf("unexpected attempts:%d", options.Attempts)
   156  			}
   157  			if duration != time.Second {
   158  				t.Fatalf("unexpected duration:%v", duration)
   159  			}
   160  		},
   161  		AfterBackoff: func(req *http.Request, options *retrier.RetrierOptions, duration time.Duration) {
   162  			callbackedCount += 1
   163  			if options.Attempts != 0 {
   164  				t.Fatalf("unexpected attempts:%d", options.Attempts)
   165  			}
   166  			if duration != time.Second {
   167  				t.Fatalf("unexpected duration:%v", duration)
   168  			}
   169  		},
   170  		BeforeRequest: func(req *http.Request, options *retrier.RetrierOptions) {
   171  			callbackedCount += 1
   172  			if options.Attempts != doCount {
   173  				t.Fatal("unexpected attempts", options.Attempts)
   174  			}
   175  		},
   176  		AfterResponse: func(resp *http.Response, options *retrier.RetrierOptions, err error) {
   177  			callbackedCount += 1
   178  			if options.Attempts != (doCount - 1) {
   179  				t.Fatal("unexpected attempts", options.Attempts)
   180  			}
   181  			if err != nil {
   182  				t.Fatal("unexpected error", err)
   183  			}
   184  		},
   185  	})
   186  
   187  	interceptor := NewSimpleInterceptor(func(req *http.Request, handler Handler) (*http.Response, error) {
   188  		doCount += 1
   189  
   190  		value := req.Header.Get(headerKey)
   191  		value += " -> request"
   192  		req.Header.Set(headerKey, value)
   193  
   194  		resp, err := handler(req)
   195  
   196  		value = resp.Header.Get(headerKey)
   197  		value += " -> response"
   198  		resp.Header.Set(headerKey, value)
   199  		return resp, err
   200  	})
   201  
   202  	c := NewClient(&testClient{statusCode: 400}, rInterceptor, interceptor)
   203  
   204  	start := time.Now()
   205  	resp, _ := Do(c, RequestParams{
   206  		Context: nil,
   207  		Method:  "",
   208  		Url:     "https://aaa.com",
   209  		Header:  nil,
   210  		GetBody: nil,
   211  	})
   212  	duration := time.Since(start)
   213  
   214  	// 不重试,只执行一次,不等待
   215  	if duration > 500*time.Millisecond {
   216  		t.Fatalf("retry interval may be error")
   217  	}
   218  
   219  	if doCount != 1 {
   220  		t.Fatalf("retry count is not 1")
   221  	}
   222  
   223  	value := resp.Header.Get(headerKey)
   224  	if value != " -> request -> Do -> response" {
   225  		t.Fatalf("retry flow error")
   226  	}
   227  	if callbackedCount != 4 {
   228  		t.Fatalf("unexpected callbackedCount: %d", callbackedCount)
   229  	}
   230  }
   231  
   232  func TestSimpleRetryInterceptorWithNoopResolver(t *testing.T) {
   233  	doCount := 0
   234  	resolveCount := 0
   235  	rInterceptor := NewSimpleRetryInterceptor(SimpleRetryConfig{
   236  		RetryMax: 0,
   237  		Resolver: resolver.NewResolver(func(ctx context.Context, host string) ([]net.IP, error) {
   238  			return nil, nil
   239  		}),
   240  		BeforeResolve: func(req *http.Request) {
   241  			resolveCount += 1
   242  		},
   243  		AfterResolve: func(req *http.Request, ips []net.IP) {
   244  			resolveCount += 1
   245  			if len(ips) != 0 {
   246  				t.Fatalf("expected empty ips, got %v", ips)
   247  			}
   248  		},
   249  		ResolveError: func(req *http.Request, err error) {
   250  			t.Fatal("unexpected resolve error", err)
   251  		},
   252  		Chooser: chooser.NewDirectChooser(),
   253  	})
   254  
   255  	interceptor := NewSimpleInterceptor(func(req *http.Request, handler Handler) (*http.Response, error) {
   256  		doCount += 1
   257  		resp, err := handler(req)
   258  		return resp, err
   259  	})
   260  
   261  	c := NewClient(&testClient{statusCode: 200}, rInterceptor, interceptor)
   262  
   263  	resp, err := Do(c, RequestParams{
   264  		Context: nil,
   265  		Method:  "",
   266  		Url:     "https://aaa.com",
   267  		Header:  nil,
   268  		GetBody: nil,
   269  	})
   270  	if err != nil {
   271  		t.Fatal("unexpected error", err)
   272  	}
   273  
   274  	if doCount != 1 {
   275  		t.Fatalf("expected 1 request, got %d", doCount)
   276  	}
   277  	if resp.StatusCode != 200 {
   278  		t.Fatalf("expected status 200, got %d", resp.StatusCode)
   279  	}
   280  	// 首次 resolve 调 BeforeResolve + AfterResolve,
   281  	// ensureChoose 发现无可用 IP 后再次 bypassCache resolve 调 BeforeResolve + AfterResolve
   282  	if resolveCount != 4 {
   283  		t.Fatalf("expected 4 resolve callbacks, got %d", resolveCount)
   284  	}
   285  }
   286  
   287  func TestRetryInterceptorWithBackoff(t *testing.T) {
   288  	retryMax := 5
   289  	doCount := 0
   290  	callbackedCount := 0
   291  	rInterceptor := NewSimpleRetryInterceptor(SimpleRetryConfig{
   292  		RetryMax: retryMax,
   293  		Backoff:  backoff.NewExponentialBackoff(1*time.Second, 2),
   294  		ShouldRetry: func(req *http.Request, resp *http.Response, err error) bool {
   295  			return true
   296  		},
   297  		Resolver: resolver.NewDefaultResolver(),
   298  		BeforeResolve: func(req *http.Request) {
   299  			callbackedCount += 1
   300  		},
   301  		AfterResolve: func(req *http.Request, ips []net.IP) {
   302  			callbackedCount += 1
   303  			if len(ips) == 0 {
   304  				t.Fatal("unexpected ips", ips)
   305  			}
   306  		},
   307  		ResolveError: func(req *http.Request, err error) {
   308  			t.Fatal("unexpected error", err)
   309  		},
   310  		Chooser: chooser.NewDirectChooser(),
   311  		BeforeBackoff: func(req *http.Request, options *retrier.RetrierOptions, duration time.Duration) {
   312  			callbackedCount += 1
   313  			if options.Attempts != (doCount - 1) {
   314  				t.Fatalf("unexpected attempts:%d", options.Attempts)
   315  			}
   316  			if duration != 1*time.Second*time.Duration(math.Pow(2, float64(options.Attempts))) {
   317  				t.Fatalf("unexpected duration:%v", duration)
   318  			}
   319  		},
   320  		AfterBackoff: func(req *http.Request, options *retrier.RetrierOptions, duration time.Duration) {
   321  			callbackedCount += 1
   322  			if options.Attempts != (doCount - 1) {
   323  				t.Fatalf("unexpected attempts:%d", options.Attempts)
   324  			}
   325  			if duration != 1*time.Second*time.Duration(math.Pow(2, float64(options.Attempts))) {
   326  				t.Fatalf("unexpected duration:%v", duration)
   327  			}
   328  		},
   329  		BeforeRequest: func(req *http.Request, options *retrier.RetrierOptions) {
   330  			callbackedCount += 1
   331  			if options.Attempts != doCount {
   332  				t.Fatal("unexpected attempts", options.Attempts)
   333  			}
   334  		},
   335  		AfterResponse: func(resp *http.Response, options *retrier.RetrierOptions, err error) {
   336  			callbackedCount += 1
   337  			if options.Attempts != (doCount - 1) {
   338  				t.Fatal("unexpected attempts", options.Attempts)
   339  			}
   340  			if err != nil {
   341  				t.Fatal("unexpected error", err)
   342  			}
   343  		},
   344  	})
   345  
   346  	interceptor := NewSimpleInterceptor(func(req *http.Request, handler Handler) (*http.Response, error) {
   347  		doCount += 1
   348  
   349  		value := req.Header.Get(headerKey)
   350  		value += " -> request"
   351  		req.Header.Set(headerKey, value)
   352  
   353  		resp, err := handler(req)
   354  
   355  		value = resp.Header.Get(headerKey)
   356  		value += " -> response"
   357  		resp.Header.Set(headerKey, value)
   358  		return resp, err
   359  	})
   360  
   361  	c := NewClient(&testClient{}, rInterceptor, interceptor)
   362  
   363  	start := time.Now()
   364  	resp, _ := Do(c, RequestParams{
   365  		Context: nil,
   366  		Method:  "",
   367  		Url:     "https://aaa.com",
   368  		Header:  nil,
   369  		GetBody: nil,
   370  	})
   371  	duration := time.Since(start)
   372  
   373  	if d := duration - 31*time.Second; d >= 900*time.Millisecond || d <= -900*time.Millisecond {
   374  		t.Fatalf("retry interval may be error:%v", duration)
   375  	}
   376  
   377  	if (retryMax + 1) != doCount {
   378  		t.Fatalf("retry count is not 2")
   379  	}
   380  
   381  	value := resp.Header.Get(headerKey)
   382  	if value != " -> request -> Do -> response" {
   383  		t.Fatalf("retry flow error")
   384  	}
   385  	if callbackedCount != 24 {
   386  		t.Fatalf("unexpected callbackedCount: %d", callbackedCount)
   387  	}
   388  }