Compare commits

...

4 Commits

Author SHA1 Message Date
Abhishek Pandey
535139a324 Add test 2024-01-07 23:13:39 -08:00
Abhishek Pandey
0069222f74 Retry 400 from resp 2024-01-05 19:25:04 -08:00
Abhishek Pandey
f021a65ebd 400 experiment setup 2023-12-29 17:26:46 -08:00
Abhishek Pandey
f9bf9bc1aa Add 400 retry 2023-12-29 17:18:11 -08:00
4 changed files with 80 additions and 19 deletions

View File

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/alcionai/clues" "github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/spatialcurrent/go-lazy/pkg/lazy" "github.com/spatialcurrent/go-lazy/pkg/lazy"
"github.com/alcionai/corso/src/internal/common/idname" "github.com/alcionai/corso/src/internal/common/idname"
@ -386,11 +387,22 @@ func downloadContent(
logger.CtxErr(ctx, err).Debug("url cache miss: refetching from API") logger.CtxErr(ctx, err).Debug("url cache miss: refetching from API")
counter.Inc(count.URLCacheMiss) counter.Inc(count.URLCacheMiss)
// di, err := iaag.GetItem(ctx, driveID, ptr.Val(item.GetId()))
// if err != nil {
// return nil, clues.Wrap(err, "retrieving expired item")
// }
var di models.DriveItemable
for i := 0; i < 1000000; i++ {
di, err := iaag.GetItem(ctx, driveID, ptr.Val(item.GetId())) di, err := iaag.GetItem(ctx, driveID, ptr.Val(item.GetId()))
if err != nil { if err != nil {
return nil, clues.Wrap(err, "retrieving expired item") return nil, clues.Wrap(err, "retrieving expired item")
} }
logger.Ctx(ctx).Debugf("iteration %d item id %s\n ", i, ptr.Val(di.GetId()))
}
cdi := custom.ToCustomDriveItem(di) cdi := custom.ToCustomDriveItem(di)
content, err = downloadItem(ctx, iaag, cdi) content, err = downloadItem(ctx, iaag, cdi)

View File

@ -53,6 +53,7 @@ const (
// the same name as another folder in the same parent. Such duplicate folder // the same name as another folder in the same parent. Such duplicate folder
// names are not allowed by graph. // names are not allowed by graph.
folderExists errorCode = "ErrorFolderExists" folderExists errorCode = "ErrorFolderExists"
invalidRequest errorCode = "invalidRequest"
// Some datacenters are returning this when we try to get the inbox of a user // Some datacenters are returning this when we try to get the inbox of a user
// that doesn't exist. // that doesn't exist.
invalidUser errorCode = "ErrorInvalidUser" invalidUser errorCode = "ErrorInvalidUser"
@ -140,6 +141,14 @@ var (
ErrTokenExpired = clues.New("jwt token expired") ErrTokenExpired = clues.New("jwt token expired")
) )
// IsErrInvalidRequest is to retry transient graph 400 errors with odata error code
// set to invalidRequest.
func IsErrInvalidRequest(err error, resp *http.Response) bool {
return resp != nil &&
resp.StatusCode == http.StatusBadRequest &&
parseODataErr(err).hasErrorCode(err, invalidRequest)
}
func IsErrApplicationThrottled(err error) bool { func IsErrApplicationThrottled(err error) bool {
return errors.Is(err, ErrApplicationThrottled) || return errors.Is(err, ErrApplicationThrottled) ||
parseODataErr(err).hasErrorCode(err, applicationThrottled) parseODataErr(err).hasErrorCode(err, applicationThrottled)

View File

@ -206,6 +206,7 @@ func (mw RetryMiddleware) retryRequest(
// 1, there was a prior error OR the status code match retriable conditions. // 1, there was a prior error OR the status code match retriable conditions.
// 3, the request method is retriable. // 3, the request method is retriable.
// 4, we haven't already hit maximum retries. // 4, we haven't already hit maximum retries.
shouldRetry := (priorErr != nil || mw.isRetriableRespCode(ctx, resp)) && shouldRetry := (priorErr != nil || mw.isRetriableRespCode(ctx, resp)) &&
mw.isRetriableRequest(req) && mw.isRetriableRequest(req) &&
executionCount < mw.MaxRetries executionCount < mw.MaxRetries
@ -214,6 +215,10 @@ func (mw RetryMiddleware) retryRequest(
return resp, stackReq(ctx, req, resp, priorErr).OrNil() return resp, stackReq(ctx, req, resp, priorErr).OrNil()
} }
if resp != nil && resp.StatusCode == http.StatusBadRequest {
logger.Ctx(ctx).Info("retrying 400_invalid_request")
}
executionCount++ executionCount++
delay := mw.getRetryDelay(req, resp, exponentialBackoff) delay := mw.getRetryDelay(req, resp, exponentialBackoff)
@ -267,6 +272,10 @@ var retryableRespCodes = []int{
http.StatusBadGateway, http.StatusBadGateway,
} }
const (
invalidRequestMsg = "invalidRequest"
)
func (mw RetryMiddleware) isRetriableRespCode(ctx context.Context, resp *http.Response) bool { func (mw RetryMiddleware) isRetriableRespCode(ctx context.Context, resp *http.Response) bool {
if resp == nil { if resp == nil {
return false return false
@ -284,9 +293,16 @@ func (mw RetryMiddleware) isRetriableRespCode(ctx context.Context, resp *http.Re
// not a status code, but the message itself might indicate a connectivity issue that // not a status code, but the message itself might indicate a connectivity issue that
// can be retried independent of the status code. // can be retried independent of the status code.
return strings.Contains( first := strings.Contains(
strings.ToLower(getRespDump(ctx, resp, true)), strings.ToLower(getRespDump(ctx, resp, true)),
strings.ToLower(string(IOErrDuringRead))) strings.ToLower(string(IOErrDuringRead)))
second := resp.StatusCode == http.StatusBadRequest &&
strings.Contains(
strings.ToLower(getRespDump(ctx, resp, true)),
strings.ToLower(invalidRequestMsg))
return first || second
} }
func (mw RetryMiddleware) isRetriableRequest(req *http.Request) bool { func (mw RetryMiddleware) isRetriableRequest(req *http.Request) bool {

View File

@ -15,7 +15,6 @@ import (
msgraphsdkgo "github.com/microsoftgraph/msgraph-sdk-go" msgraphsdkgo "github.com/microsoftgraph/msgraph-sdk-go"
msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core" msgraphgocore "github.com/microsoftgraph/msgraph-sdk-go-core"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/users"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
@ -44,9 +43,14 @@ func newMWReturns(code int, body []byte, err error) mwReturns {
resp := &http.Response{ resp := &http.Response{
StatusCode: code, StatusCode: code,
//Status: "",
Body: brc, Body: brc,
} }
resp.Header = make(http.Header)
resp.Header.Add("Content-Type", "application/json")
if code == 0 { if code == 0 {
resp = nil resp = nil
} }
@ -154,9 +158,22 @@ func (suite *RetryMWIntgSuite) SetupSuite() {
func (suite *RetryMWIntgSuite) TestRetryMiddleware_Intercept_byStatusCode() { func (suite *RetryMWIntgSuite) TestRetryMiddleware_Intercept_byStatusCode() {
var ( var (
uri = "https://graph.microsoft.com" //uri = "https://graph.microsoft.com"
urlPath = "/v1.0/users/user/messages/foo" //urlPath = "/v1.0/users/"
url = uri + urlPath //url = uri + urlPath
//1623c35a-b67a-473a-9b21-5e4891f22e70
jsonData = map[string]interface{}{
"error": map[string]interface{}{
"code": "invalidRequest",
"message": "Invalid request",
"innerError": map[string]interface{}{
"date": "mmddyy",
"request-id": "rid",
"client-request-id": "cid",
},
},
}
) )
tests := []struct { tests := []struct {
@ -167,13 +184,13 @@ func (suite *RetryMWIntgSuite) TestRetryMiddleware_Intercept_byStatusCode() {
mw testMW mw testMW
expectErr assert.ErrorAssertionFunc expectErr assert.ErrorAssertionFunc
}{ }{
{ // {
name: "200, no retries", // name: "200, no retries",
status: http.StatusOK, // status: http.StatusOK,
providedErr: nil, // providedErr: nil,
expectRetryCount: 0, // expectRetryCount: 0,
expectErr: assert.NoError, // expectErr: assert.NoError,
}, // },
{ {
name: "400, no retries", name: "400, no retries",
status: http.StatusBadRequest, status: http.StatusBadRequest,
@ -252,10 +269,13 @@ func (suite *RetryMWIntgSuite) TestRetryMiddleware_Intercept_byStatusCode() {
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
defer flush() defer flush()
json_string, err := json.Marshal(jsonData)
require.NoError(t, err, clues.ToCore(err))
called := 0 called := 0
mw := newTestMW( mw := newTestMW(
func(*http.Request) { called++ }, func(*http.Request) { called++ },
newMWReturns(test.status, nil, test.providedErr)) newMWReturns(test.status, json_string, test.providedErr))
mw.repeatReturn0 = true mw.repeatReturn0 = true
// Add a large timeout of 100 seconds to ensure that the ctx deadline // Add a large timeout of 100 seconds to ensure that the ctx deadline
@ -272,7 +292,11 @@ func (suite *RetryMWIntgSuite) TestRetryMiddleware_Intercept_byStatusCode() {
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
// url doesn't fit the builder, but that shouldn't matter // url doesn't fit the builder, but that shouldn't matter
_, err = users.NewCountRequestBuilder(url, adpt).Get(ctx, nil) //_, err = users.NewCountRequestBuilder(url, adpt).Get(ctx, nil)
//Users().Get(context.Background(), nil)
_, err = NewService(adpt).Client().Users().Get(ctx, nil)
test.expectErr(t, err, clues.ToCore(err)) test.expectErr(t, err, clues.ToCore(err))
// -1 because the non-retried call always counts for one, then // -1 because the non-retried call always counts for one, then