-
Notifications
You must be signed in to change notification settings - Fork 592
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(util-waiter): add createWaiter() (#1759)
* feat(util-waiter): add create waiter Refactor the original waiter util: 1. expose a createWaiter() only to clients in order to reduce the duplicated code-gen 2. fix infinite loop in job poller 3. fix potential number overflow * fix: address feedbacks * feat(util-waiter): merge waiter options with client * fix(util-waiter): use timestamp to determine maxWaittime rather than delay sum * fix(util-waiter): remove maxWaitTime promise The racing maxWaitTime will set a timeout that would eventually hang the user's process. So remove it. Instead, we break the poller promise if the totol wait time get close to the maxWaitTime config.
- Loading branch information
1 parent
6f73e9b
commit 3d6eb2d
Showing
13 changed files
with
253 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { AbortController } from "@aws-sdk/abort-controller"; | ||
|
||
import { ResolvedWaiterOptions, WaiterState } from "./waiter"; | ||
|
||
const mockValidate = jest.fn(); | ||
jest.mock("./utils/validate", () => ({ | ||
validateWaiterOptions: mockValidate, | ||
})); | ||
|
||
jest.useFakeTimers(); | ||
|
||
import { createWaiter } from "./createWaiter"; | ||
|
||
describe("createWaiter", () => { | ||
beforeEach(() => { | ||
jest.clearAllTimers(); | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
const minimalWaiterConfig = { | ||
minDelay: 2, | ||
maxDelay: 120, | ||
maxWaitTime: 9999, | ||
client: "client", | ||
} as ResolvedWaiterOptions<any>; | ||
const input = "input"; | ||
|
||
const abortedState = { | ||
state: WaiterState.ABORTED, | ||
}; | ||
const failureState = { | ||
state: WaiterState.FAILURE, | ||
}; | ||
const retryState = { | ||
state: WaiterState.RETRY, | ||
}; | ||
const successState = { | ||
state: WaiterState.SUCCESS, | ||
}; | ||
|
||
it("should abort when abortController is signalled", async () => { | ||
const abortController = new AbortController(); | ||
const mockAcceptorChecks = jest.fn().mockResolvedValue(retryState); | ||
const statusPromise = createWaiter( | ||
{ | ||
...minimalWaiterConfig, | ||
maxWaitTime: 20, | ||
abortController, | ||
}, | ||
input, | ||
mockAcceptorChecks | ||
); | ||
jest.advanceTimersByTime(10 * 1000); | ||
abortController.abort(); // Abort before maxWaitTime(20s); | ||
expect(await statusPromise).toEqual(abortedState); | ||
}); | ||
|
||
it("should success when acceptor checker returns seccess", async () => { | ||
const mockAcceptorChecks = jest.fn().mockResolvedValue(successState); | ||
const statusPromise = createWaiter( | ||
{ | ||
...minimalWaiterConfig, | ||
maxWaitTime: 20, | ||
}, | ||
input, | ||
mockAcceptorChecks | ||
); | ||
jest.advanceTimersByTime(minimalWaiterConfig.minDelay * 1000); | ||
expect(await statusPromise).toEqual(successState); | ||
}); | ||
|
||
it("should fail when acceptor checker returns failure", async () => { | ||
const mockAcceptorChecks = jest.fn().mockResolvedValue(failureState); | ||
const statusPromise = createWaiter( | ||
{ | ||
...minimalWaiterConfig, | ||
maxWaitTime: 20, | ||
}, | ||
input, | ||
mockAcceptorChecks | ||
); | ||
jest.advanceTimersByTime(minimalWaiterConfig.minDelay * 1000); | ||
expect(await statusPromise).toEqual(failureState); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { AbortSignal } from "@aws-sdk/types"; | ||
|
||
import { runPolling } from "./poller"; | ||
import { sleep, validateWaiterOptions } from "./utils"; | ||
import { SmithyClient, WaiterOptions, WaiterResult, waiterServiceDefaults, WaiterState } from "./waiter"; | ||
|
||
const waiterTimeout = async (seconds: number): Promise<WaiterResult> => { | ||
await sleep(seconds); | ||
return { state: WaiterState.TIMEOUT }; | ||
}; | ||
|
||
const abortTimeout = async (abortSignal: AbortSignal): Promise<WaiterResult> => { | ||
return new Promise((resolve) => { | ||
abortSignal.onabort = () => resolve({ state: WaiterState.ABORTED }); | ||
}); | ||
}; | ||
|
||
/** | ||
* Create a waiter promise that only resolves when: | ||
* 1. Abort controller is signaled | ||
* 2. Max wait time is reached | ||
* 3. `acceptorChecks` succeeds, or fails | ||
* Otherwise, it invokes `acceptorChecks` with exponential-backoff delay. | ||
* | ||
* @internal | ||
*/ | ||
export const createWaiter = async <Client extends SmithyClient, Input>( | ||
options: WaiterOptions<Client>, | ||
input: Input, | ||
acceptorChecks: (client: Client, input: Input) => Promise<WaiterResult> | ||
): Promise<WaiterResult> => { | ||
const params = { | ||
...waiterServiceDefaults, | ||
...options, | ||
}; | ||
validateWaiterOptions(params); | ||
|
||
const exitConditions = [runPolling<Client, Input>(params, input, acceptorChecks)]; | ||
if (options.abortController) { | ||
exitConditions.push(abortTimeout(options.abortController.signal)); | ||
} | ||
return Promise.race(exitConditions); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,2 @@ | ||
export * from "./utils/validate"; | ||
export * from "./utils/sleep"; | ||
export * from "./poller"; | ||
export * from "./createWaiter"; | ||
export * from "./waiter"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./sleep"; | ||
export * from "./validate"; |
Oops, something went wrong.