@@ -233,9 +233,11 @@ func (r *Attempt) handleAttempt(
233233 "failed to release retry token after request error, %w" , err )
234234 }
235235 // Release the attempt token based on the state of the attempt's error (if any).
236- if releaseError := releaseAttemptToken (err ); releaseError != nil && err != nil {
237- return out , attemptResult , nopRelease , fmt .Errorf (
238- "failed to release initial token after request error, %w" , err )
236+ if ! newRetries2026 () || attemptNum == 1 {
237+ if releaseError := releaseAttemptToken (err ); releaseError != nil && err != nil {
238+ return out , attemptResult , nopRelease , fmt .Errorf (
239+ "failed to release initial token after request error, %w" , err )
240+ }
239241 }
240242 // If there was no error making the attempt, nothing further to do. There
241243 // will be nothing to retry.
@@ -276,6 +278,13 @@ func (r *Attempt) handleAttempt(
276278 // Get a retry token that will be released after the
277279 releaseRetryToken , retryTokenErr := r .retryer .GetRetryToken (ctx , err )
278280 if retryTokenErr != nil {
281+ // Long-polling operations must still back off when quota is exceeded.
282+ if newRetries2026 () && internalcontext .GetIsLongPolling (ctx ) {
283+ if retryDelay , delayErr := r .retryer .RetryDelay (attemptNum - 1 , err ); delayErr == nil {
284+ retryDelay = adjustForRetryAfterHeader (retryDelay , err , logger , r .LogAttempts )
285+ _ = sdk .SleepWithContext (ctx , retryDelay )
286+ }
287+ }
279288 return out , attemptResult , nopRelease , errors .Join (err , retryTokenErr )
280289 }
281290
@@ -285,10 +294,17 @@ func (r *Attempt) handleAttempt(
285294 // Get the retry delay before another attempt can be made, and sleep for
286295 // that time. Potentially early exist if the sleep is canceled via the
287296 // context.
288- retryDelay , reqErr := r .retryer .RetryDelay (attemptNum , err )
297+ attempt := attemptNum
298+ if newRetries2026 () {
299+ attempt = attemptNum - 1
300+ }
301+ retryDelay , reqErr := r .retryer .RetryDelay (attempt , err )
289302 if reqErr != nil {
290303 return out , attemptResult , releaseRetryToken , reqErr
291304 }
305+ if newRetries2026 () {
306+ retryDelay = adjustForRetryAfterHeader (retryDelay , err , logger , r .LogAttempts )
307+ }
292308 if reqErr = sdk .SleepWithContext (ctx , retryDelay ); reqErr != nil {
293309 err = & aws.RequestCanceledError {Err : reqErr }
294310 return out , attemptResult , releaseRetryToken , err
@@ -423,6 +439,43 @@ func AddRetryMiddlewares(stack *smithymiddle.Stack, options AddRetryMiddlewaresO
423439 return nil
424440}
425441
442+ // adjustForRetryAfterHeader checks for the x-amz-retry-after response header
443+ // and clamps the backoff duration accordingly. The header value is an integer
444+ // representing milliseconds. The result is clamped to [t_i, 5s + t_i] where
445+ // t_i is the jittered exponential backoff duration. Invalid header values are
446+ // ignored.
447+ func adjustForRetryAfterHeader (backoff time.Duration , err error , logger logging.Logger , logAttempts bool ) time.Duration {
448+ var re * http.ResponseError
449+ if ! errors .As (err , & re ) || re .Response == nil || re .Response .Response == nil {
450+ return backoff
451+ }
452+
453+ headerVal := re .Response .Header .Get ("X-Amz-Retry-After" )
454+ if headerVal == "" {
455+ return backoff
456+ }
457+
458+ ms , parseErr := strconv .ParseInt (headerVal , 10 , 64 )
459+ if parseErr != nil || ms < 0 {
460+ if logAttempts {
461+ logger .Logf (logging .Debug , "ignoring invalid x-amz-retry-after header value %q" , headerVal )
462+ }
463+ return backoff
464+ }
465+
466+ retryAfter := time .Duration (ms ) * time .Millisecond
467+ minDuration := backoff
468+ maxDuration := 5 * time .Second + backoff
469+
470+ if retryAfter < minDuration {
471+ return minDuration
472+ }
473+ if retryAfter > maxDuration {
474+ return maxDuration
475+ }
476+ return retryAfter
477+ }
478+
426479// Determines the value of exception.type for metrics purposes. We prefer an
427480// API-specific error code, otherwise it's just the Go type for the value.
428481func errorType (err error ) string {
0 commit comments