Skip to content

Commit 3adba79

Browse files
committed
fix: make clock skew corrected errors transient
1 parent 68174c9 commit 3adba79

File tree

9 files changed

+68
-60
lines changed

9 files changed

+68
-60
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

.changeset/red-carrots-clap.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

.changeset/rude-apricots-remember.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

packages/core/src/middleware-http-signing/httpSigningMiddleware.ts

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,11 @@ const defaultSuccessHandler: SuccessHandler = (
3535
signingProperties: Record<string, unknown>
3636
): void => {};
3737

38-
/**
39-
* @internal
40-
*/
41-
interface HttpSigningMiddlewarePreviouslyResolved {
42-
systemClockOffset?: number;
43-
}
44-
4538
/**
4639
* @internal
4740
*/
4841
export const httpSigningMiddleware = <Input extends object, Output extends object>(
49-
config: HttpSigningMiddlewarePreviouslyResolved
42+
config: object
5043
): FinalizeRequestMiddleware<Input, Output> => (
5144
next: FinalizeHandler<Input, Output>,
5245
context: HttpSigningMiddlewareHandlerExecutionContext
@@ -67,35 +60,10 @@ export const httpSigningMiddleware = <Input extends object, Output extends objec
6760
identity,
6861
signer,
6962
} = scheme;
70-
const initialSystemClockOffset = config.systemClockOffset | 0;
71-
72-
const makeSignedRequest = async () =>
73-
next({
74-
...args,
75-
request: await signer.sign(args.request as HttpRequest, identity, signingProperties),
76-
});
77-
78-
const onError = (signer.errorHandler || defaultErrorHandler)(signingProperties);
79-
const onSuccess = signer.successHandler || defaultSuccessHandler;
80-
81-
const output = await makeSignedRequest().catch(async (error: unknown) => {
82-
let thrownError: unknown;
83-
try {
84-
onError(error as Error);
85-
} catch (e) {
86-
thrownError = e;
87-
}
88-
const latestSystemClockOffset = config.systemClockOffset | 0;
89-
const systemClockOffsetWasModified = initialSystemClockOffset !== latestSystemClockOffset;
90-
91-
if (systemClockOffsetWasModified) {
92-
return makeSignedRequest().catch(onError);
93-
}
94-
95-
if (thrownError) {
96-
throw thrownError;
97-
}
98-
});
99-
onSuccess(output.response, signingProperties);
63+
const output = await next({
64+
...args,
65+
request: await signer.sign(args.request, identity, signingProperties),
66+
}).catch((signer.errorHandler || defaultErrorHandler)(signingProperties));
67+
(signer.successHandler || defaultSuccessHandler)(output.response, signingProperties);
10068
return output;
10169
};

packages/middleware-retry/src/configurations.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,22 @@ 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>;
60+
61+
systemClockOffset?: number;
5762
}
5863

