Skip to content

Commit dd0d9b4

Browse files
authored
fix(core): retry after clock skew correction (#1170)
* fix(core): retry after clock skew correction * fix: variable rename * fix: make clock skew corrected errors transient * fix types and formatting * unit test update * move system clock offset check to AWS SDK * formatting
1 parent 3b37740 commit dd0d9b4

File tree

8 files changed

+55
-4
lines changed

8 files changed

+55
-4
lines changed

.changeset/giant-pianos-lie.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@smithy/service-error-classification": patch
3+
"@smithy/middleware-retry": patch
4+
"@smithy/types": patch
5+
---
6+
7+
make clock skew correcting errors transient

packages/middleware-retry/src/configurations.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,20 @@ export interface RetryInputConfig {
4848
retryStrategy?: RetryStrategy | RetryStrategyV2;
4949
}
5050

51-
interface PreviouslyResolved {
51+
/**
52+
* @internal
53+
*/
54+
export interface PreviouslyResolved {
5255
/**
5356
* Specifies provider for retry algorithm to use.
5457
* @internal
5558
*/
5659
retryMode: string | Provider<string>;
5760
}
5861

62+
/**
63+
* @internal
64+
*/
5965
export interface RetryResolvedConfig {
6066
/**
6167
* Resolved value for input config {@link RetryInputConfig.maxAttempts}

packages/middleware-retry/src/retryDecider.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import {
66
} from "@smithy/service-error-classification";
77
import { SdkError } from "@smithy/types";
88

9+
/**
10+
* @deprecated this is only used in the deprecated StandardRetryStrategy. Do not use in new code.
11+
*/
912
export const defaultRetryDecider = (error: SdkError) => {
1013
if (!error) {
1114
return false;

packages/middleware-retry/src/retryMiddleware.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ describe(retryMiddleware.name, () => {
138138
(isThrottlingError as jest.Mock).mockReturnValue(true);
139139
const next = jest.fn().mockRejectedValue(requestError);
140140
const errorInfo = {
141+
error: requestError,
141142
errorType: "THROTTLING",
142143
};
143144
const mockRetryStrategy = {
@@ -172,6 +173,7 @@ describe(retryMiddleware.name, () => {
172173
(isThrottlingError as jest.Mock).mockReturnValue(true);
173174
const next = jest.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce(mockSuccess);
174175
const errorInfo = {
176+
error: mockError,
175177
errorType: "THROTTLING",
176178
};
177179
const { response, output } = await retryMiddleware({
@@ -193,6 +195,7 @@ describe(retryMiddleware.name, () => {
193195
(isThrottlingError as jest.Mock).mockReturnValue(false);
194196
const next = jest.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce(mockSuccess);
195197
const errorInfo = {
198+
error: mockError,
196199
errorType: "TRANSIENT",
197200
};
198201
const { response, output } = await retryMiddleware({
@@ -215,6 +218,7 @@ describe(retryMiddleware.name, () => {
215218
(isThrottlingError as jest.Mock).mockReturnValue(false);
216219
const next = jest.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce(mockSuccess);
217220
const errorInfo = {
221+
error: mockError,
218222
errorType: "SERVER_ERROR",
219223
};
220224
const { response, output } = await retryMiddleware({
@@ -237,6 +241,7 @@ describe(retryMiddleware.name, () => {
237241
(isThrottlingError as jest.Mock).mockReturnValue(false);
238242
const next = jest.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce(mockSuccess);
239243
const errorInfo = {
244+
error: mockError,
240245
errorType: "CLIENT_ERROR",
241246
};
242247
const { response, output } = await retryMiddleware({
@@ -265,6 +270,7 @@ describe(retryMiddleware.name, () => {
265270
});
266271
const next = jest.fn().mockRejectedValueOnce(mockError).mockResolvedValueOnce(mockSuccess);
267272
const errorInfo = {
273+
error: mockError,
268274
errorType: "CLIENT_ERROR",
269275
};
270276
const { response, output } = await retryMiddleware({
@@ -289,6 +295,7 @@ describe(retryMiddleware.name, () => {
289295
const { isInstance } = HttpResponse;
290296
((isInstance as unknown) as jest.Mock).mockReturnValue(true);
291297
const errorInfo = {
298+
error: mockError,
292299
errorType: "CLIENT_ERROR",
293300
retryAfterHint: retryAfterDate,
294301
};

packages/middleware-retry/src/retryMiddleware.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const retryMiddleware = (options: RetryResolvedConfig) => <Output extends
5555
output.$metadata.attempts = attempts + 1;
5656
output.$metadata.totalRetryDelay = totalRetryDelay;
5757
return { response, output };
58-
} catch (e) {
58+
} catch (e: any) {
5959
const retryErrorInfo = getRetryErrorInfo(e);
6060
lastError = asSdkError(e);
6161

@@ -97,6 +97,7 @@ const isRetryStrategyV2 = (retryStrategy: RetryStrategy | RetryStrategyV2) =>
9797

9898
const getRetryErrorInfo = (error: SdkError): RetryErrorInfo => {
9999
const errorInfo: RetryErrorInfo = {
100+
error,
100101
errorType: getRetryErrorType(error),
101102
};
102103
const retryAfterHint = getRetryAfterHint(error.$response);

packages/service-error-classification/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,16 @@ import {
1010

1111
export const isRetryableByTrait = (error: SdkError) => error.$retryable !== undefined;
1212

13+
/**
14+
* @deprecated use isClockSkewCorrectedError. This is only used in deprecated code.
15+
*/
1316
export const isClockSkewError = (error: SdkError) => CLOCK_SKEW_ERROR_CODES.includes(error.name);
1417

18+
/**
19+
* @returns whether the error resulted in a systemClockOffset aka clock skew correction.
20+
*/
21+
export const isClockSkewCorrectedError = (error: SdkError) => error.$metadata?.clockSkewCorrected;
22+
1523
export const isThrottlingError = (error: SdkError) =>
1624
error.$metadata?.httpStatusCode === 429 ||
1725
THROTTLING_ERROR_CODES.includes(error.name) ||
@@ -24,6 +32,7 @@ export const isThrottlingError = (error: SdkError) =>
2432
* the name "TimeoutError" to be checked by the TRANSIENT_ERROR_CODES condition.
2533
*/
2634
export const isTransientError = (error: SdkError) =>
35+
isClockSkewCorrectedError(error) ||
2736
TRANSIENT_ERROR_CODES.includes(error.name) ||
2837
NODEJS_TIMEOUT_ERROR_CODES.includes((error as { code?: string })?.code || "") ||
2938
TRANSIENT_ERROR_STATUS_CODES.includes(error.$metadata?.httpStatusCode || 0);

packages/types/src/retry.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { SdkError } from "./shapes";
2+
13
/**
24
* @public
35
*/
@@ -33,14 +35,19 @@ export type RetryErrorType =
3335
* @public
3436
*/
3537
export interface RetryErrorInfo {
38+
/**
39+
* The error thrown during the initial request, if available.
40+
*/
41+
error?: SdkError;
42+
3643
errorType: RetryErrorType;
3744

3845
/**
3946
* Protocol hint. This could come from Http's 'retry-after' header or
4047
* something from MQTT or any other protocol that has the ability to convey
4148
* retry info from a peer.
4249
*
43-
* @returns the Date after which a retry should be attempted.
50+
* The Date after which a retry should be attempted.
4451
*/
4552
retryAfterHint?: Date;
4653
}

packages/types/src/shapes.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,15 @@ export interface SmithyException {
7979
* the base exception for the service should be used. Each client exports
8080
* a base ServiceException prefixed with the service name.
8181
*/
82-
export type SdkError = Error & Partial<SmithyException> & Partial<MetadataBearer>;
82+
export type SdkError = Error &
83+
Partial<SmithyException> &
84+
Partial<MetadataBearer> & {
85+
$metadata?: Partial<MetadataBearer>["$metadata"] & {
86+
/**
87+
* If present, will have value of true and indicates that the error resulted in a
88+
* correction of the clock skew, a.k.a. config.systemClockOffset.
89+
* This is specific to AWS SDK and sigv4.
90+
*/
91+
readonly clockSkewCorrected?: true;
92+
};
93+
};

0 commit comments

Comments
 (0)