-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[core-http] Throttling retry policy fix in core-http #15832
Changes from 23 commits
0616843
10a3816
8bf61fe
d876b54
db74514
01bd7c4
a0307af
78814aa
ff9cb2a
d003ea8
15f9428
c774368
daedfbb
0dd2974
6c4ed84
0ad0a26
f7e294a
d80e0d9
97c6f0a
99f4d41
8d77016
ed5e621
8977a42
3029418
75ad58e
6ede956
37018a1
1a6555b
58b5855
a3a0a04
9b7f233
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -181,7 +181,7 @@ export class DefaultHttpClient extends FetchHttpClient { | |
} | ||
|
||
// @public | ||
export function delay<T>(t: number, value?: T): Promise<T | void>; | ||
export function delay<T>(delayInMs: number, abortSignal?: AbortSignalLike, abortErrorMsg?: string, value?: T): Promise<T | void>; | ||
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.
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. Good point. I'll switch to that. 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. @HarshaNalluru I think there is no need to care about their consistency, we can just ignore them once |
||
|
||
// @public | ||
export interface DeserializationContentTypes { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
import { isDefined } from "./typeguards"; | ||
import { AbortError, AbortSignalLike } from "@azure/abort-controller"; | ||
const StandardAbortMessage = "The operation was aborted."; | ||
|
||
/** | ||
* A wrapper for setTimeout that resolves a promise after delayInMs milliseconds. | ||
* @param delayInMs - The number of milliseconds to be delayed. | ||
* @param abortSignal - The abortSignal associated with containing operation. | ||
* @param abortErrorMsg - The abort error message associated with containing operation. | ||
* @param value - The value to be resolved with after a timeout of t milliseconds. | ||
* @returns - Resolved promise | ||
*/ | ||
export function delay<T>( | ||
delayInMs: number, | ||
abortSignal?: AbortSignalLike, | ||
abortErrorMsg?: string, | ||
value?: T | ||
): 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(abortErrorMsg ? abortErrorMsg : StandardAbortMessage)); | ||
}; | ||
|
||
const removeListeners = (): void => { | ||
if (abortSignal && onAborted) { | ||
abortSignal.removeEventListener("abort", onAborted); | ||
} | ||
}; | ||
|
||
onAborted = (): void => { | ||
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. why is this assigned? I don't see it getting assigned back to 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 just copied this from core-amqp. Can be done, will do. 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. |
||
if (isDefined(timer)) { | ||
HarshaNalluru marked this conversation as resolved.
Show resolved
Hide resolved
|
||
clearTimeout(timer); | ||
} | ||
removeListeners(); | ||
return rejectOnAbort(); | ||
}; | ||
|
||
if (abortSignal && abortSignal.aborted) { | ||
return rejectOnAbort(); | ||
} | ||
|
||
timer = setTimeout(() => { | ||
removeListeners(); | ||
resolve(value); | ||
}, delayInMs); | ||
|
||
if (abortSignal) { | ||
abortSignal.addEventListener("abort", onAborted); | ||
} | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
/** | ||
* Helper TypeGuard that checks if something is defined or not. | ||
HarshaNalluru marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @param thing - Anything | ||
* @internal | ||
*/ | ||
export function isDefined<T>(thing: T | undefined | null): thing is T { | ||
return typeof thing !== "undefined" && thing !== null; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
import nock from "nock"; | ||
jeremymeng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import { Constants, ServiceClient } from "../../src/coreHttp"; | ||
import { AbortController } from "@azure/abort-controller"; | ||
import { assert } from "chai"; | ||
|
||
describe("Throttling retry policy", () => { | ||
let client: ServiceClient; | ||
|
||
beforeEach(function() { | ||
if (!nock.isActive()) { | ||
nock.activate(); | ||
} | ||
nock("https://fakeservice.io:443") | ||
.persist() | ||
.put(/.*/g) | ||
.reply( | ||
Constants.HttpConstants.StatusCodes.TooManyRequests, | ||
{ | ||
type: "https://fakeservice.io/errors/too-many-requests", | ||
title: "Resource utilization has surpassed the assigned quota", | ||
policy: "Total Requests", | ||
status: Constants.HttpConstants.StatusCodes.TooManyRequests | ||
}, | ||
["Retry-After", "10000"] | ||
); | ||
client = new ServiceClient(); | ||
}); | ||
|
||
afterEach(async function() { | ||
nock.restore(); | ||
nock.cleanAll(); | ||
nock.enableNetConnect(); | ||
}); | ||
|
||
it("Should not retry forever (honors the abort signal passed)", async () => { | ||
let errorWasThrown = false; | ||
try { | ||
await client.sendRequest({ | ||
url: "https://fakeservice.io/ABCD", | ||
abortSignal: AbortController.timeout(100), | ||
method: "PUT" | ||
}); | ||
} catch (error) { | ||
errorWasThrown = true; | ||
assert.equal((error as any).name, "AbortError", "Unexpected error thrown"); | ||
} | ||
assert.equal(errorWasThrown, true, "Error was not thrown"); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now we just need to move to corev2 so we can fix #6484