Add connection timeout retries to item retry handler (#4706)
We were missing a retry for connection timeout issues. Adding it. --- #### Does this PR need a docs update or release note? - [x] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [ ] ⛔ No #### Type of change <!--- Please check the type of change your PR introduces: ---> - [ ] 🌻 Feature - [x] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * #<issue> #### Test Plan <!-- How will this be tested prior to merging.--> - [ ] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
957a33b6d9
commit
0fcbf75f6f
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Handle OneDrive folders being deleted and recreated midway through a backup
|
- Handle OneDrive folders being deleted and recreated midway through a backup
|
||||||
- Automatically re-run a full delta query on incremental if the prior backup is found to have malformed prior-state information.
|
- Automatically re-run a full delta query on incremental if the prior backup is found to have malformed prior-state information.
|
||||||
- Retry drive item permission downloads during long-running backups after the jwt token expires and refreshes.
|
- Retry drive item permission downloads during long-running backups after the jwt token expires and refreshes.
|
||||||
|
- Retry item downloads during connection timeouts.
|
||||||
|
|
||||||
## [v0.15.0] (beta) - 2023-10-31
|
## [v0.15.0] (beta) - 2023-10-31
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -31,6 +34,18 @@ var retryErrs = []error{
|
|||||||
io.ErrUnexpectedEOF,
|
io.ErrUnexpectedEOF,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(pandeyabs): Consolidate error funcs with retryErrs slice.
|
||||||
|
func isTimeoutErr(err error) bool {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *url.Error:
|
||||||
|
return err.Timeout()
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Is(err, os.ErrDeadlineExceeded) ||
|
||||||
|
errors.Is(err, http.ErrHandlerTimeout) ||
|
||||||
|
os.IsTimeout(err)
|
||||||
|
}
|
||||||
|
|
||||||
type Getter interface {
|
type Getter interface {
|
||||||
// SupportsRange returns true if this Getter supports adding Range headers to
|
// SupportsRange returns true if this Getter supports adding Range headers to
|
||||||
// the Get call. Otherwise returns false.
|
// the Get call. Otherwise returns false.
|
||||||
@ -91,7 +106,8 @@ func isRetriable(err error) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
// Retry on read connection timeouts.
|
||||||
|
return isTimeoutErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rrh *resetRetryHandler) Read(p []byte) (int, error) {
|
func (rrh *resetRetryHandler) Read(p []byte) (int, error) {
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -510,52 +514,91 @@ func (suite *ResetRetryHandlerUnitSuite) TestResetRetryHandler() {
|
|||||||
func (suite *ResetRetryHandlerUnitSuite) TestIsRetriable() {
|
func (suite *ResetRetryHandlerUnitSuite) TestIsRetriable() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
err error
|
err func() error
|
||||||
expect bool
|
expect bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "nil",
|
name: "nil",
|
||||||
err: nil,
|
err: func() error { return nil },
|
||||||
expect: false,
|
expect: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Connection Reset Error",
|
name: "Connection Reset Error",
|
||||||
err: syscall.ECONNRESET,
|
err: func() error { return syscall.ECONNRESET },
|
||||||
expect: true,
|
expect: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Unexpected EOF Error",
|
name: "Unexpected EOF Error",
|
||||||
err: io.ErrUnexpectedEOF,
|
err: func() error { return io.ErrUnexpectedEOF },
|
||||||
expect: true,
|
expect: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Not Retriable Error",
|
name: "Not Retriable Error",
|
||||||
err: assert.AnError,
|
err: func() error { return assert.AnError },
|
||||||
expect: false,
|
expect: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Chained Errors With No Retriables",
|
name: "Chained Errors With No Retriables",
|
||||||
err: clues.Stack(assert.AnError, clues.New("another error")),
|
err: func() error {
|
||||||
|
return clues.Stack(assert.AnError, clues.New("another error"))
|
||||||
|
},
|
||||||
expect: false,
|
expect: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Chained Errors With ECONNRESET",
|
name: "Chained Errors With ECONNRESET",
|
||||||
err: clues.Stack(assert.AnError, syscall.ECONNRESET, assert.AnError),
|
err: func() error {
|
||||||
|
return clues.Stack(assert.AnError, syscall.ECONNRESET, assert.AnError)
|
||||||
|
},
|
||||||
expect: true,
|
expect: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Chained Errors With ErrUnexpectedEOF",
|
name: "Chained Errors With ErrUnexpectedEOF",
|
||||||
err: clues.Stack(assert.AnError, io.ErrUnexpectedEOF, assert.AnError),
|
err: func() error {
|
||||||
|
return clues.Stack(assert.AnError, io.ErrUnexpectedEOF, assert.AnError)
|
||||||
|
},
|
||||||
expect: true,
|
expect: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Wrapped ECONNRESET Error",
|
name: "Wrapped ECONNRESET Error",
|
||||||
err: clues.Wrap(syscall.ECONNRESET, "wrapped error"),
|
err: func() error {
|
||||||
|
return clues.Wrap(syscall.ECONNRESET, "wrapped error")
|
||||||
|
},
|
||||||
expect: true,
|
expect: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Wrapped ErrUnexpectedEOF Error",
|
name: "Wrapped ErrUnexpectedEOF Error",
|
||||||
err: clues.Wrap(io.ErrUnexpectedEOF, "wrapped error"),
|
err: func() error {
|
||||||
|
return clues.Wrap(io.ErrUnexpectedEOF, "wrapped error")
|
||||||
|
},
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Timeout - http timeout",
|
||||||
|
err: func() error { return http.ErrHandlerTimeout },
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Timeout - url error",
|
||||||
|
err: func() error {
|
||||||
|
return &url.Error{Err: os.ErrDeadlineExceeded}
|
||||||
|
},
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Timeout - OS timeout",
|
||||||
|
err: func() error {
|
||||||
|
return &os.PathError{Err: os.ErrDeadlineExceeded}
|
||||||
|
},
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Timeout - net timeout",
|
||||||
|
err: func() error {
|
||||||
|
return &net.OpError{
|
||||||
|
Op: "read",
|
||||||
|
Err: &os.PathError{Err: os.ErrDeadlineExceeded},
|
||||||
|
}
|
||||||
|
},
|
||||||
expect: true,
|
expect: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -564,7 +607,7 @@ func (suite *ResetRetryHandlerUnitSuite) TestIsRetriable() {
|
|||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
|
||||||
assert.Equal(t, test.expect, isRetriable(test.err))
|
assert.Equal(t, test.expect, isRetriable(test.err()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user