64+
/**
65+
* @internal
66+
*/
5967
export interface RetryResolvedConfig {
6068
/**
6169
* Resolved value for input config {@link RetryInputConfig.maxAttempts}

packages/middleware-retry/src/retryMiddleware.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ import {
1616
RetryStrategyV2,
1717
RetryToken,
1818
SdkError,
19+
SkdErrorWithClockSkewMetadata,
1920
} from "@smithy/types";
2021
import { INVOCATION_ID_HEADER, REQUEST_HEADER } from "@smithy/util-retry";
2122
import { v4 } from "uuid";
2223

23-
import { RetryResolvedConfig } from "./configurations";
24+
import { PreviouslyResolved, RetryResolvedConfig } from "./configurations";
2425
import { isStreamingPayload } from "./isStreamingPayload/isStreamingPayload";
2526
import { asSdkError } from "./util";
2627

27-
export const retryMiddleware = (options: RetryResolvedConfig) => <Output extends MetadataBearer = MetadataBearer>(
28+
export const retryMiddleware = (options: RetryResolvedConfig & Partial<PreviouslyResolved>) => <
29+
Output extends MetadataBearer = MetadataBearer
30+
>(
2831
next: FinalizeHandler<any, Output>,
2932
context: HandlerExecutionContext
3033
): FinalizeHandler<any, Output> => async (
@@ -45,19 +48,31 @@ export const retryMiddleware = (options: RetryResolvedConfig) => <Output extends
4548
if (isRequest) {
4649
request.headers[INVOCATION_ID_HEADER] = v4();
4750
}
51+
52+
let initialSystemClockOffset = 0;
53+
4854
while (true) {
4955
try {
5056
if (isRequest) {
5157
request.headers[REQUEST_HEADER] = `attempt=${attempts + 1}; max=${maxAttempts}`;
5258
}
59+
initialSystemClockOffset = options.systemClockOffset | 0;
5360
const { response, output } = await next(args);
5461
retryStrategy.recordSuccess(retryToken);
5562
output.$metadata.attempts = attempts + 1;
5663
output.$metadata.totalRetryDelay = totalRetryDelay;
5764
return { response, output };
58-
} catch (e) {
59-
const retryErrorInfo = getRetryErrorInfo(e);
60-
lastError = asSdkError(e);
65+
} catch (e: unknown) {
66+
const latestSystemClockOffset = options.systemClockOffset | 0;
67+
const clockSkewCorrected = initialSystemClockOffset !== latestSystemClockOffset;
68+
69+
const sdkError = e as SkdErrorWithClockSkewMetadata;
70+
if (clockSkewCorrected && sdkError.$metadata && typeof sdkError.$metadata === "object") {
71+
sdkError.$metadata.clockSkewCorrected = true;
72+
}
73+
74+
const retryErrorInfo = getRetryErrorInfo(sdkError);
75+
lastError = asSdkError(sdkError);
6176

6277
if (isRequest && isStreamingPayload(request)) {
6378
(context.logger instanceof NoOpLogger ? console : context.logger)?.warn(
@@ -95,8 +110,9 @@ const isRetryStrategyV2 = (retryStrategy: RetryStrategy | RetryStrategyV2) =>
95110
typeof (retryStrategy as RetryStrategyV2).refreshRetryTokenForRetry !== "undefined" &&
96111
typeof (retryStrategy as RetryStrategyV2).recordSuccess !== "undefined";
97112

98-
const getRetryErrorInfo = (error: SdkError): RetryErrorInfo => {
113+
const getRetryErrorInfo = (error: SkdErrorWithClockSkewMetadata): RetryErrorInfo => {
99114
const errorInfo: RetryErrorInfo = {
115+
error,
100116
errorType: getRetryErrorType(error),
101117
};
102118
const retryAfterHint = getRetryAfterHint(error.$response);
@@ -106,7 +122,7 @@ const getRetryErrorInfo = (error: SdkError): RetryErrorInfo => {
106122
return errorInfo;
107123
};
108124

109-
const getRetryErrorType = (error: SdkError): RetryErrorType => {
125+
const getRetryErrorType = (error: SkdErrorWithClockSkewMetadata): RetryErrorType => {
110126
if (isThrottlingError(error)) return "THROTTLING";
111127
if (isTransientError(error)) return "TRANSIENT";
112128
if (isServerError(error)) return "SERVER_ERROR";

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SdkError } from "@smithy/types";
1+
import { SdkError, SkdErrorWithClockSkewMetadata } from "@smithy/types";
22

33
import {
44
CLOCK_SKEW_ERROR_CODES,
@@ -23,12 +23,13 @@ export const isThrottlingError = (error: SdkError) =>
2323
* cause where the NodeHttpHandler does not decorate the Error with
2424
* the name "TimeoutError" to be checked by the TRANSIENT_ERROR_CODES condition.
2525
*/
26-
export const isTransientError = (error: SdkError) =>
26+
export const isTransientError = (error: SkdErrorWithClockSkewMetadata) =>
27+
error.$metadata?.clockSkewCorrected ||
2728
TRANSIENT_ERROR_CODES.includes(error.name) ||
2829
NODEJS_TIMEOUT_ERROR_CODES.includes((error as { code?: string })?.code || "") ||
2930
TRANSIENT_ERROR_STATUS_CODES.includes(error.$metadata?.httpStatusCode || 0);
3031

31-
export const isServerError = (error: SdkError) => {
32+
export const isServerError = (error: SkdErrorWithClockSkewMetadata) => {
3233
if (error.$metadata?.httpStatusCode !== undefined) {
3334
const statusCode = error.$metadata.httpStatusCode;
3435
if (500 <= statusCode && statusCode <= 599 && !isTransientError(error)) {

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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,14 @@ export interface SmithyException {
8080
* a base ServiceException prefixed with the service name.
8181
*/
8282
export type SdkError = Error & Partial<SmithyException> & Partial<MetadataBearer>;
83+
84+
/**
85+
* @internal
86+
*
87+
* @deprecated for same reason as SdkError. Use public client modeled exceptions in application code.
88+
*/
89+
export type SkdErrorWithClockSkewMetadata = SdkError & {
90+
$metadata: SdkError["$metadata"] & {
91+
clockSkewCorrected?: boolean;
92+
};
93+
};

0 commit comments

Comments
 (0)