From 09ef350bc0dc289abc1219e7a3cd98544813363f Mon Sep 17 00:00:00 2001 From: Keepers Date: Wed, 19 Apr 2023 12:51:29 -0600 Subject: [PATCH] retry on "connection reset by peer" (#3138) retry when we receive a "connection reset by peer" error --- #### Does this PR need a docs update or release note? - [x] :white_check_mark: Yes, it's included #### Type of change - [x] :bug: Bugfix #### Issue(s) * #3129 #### Test Plan - [x] :muscle: Manual - [x] :zap: Unit test --- CHANGELOG.md | 1 + src/internal/connector/graph/errors.go | 5 ++++ src/internal/connector/graph/errors_test.go | 30 +++++++++++++++++++++ src/internal/connector/graph/middleware.go | 2 +- 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c220b5a9..14637004d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Skip OneNote items bigger than 2GB (Graph API prevents us from downloading them) - ParentPath of json output for Exchange calendar now shows names instead of IDs. - Fixed failure when downloading huge amount of attachments +- Graph API requests that return an ECONNRESET error are now retried. ### Known Issues - Restoring a OneDrive or SharePoint file with the same name as a file with that name as its M365 ID may restore both items. diff --git a/src/internal/connector/graph/errors.go b/src/internal/connector/graph/errors.go index 0df4c26f0..b175457eb 100644 --- a/src/internal/connector/graph/errors.go +++ b/src/internal/connector/graph/errors.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "strings" + "syscall" "github.com/alcionai/clues" "github.com/microsoftgraph/msgraph-sdk-go/models" @@ -116,6 +117,10 @@ func IsErrTimeout(err error) bool { os.IsTimeout(err) } +func IsErrConnectionReset(err error) bool { + return errors.Is(err, syscall.ECONNRESET) +} + func IsErrUnauthorized(err error) bool { // TODO: refine this investigation. We don't currently know if // a specific item download url expired, or if the full connection diff --git a/src/internal/connector/graph/errors_test.go b/src/internal/connector/graph/errors_test.go index 0ba535ce9..f6f5788da 100644 --- a/src/internal/connector/graph/errors_test.go +++ b/src/internal/connector/graph/errors_test.go @@ -3,6 +3,7 @@ package graph import ( "context" "net/http" + "syscall" "testing" "github.com/alcionai/clues" @@ -32,6 +33,35 @@ func odErr(code string) *odataerrors.ODataError { return odErr } +func (suite *GraphErrorsUnitSuite) TestIsErrConnectionReset() { + table := []struct { + name string + err error + expect assert.BoolAssertionFunc + }{ + { + name: "nil", + err: nil, + expect: assert.False, + }, + { + name: "non-matching", + err: assert.AnError, + expect: assert.False, + }, + { + name: "matching", + err: syscall.ECONNRESET, + expect: assert.True, + }, + } + for _, test := range table { + suite.Run(test.name, func() { + test.expect(suite.T(), IsErrConnectionReset(test.err)) + }) + } +} + func (suite *GraphErrorsUnitSuite) TestIsErrDeletedInFlight() { table := []struct { name string diff --git a/src/internal/connector/graph/middleware.go b/src/internal/connector/graph/middleware.go index 4b3e7e96e..bedfbd932 100644 --- a/src/internal/connector/graph/middleware.go +++ b/src/internal/connector/graph/middleware.go @@ -213,7 +213,7 @@ func (middleware RetryHandler) retryRequest( } response, err := pipeline.Next(req, middlewareIndex) - if err != nil && !IsErrTimeout(err) { + if err != nil && !IsErrTimeout(err) && !IsErrConnectionReset(err) { return response, Stack(ctx, err).With("retry_count", executionCount) }