Skip to content

Commit 8950c05

Browse files
authored
feat(util-waiter): aggregate observed responses in waiter response (#1467)
* feat(util-waiter): aggregate observed responses in waiter response * formatting * changeset
1 parent 50d8c54 commit 8950c05

File tree

5 files changed

+74
-7
lines changed

5 files changed

+74
-7
lines changed

.changeset/plenty-parents-accept.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smithy/util-waiter": minor
3+
---
4+
5+
record observed responses in waiter results

packages/util-waiter/src/createWaiter.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe("createWaiter", () => {
5353
);
5454
vi.advanceTimersByTime(10 * 1000);
5555
abortController.abort(); // Abort before maxWaitTime(20s);
56-
expect(await statusPromise).toEqual(abortedState);
56+
expect(await statusPromise).toContain(abortedState);
5757
});
5858

5959
it("should success when acceptor checker returns seccess", async () => {
@@ -67,7 +67,7 @@ describe("createWaiter", () => {
6767
mockAcceptorChecks
6868
);
6969
vi.advanceTimersByTime(minimalWaiterConfig.minDelay * 1000);
70-
expect(await statusPromise).toEqual(successState);
70+
expect(await statusPromise).toContain(successState);
7171
});
7272

7373
it("should fail when acceptor checker returns failure", async () => {
@@ -81,6 +81,6 @@ describe("createWaiter", () => {
8181
mockAcceptorChecks
8282
);
8383
vi.advanceTimersByTime(minimalWaiterConfig.minDelay * 1000);
84-
expect(await statusPromise).toEqual(failureState);
84+
expect(await statusPromise).toContain(failureState);
8585
});
8686
});

packages/util-waiter/src/poller.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,40 @@ describe(runPolling.name, () => {
1717
const input = "mockInput";
1818
const abortedState = {
1919
state: WaiterState.ABORTED,
20+
observedResponses: {
21+
"AbortController signal aborted.": 1,
22+
},
2023
};
2124
const failureState = {
2225
state: WaiterState.FAILURE,
2326
reason: {
2427
mockedReason: "some-failure-value",
2528
},
29+
observedResponses: {
30+
[JSON.stringify({
31+
mockedReason: "some-failure-value",
32+
})]: 1,
33+
},
2634
};
2735
const successState = {
2836
state: WaiterState.SUCCESS,
2937
reason: {
3038
mockedReason: "some-success-value",
3139
},
40+
observedResponses: {
41+
[JSON.stringify({
42+
mockedReason: "some-success-value",
43+
})]: 1,
44+
},
3245
};
3346
const retryState = {
3447
state: WaiterState.RETRY,
3548
reason: undefined,
49+
observedResponses: {},
3650
};
3751
const timeoutState = {
3852
state: WaiterState.TIMEOUT,
53+
observedResponses: {},
3954
};
4055

4156
let mockAcceptorChecks;

packages/util-waiter/src/poller.ts

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,17 @@ export const runPolling = async <Client, Input>(
2727
input: Input,
2828
acceptorChecks: (client: Client, input: Input) => Promise<WaiterResult>
2929
): Promise<WaiterResult> => {
30+
const observedResponses: Record<string, number> = {};
31+
3032
const { state, reason } = await acceptorChecks(client, input);
33+
if (reason) {
34+
const message = createMessageFromResponse(reason);
35+
observedResponses[message] |= 0;
36+
observedResponses[message] += 1;
37+
}
38+
3139
if (state !== WaiterState.RETRY) {
32-
return { state, reason };
40+
return { state, reason, observedResponses };
3341
}
3442

3543
let currentAttempt = 1;
@@ -39,20 +47,53 @@ export const runPolling = async <Client, Input>(
3947
const attemptCeiling = Math.log(maxDelay / minDelay) / Math.log(2) + 1;
4048
while (true) {
4149
if (abortController?.signal?.aborted || abortSignal?.aborted) {
42-
return { state: WaiterState.ABORTED };
50+
const message = "AbortController signal aborted.";
51+
observedResponses[message] |= 0;
52+
observedResponses[message] += 1;
53+
return { state: WaiterState.ABORTED, observedResponses };
4354
}
4455
const delay = exponentialBackoffWithJitter(minDelay, maxDelay, attemptCeiling, currentAttempt);
4556
// Resolve the promise explicitly at timeout or aborted. Otherwise this while loop will keep making API call until
4657
// `acceptorCheck` returns non-retry status, even with the Promise.race() outside.
4758
if (Date.now() + delay * 1000 > waitUntil) {
48-
return { state: WaiterState.TIMEOUT };
59+
return { state: WaiterState.TIMEOUT, observedResponses };
4960
}
5061
await sleep(delay);
5162
const { state, reason } = await acceptorChecks(client, input);
63+
64+
if (reason) {
65+
const message = createMessageFromResponse(reason);
66+
observedResponses[message] |= 0;
67+
observedResponses[message] += 1;
68+
}
69+
5270
if (state !== WaiterState.RETRY) {
53-
return { state, reason };
71+
return { state, reason, observedResponses };
5472
}
5573

5674
currentAttempt += 1;
5775
}
5876
};
77+
78+
/**
79+
* @internal
80+
* convert the result of an SDK operation, either an error or response object, to a
81+
* readable string.
82+
*/
83+
const createMessageFromResponse = (reason: any): string => {
84+
if (reason?.$responseBodyText) {
85+
// is a deserialization error.
86+
return `Deserialization error for body: ${reason.$responseBodyText}`;
87+
}
88+
if (reason?.$metadata?.httpStatusCode) {
89+
// has a status code.
90+
if (reason.$response || reason.message) {
91+
// is an error object.
92+
return `${reason.$response.statusCode ?? reason.$metadata.httpStatusCode ?? "Unknown"}: ${reason.message}`;
93+
}
94+
// is an output object.
95+
return `${reason.$metadata.httpStatusCode}: OK`;
96+
}
97+
// is an unknown object.
98+
return String(reason?.message ?? JSON.stringify(reason) ?? "Unknown");
99+
};

packages/util-waiter/src/waiter.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ export type WaiterResult = {
4040
* (optional) Indicates a reason for why a waiter has reached its state.
4141
*/
4242
reason?: any;
43+
44+
/**
45+
* Responses observed by the waiter during its polling, where the value
46+
* is the count.
47+
*/
48+
observedResponses?: Record<string, number>;
4349
};
4450

4551
/**

0 commit comments

Comments
 (0)