Retry unexpected EOF errors (#4037)

<!-- PR description-->

Adds `io.ErrUnexpectedEOF` to the list of retriable errors looked up by retry handler. 

---

#### Does this PR need a docs update or release note?

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  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:
Abhishek Pandey 2023-08-16 00:57:24 +05:30 committed by GitHub
parent 56820e95e0
commit 77ab14b5df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 98 additions and 4 deletions

View File

@ -28,6 +28,7 @@ const (
// between callers.
var retryErrs = []error{
syscall.ECONNRESET,
io.ErrUnexpectedEOF,
}
type Getter interface {

View File

@ -1,4 +1,4 @@
package readers_test
package readers
import (
"bytes"
@ -12,7 +12,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/common/readers"
"github.com/alcionai/corso/src/internal/tester"
)
@ -141,7 +140,7 @@ func (suite *ResetRetryHandlerUnitSuite) TestResetRetryHandler() {
expectErr error
}{
{
name: "OnlyFirstGetErrors NoRangeSupport",
name: "OnlyFirstGetErrors ECONNRESET NoRangeSupport",
getterResps: map[int]getterResp{
0: {
err: syscall.ECONNRESET,
@ -149,6 +148,15 @@ func (suite *ResetRetryHandlerUnitSuite) TestResetRetryHandler() {
},
expectData: data,
},
{
name: "OnlyFirstGetErrors ErrUnexpectedEOF NoRangeSupport",
getterResps: map[int]getterResp{
0: {
err: io.ErrUnexpectedEOF,
},
},
expectData: data,
},
{
name: "OnlyFirstReadErrors RangeSupport",
supportsRange: true,
@ -223,6 +231,29 @@ func (suite *ResetRetryHandlerUnitSuite) TestResetRetryHandler() {
},
expectData: data,
},
{
name: "MultipleRetriableErrorTypesInMiddle RangeSupport",
supportsRange: true,
getterResps: map[int]getterResp{
1: {offset: 12},
2: {offset: 20},
},
getterExpectHeaders: map[int]map[string]string{
1: {"Range": "bytes=12-"},
2: {"Range": "bytes=20-"},
},
readerResps: map[int]readResp{
3: {
read: 0,
err: io.ErrUnexpectedEOF,
},
6: {
read: 0,
err: syscall.ECONNRESET,
},
},
expectData: data,
},
{
name: "ShortReadWithError NoRangeSupport",
readerResps: map[int]readResp{
@ -450,7 +481,7 @@ func (suite *ResetRetryHandlerUnitSuite) TestResetRetryHandler() {
resData = make([]byte, len(data))
)
rrh, err := readers.NewResetRetryHandler(ctx, getter)
rrh, err := NewResetRetryHandler(ctx, getter)
require.NoError(t, err, "making reader wrapper: %v", clues.ToCore(err))
for err == nil && offset < len(data) {
@ -475,3 +506,65 @@ func (suite *ResetRetryHandlerUnitSuite) TestResetRetryHandler() {
})
}
}
func (suite *ResetRetryHandlerUnitSuite) TestIsRetriable() {
table := []struct {
name string
err error
expect bool
}{
{
name: "nil",
err: nil,
expect: false,
},
{
name: "Connection Reset Error",
err: syscall.ECONNRESET,
expect: true,
},
{
name: "Unexpected EOF Error",
err: io.ErrUnexpectedEOF,
expect: true,
},
{
name: "Not Retriable Error",
err: assert.AnError,
expect: false,
},
{
name: "Chained Errors With No Retriables",
err: clues.Stack(assert.AnError, clues.New("another error")),
expect: false,
},
{
name: "Chained Errors With ECONNRESET",
err: clues.Stack(assert.AnError, syscall.ECONNRESET, assert.AnError),
expect: true,
},
{
name: "Chained Errors With ErrUnexpectedEOF",
err: clues.Stack(assert.AnError, io.ErrUnexpectedEOF, assert.AnError),
expect: true,
},
{
name: "Wrapped ECONNRESET Error",
err: clues.Wrap(syscall.ECONNRESET, "wrapped error"),
expect: true,
},
{
name: "Wrapped ErrUnexpectedEOF Error",
err: clues.Wrap(io.ErrUnexpectedEOF, "wrapped error"),
expect: true,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
assert.Equal(t, test.expect, isRetriable(test.err))
})
}
}