-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[core-rest-pipeline] Bug Fix - Retry logic to honor abort signal #20781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cf70c36
435f95c
80cce74
5d09ea4
ce807ed
8c859a7
2d531dc
0b6e0a2
6f82c7e
972936e
4576a0e
a3734c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| import { PipelineResponse, PipelineRequest, SendRequest } from "../interfaces"; | ||
| import { PipelineRequest, PipelineResponse, SendRequest } from "../interfaces"; | ||
| import { PipelinePolicy } from "../pipeline"; | ||
| import { delay } from "../util/helpers"; | ||
| import { createClientLogger } from "@azure/logger"; | ||
|
|
@@ -113,7 +113,7 @@ export function retryPolicy( | |
| strategyLogger.info( | ||
| `Retry ${retryCount}: Retry strategy ${strategy.name} retries after ${retryAfterInMs}` | ||
| ); | ||
| await delay(retryAfterInMs); | ||
| await delay(retryAfterInMs, undefined, { abortSignal: request.abortSignal }); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It always feels cumbersome to have to pass in undefined in the middle. Can we make
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should I put everything in the options bag instead?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my impression is that only the first parameter is used most of time. But no strong feeling. This is not public. |
||
| continue retryRequest; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,71 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| import { AbortError, AbortSignalLike } from "@azure/abort-controller"; | ||
|
|
||
| /** | ||
| * A constant that indicates whether the environment the code is running is Node.JS. | ||
| * @internal | ||
| */ | ||
| export const isNode = | ||
| typeof process !== "undefined" && Boolean(process.version) && Boolean(process.versions?.node); | ||
|
|
||
| const StandardAbortMessage = "The operation was aborted."; | ||
|
|
||
| /** | ||
| * A wrapper for setTimeout that resolves a promise after t milliseconds. | ||
| * @internal | ||
| * @param t - The number of milliseconds to be delayed. | ||
| * A wrapper for setTimeout that resolves a promise after delayInMs milliseconds. | ||
| * @param delayInMs - The number of milliseconds to be delayed. | ||
| * @param value - The value to be resolved with after a timeout of t milliseconds. | ||
| * @param options - The options for delay - currently abort options | ||
| * - abortSignal - The abortSignal associated with containing operation. | ||
| * - abortErrorMsg - The abort error message associated with containing operation. | ||
| * @returns Resolved promise | ||
| */ | ||
| export function delay<T>(t: number, value?: T): Promise<T | void> { | ||
| return new Promise((resolve) => setTimeout(() => resolve(value), t)); | ||
| export function delay<T>( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t know if the delay function is the best place to put this code. Do we have other examples of the delay function handling the abort signal?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have not written this from scratch, this was from And we also have a similar(identical) method in Basically, it's already prior art for a few years now and I'm not introducing any new patterns.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@sadasant It is now added in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anyway, I like the design! A lot! Just food for thought.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ohh I understand now! Alright |
||
| delayInMs: number, | ||
| value?: T, | ||
| options?: { | ||
| abortSignal?: AbortSignalLike; | ||
| abortErrorMsg?: string; | ||
| } | ||
| ): Promise<T | void> { | ||
| return new Promise((resolve, reject) => { | ||
| let timer: ReturnType<typeof setTimeout> | undefined = undefined; | ||
| let onAborted: (() => void) | undefined = undefined; | ||
|
|
||
| const rejectOnAbort = (): void => { | ||
| return reject( | ||
| new AbortError(options?.abortErrorMsg ? options?.abortErrorMsg : StandardAbortMessage) | ||
| ); | ||
| }; | ||
|
|
||
| const removeListeners = (): void => { | ||
| if (options?.abortSignal && onAborted) { | ||
| options.abortSignal.removeEventListener("abort", onAborted); | ||
| } | ||
| }; | ||
|
|
||
| onAborted = (): void => { | ||
| if (timer) { | ||
| clearTimeout(timer); | ||
| } | ||
| removeListeners(); | ||
| return rejectOnAbort(); | ||
| }; | ||
|
|
||
| if (options?.abortSignal && options.abortSignal.aborted) { | ||
| return rejectOnAbort(); | ||
| } | ||
|
|
||
| timer = setTimeout(() => { | ||
| removeListeners(); | ||
| resolve(value); | ||
| }, delayInMs); | ||
|
|
||
| if (options?.abortSignal) { | ||
| options.abortSignal.addEventListener("abort", onAborted); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.