@@ -16,8 +16,8 @@ import (
1616 "github.com/aws/smithy-go/transport/http"
1717)
1818
19- // RequestCloner is a function that can take an input request type and clone the request
20- // for use in a subsequent retry attempt
19+ // RequestCloner is a function that can take an input request type and clone
20+ // the request for use in a subsequent retry attempt.
2121type RequestCloner func (interface {}) interface {}
2222
2323type retryMetadata struct {
@@ -27,11 +27,12 @@ type retryMetadata struct {
2727 AttemptClockSkew time.Duration
2828}
2929
30- // Attempt is a Smithy FinalizeMiddleware that handles retry attempts using the provided
31- // Retryer implementation
30+ // Attempt is a Smithy Finalize middleware that handles retry attempts using
31+ // the provided Retryer implementation.
3232type Attempt struct {
33- // Enable the logging of retry attempts performed by the SDK.
34- // This will include logging retry attempts, unretryable errors, and when max attempts are reached.
33+ // Enable the logging of retry attempts performed by the SDK. This will
34+ // include logging retry attempts, unretryable errors, and when max
35+ // attempts are reached.
3536 LogAttempts bool
3637
3738 retryer aws.Retryer
@@ -59,21 +60,24 @@ func (r Attempt) logf(logger logging.Logger, classification logging.Classificati
5960 logger .Logf (classification , format , v ... )
6061}
6162
62- // HandleFinalize utilizes the provider Retryer implementation to attempt retries over the next handler
63- func (r Attempt ) HandleFinalize (ctx context.Context , in smithymiddle.FinalizeInput , next smithymiddle.FinalizeHandler ) (
63+ // HandleFinalize utilizes the provider Retryer implementation to attempt
64+ // retries over the next handler
65+ func (r * Attempt ) HandleFinalize (ctx context.Context , in smithymiddle.FinalizeInput , next smithymiddle.FinalizeHandler ) (
6466 out smithymiddle.FinalizeOutput , metadata smithymiddle.Metadata , err error ,
6567) {
6668 var attemptNum int
6769 var attemptClockSkew time.Duration
6870 var attemptResults AttemptResults
6971
7072 maxAttempts := r .retryer .MaxAttempts ()
73+ releaseRetryToken := nopRelease
7174
7275 for {
7376 attemptNum ++
7477 attemptInput := in
7578 attemptInput .Request = r .requestCloner (attemptInput .Request )
7679
80+ // Record the metadata for the for attempt being started.
7781 attemptCtx := setRetryMetadata (ctx , retryMetadata {
7882 AttemptNum : attemptNum ,
7983 AttemptTime : sdk .NowTime ().UTC (),
@@ -82,23 +86,20 @@ func (r Attempt) HandleFinalize(ctx context.Context, in smithymiddle.FinalizeInp
8286 })
8387
8488 var attemptResult AttemptResult
89+ out , attemptResult , releaseRetryToken , err = r .handleAttempt (attemptCtx , attemptInput , releaseRetryToken , next )
90+ attemptClockSkew , _ = awsmiddle .GetAttemptSkew (attemptResult .ResponseMetadata )
8591
86- out , attemptResult , err = r .handleAttempt (attemptCtx , attemptInput , next )
87-
88- var ok bool
89- attemptClockSkew , ok = awsmiddle .GetAttemptSkew (attemptResult .ResponseMetadata )
90- if ! ok {
91- attemptClockSkew = 0
92- }
93-
92+ // AttempResult Retried states that the attempt was not successful, and
93+ // should be retried.
9494 shouldRetry := attemptResult .Retried
9595
96- // add attempt metadata to list of all attempt metadata
96+ // Add attempt metadata to list of all attempt metadata
9797 attemptResults .Results = append (attemptResults .Results , attemptResult )
9898
9999 if ! shouldRetry {
100100 // Ensure the last response's metadata is used as the bases for result
101- // metadata returned by the stack.
101+ // metadata returned by the stack. The Slice of attempt results
102+ // will be added to this cloned metadata.
102103 metadata = attemptResult .ResponseMetadata .Clone ()
103104
104105 break
@@ -110,26 +111,36 @@ func (r Attempt) HandleFinalize(ctx context.Context, in smithymiddle.FinalizeInp
110111}
111112
112113// handleAttempt handles an individual request attempt.
113- func (r Attempt ) handleAttempt (ctx context.Context , in smithymiddle.FinalizeInput , next smithymiddle.FinalizeHandler ) (
114- out smithymiddle.FinalizeOutput , attemptResult AttemptResult , err error ,
114+ func (r * Attempt ) handleAttempt (
115+ ctx context.Context , in smithymiddle.FinalizeInput , releaseRetryToken func (error ) error , next smithymiddle.FinalizeHandler ,
116+ ) (
117+ out smithymiddle.FinalizeOutput , attemptResult AttemptResult , _ func (error ) error , err error ,
115118) {
116119 defer func () {
117120 attemptResult .Err = err
118121 }()
119122
120- relRetryToken := r .retryer .GetInitialToken ()
123+ //------------------------------
124+ // Get Initial (aka Send) Token
125+ //------------------------------
126+ releaseInitialToken := r .retryer .GetInitialToken ()
127+
128+ //------------------------------
129+ // Send Attempt
130+ //------------------------------
121131 logger := smithymiddle .GetLogger (ctx )
122132 service , operation := awsmiddle .GetServiceID (ctx ), awsmiddle .GetOperationName (ctx )
123-
124133 retryMetadata , _ := getRetryMetadata (ctx )
125134 attemptNum := retryMetadata .AttemptNum
126135 maxAttempts := retryMetadata .MaxAttempts
127136
137+ // Following attempts must ensure the request payload stream starts in a
138+ // rewound state.
128139 if attemptNum > 1 {
129140 if rewindable , ok := in .Request .(interface { RewindStream () error }); ok {
130141 if rewindErr := rewindable .RewindStream (); rewindErr != nil {
131142 err = fmt .Errorf ("failed to rewind transport stream for retry, %w" , rewindErr )
132- return out , attemptResult , err
143+ return out , attemptResult , nopRelease , err
133144 }
134145 }
135146
@@ -140,51 +151,78 @@ func (r Attempt) handleAttempt(ctx context.Context, in smithymiddle.FinalizeInpu
140151 out , metadata , err = next .HandleFinalize (ctx , in )
141152 attemptResult .ResponseMetadata = metadata
142153
143- if releaseError := relRetryToken (err ); releaseError != nil && err != nil {
144- err = fmt .Errorf ("failed to release token after request error, %w" , err )
145- return out , attemptResult , err
154+ //------------------------------
155+ // Bookkeeping
156+ //------------------------------
157+ // Release the initial send token based on the state of the attempt's error (if any).
158+ if releaseError := releaseInitialToken (err ); releaseError != nil && err != nil {
159+ err = fmt .Errorf ("failed to release initial token after request error, %w" , err )
160+ return out , attemptResult , nopRelease , err
146161 }
147-
162+ // Release the retry token based on the state of the attempt's error (if any).
163+ if releaseError := releaseRetryToken (err ); releaseError != nil && err != nil {
164+ err = fmt .Errorf ("failed to release retry token after request error, %w" , err )
165+ return out , attemptResult , nopRelease , err
166+ }
167+ // If there was no error making the attempt, nothing further to do. There
168+ // will be nothing to retry.
148169 if err == nil {
149- return out , attemptResult , err
170+ return out , attemptResult , nopRelease , err
150171 }
151172
173+ //------------------------------
174+ // Is Retryable and Should Retry
175+ //------------------------------
176+ // If the attempt failed with an unretryable error, nothing further to do
177+ // but return, and inform the caller about the terminal failure.
152178 retryable := r .retryer .IsErrorRetryable (err )
153179 if ! retryable {
154180 r .logf (logger , logging .Debug , "request failed with unretryable error %v" , err )
155- return out , attemptResult , err
181+ return out , attemptResult , nopRelease , err
156182 }
157183
158184 // set retryable to true
159185 attemptResult .Retryable = true
160186
187+ // Once the maximum number of attempts have been exhausted there is nothing
188+ // further to do other than inform the caller about the terminal failure.
161189 if maxAttempts > 0 && attemptNum >= maxAttempts {
162190 r .logf (logger , logging .Debug , "max retry attempts exhausted, max %d" , maxAttempts )
163191 err = & MaxAttemptsError {
164192 Attempt : attemptNum ,
165193 Err : err ,
166194 }
167- return out , attemptResult , err
195+ return out , attemptResult , nopRelease , err
168196 }
169197
170- relRetryToken , reqErr := r .retryer .GetRetryToken (ctx , err )
171- if reqErr != nil {
172- return out , attemptResult , reqErr
198+ //------------------------------
199+ // Get Retry (aka Retry Quota) Token
200+ //------------------------------
201+ // Get a retry token that will be released after the
202+ releaseRetryToken , retryTokenErr := r .retryer .GetRetryToken (ctx , err )
203+ if retryTokenErr != nil {
204+ return out , attemptResult , nopRelease , retryTokenErr
173205 }
174206
207+ //------------------------------
208+ // Retry Delay and Sleep
209+ //------------------------------
210+ // Get the retry delay before another attempt can be made, and sleep for
211+ // that time. Potentially early exist if the sleep is canceled via the
212+ // context.
175213 retryDelay , reqErr := r .retryer .RetryDelay (attemptNum , err )
176214 if reqErr != nil {
177- return out , attemptResult , reqErr
215+ return out , attemptResult , releaseRetryToken , reqErr
178216 }
179-
180217 if reqErr = sdk .SleepWithContext (ctx , retryDelay ); reqErr != nil {
181218 err = & aws.RequestCanceledError {Err : reqErr }
182- return out , attemptResult , err
219+ return out , attemptResult , releaseRetryToken , err
183220 }
184221
222+ // The request should be re-attempted.
185223 attemptResult .Retried = true
186224
187- return out , attemptResult , err
225+ return out , attemptResult , releaseRetryToken , err
188226}
189227
190228// MetricsHeader attaches SDK request metric header for retries to the transport
@@ -195,7 +233,7 @@ func (r *MetricsHeader) ID() string {
195233 return "RetryMetricsHeader"
196234}
197235
198- // HandleFinalize attaches the sdk request metric header to the transport layer
236+ // HandleFinalize attaches the SDK request metric header to the transport layer
199237func (r MetricsHeader ) HandleFinalize (ctx context.Context , in smithymiddle.FinalizeInput , next smithymiddle.FinalizeHandler ) (
200238 out smithymiddle.FinalizeOutput , metadata smithymiddle.Metadata , err error ,
201239) {
@@ -251,13 +289,14 @@ func setRetryMetadata(ctx context.Context, metadata retryMetadata) context.Conte
251289 return middleware .WithStackValue (ctx , retryMetadataKey {}, metadata )
252290}
253291
254- // AddRetryMiddlewaresOptions is the set of options that can be passed to AddRetryMiddlewares for configuring retry
255- // associated middleware.
292+ // AddRetryMiddlewaresOptions is the set of options that can be passed to
293+ // AddRetryMiddlewares for configuring retry associated middleware.
256294type AddRetryMiddlewaresOptions struct {
257295 Retryer aws.Retryer
258296
259- // Enable the logging of retry attempts performed by the SDK.
260- // This will include logging retry attempts, unretryable errors, and when max attempts are reached.
297+ // Enable the logging of retry attempts performed by the SDK. This will
298+ // include logging retry attempts, unretryable errors, and when max
299+ // attempts are reached.
261300 LogRetryAttempts bool
262301}
263302
0 commit comments