github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/pkg/filesystem/chunk/backoff/backoff.go (about) 1 package backoff 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/cloudreve/Cloudreve/v3/pkg/util" 7 "net/http" 8 "strconv" 9 "time" 10 ) 11 12 // Backoff used for retry sleep backoff 13 type Backoff interface { 14 Next(err error) bool 15 Reset() 16 } 17 18 // ConstantBackoff implements Backoff interface with constant sleep time. If the error 19 // is retryable and with `RetryAfter` defined, the `RetryAfter` will be used as sleep duration. 20 type ConstantBackoff struct { 21 Sleep time.Duration 22 Max int 23 24 tried int 25 } 26 27 func (c *ConstantBackoff) Next(err error) bool { 28 c.tried++ 29 if c.tried > c.Max { 30 return false 31 } 32 33 var e *RetryableError 34 if errors.As(err, &e) && e.RetryAfter > 0 { 35 util.Log().Warning("Retryable error %q occurs in backoff, will sleep after %s.", e, e.RetryAfter) 36 time.Sleep(e.RetryAfter) 37 } else { 38 time.Sleep(c.Sleep) 39 } 40 41 return true 42 } 43 44 func (c *ConstantBackoff) Reset() { 45 c.tried = 0 46 } 47 48 type RetryableError struct { 49 Err error 50 RetryAfter time.Duration 51 } 52 53 // NewRetryableErrorFromHeader constructs a new RetryableError from http response header 54 // and existing error. 55 func NewRetryableErrorFromHeader(err error, header http.Header) *RetryableError { 56 retryAfter := header.Get("retry-after") 57 if retryAfter == "" { 58 retryAfter = "0" 59 } 60 61 res := &RetryableError{ 62 Err: err, 63 } 64 65 if retryAfterSecond, err := strconv.ParseInt(retryAfter, 10, 64); err == nil { 66 res.RetryAfter = time.Duration(retryAfterSecond) * time.Second 67 } 68 69 return res 70 } 71 72 func (e *RetryableError) Error() string { 73 return fmt.Sprintf("retryable error with retry-after=%s: %s", e.RetryAfter, e.Err) 74 }