Skip to content

Commit 4d53e03

Browse files
mutantcornholioCvX
andauthoredOct 31, 2022
fix(retry-count): don't leak retryCount between requests: updated (#539)
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
1 parent 302da01 commit 4d53e03

File tree

5 files changed

+71
-11
lines changed

5 files changed

+71
-11
lines changed
 

‎README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ const MyOctokit = Octokit.plugin(throttling);
5252
const octokit = new MyOctokit({
5353
auth: `secret123`,
5454
throttle: {
55-
onRateLimit: (retryAfter, options, octokit) => {
55+
onRateLimit: (retryAfter, options, octokit, retryCount) => {
5656
octokit.log.warn(
5757
`Request quota exhausted for request ${options.method} ${options.url}`
5858
);
5959

60-
if (options.request.retryCount === 0) {
60+
if (retryCount < 1) {
6161
// only retries once
6262
octokit.log.info(`Retrying after ${retryAfter} seconds!`);
6363
return true;

‎src/index.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
104104
octokit.log.warn(
105105
"[@octokit/plugin-throttling] `onAbuseLimit()` is deprecated and will be removed in a future release of `@octokit/plugin-throttling`, please use the `onSecondaryRateLimit` handler instead"
106106
);
107+
// @ts-expect-error
107108
return state.onAbuseLimit(...args);
108109
}
109110
: state.onSecondaryRateLimit
@@ -117,7 +118,7 @@ export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
117118

118119
// @ts-expect-error
119120
state.retryLimiter.on("failed", async function (error, info) {
120-
const options = info.args[info.args.length - 1];
121+
const [state, request, options] = info.args;
121122
const { pathname } = new URL(options.url, "http://github.test");
122123
const shouldRetryGraphQL =
123124
pathname.startsWith("/graphql") && error.status !== 401;
@@ -126,7 +127,10 @@ export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
126127
return;
127128
}
128129

129-
const retryCount = ~~options.request.retryCount;
130+
const retryCount = ~~request.retryCount;
131+
request.retryCount = retryCount;
132+
133+
// backward compatibility
130134
options.request.retryCount = retryCount;
131135

132136
const { wantRetry, retryAfter = 0 } = await (async function () {
@@ -144,7 +148,8 @@ export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
144148
"secondary-limit",
145149
retryAfter,
146150
options,
147-
octokit
151+
octokit,
152+
retryCount
148153
);
149154
return { wantRetry, retryAfter };
150155
}
@@ -167,15 +172,16 @@ export function throttling(octokit: Octokit, octokitOptions: OctokitOptions) {
167172
"rate-limit",
168173
retryAfter,
169174
options,
170-
octokit
175+
octokit,
176+
retryCount
171177
);
172178
return { wantRetry, retryAfter };
173179
}
174180
return {};
175181
})();
176182

177183
if (wantRetry) {
178-
options.request.retryCount++;
184+
request.retryCount++;
179185
return retryAfter * state.retryAfterBaseValue;
180186
}
181187
});

‎src/types.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ declare module "@octokit/core/dist-types/types.d" {
88
}
99

1010
type LimitHandler = (
11-
retryAfter?: number,
12-
options?: object,
13-
octokit?: Octokit
11+
retryAfter: number,
12+
options: object,
13+
octokit: Octokit,
14+
retryCount: number
1415
) => void;
1516

1617
export type AbuseLimitHandler = {

‎src/wrap-request.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ async function doRequest(state, request, options) {
1212
const isSearch = options.method === "GET" && pathname.startsWith("/search/");
1313
const isGraphQL = pathname.startsWith("/graphql");
1414

15-
const retryCount = ~~options.request.retryCount;
15+
const retryCount = ~~request.retryCount;
1616
const jobOptions = retryCount > 0 ? { priority: 0, weight: 0 } : {};
1717
if (state.clustering) {
1818
// Remove a job from Redis if it has not completed or failed within 60s

‎test/retry.test.ts

+53
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import Bottleneck from "bottleneck";
22
import { TestOctokit } from "./octokit";
3+
import { Octokit } from "@octokit/core";
4+
import { throttling } from "../src";
5+
import { AddressInfo } from "net";
6+
import { createServer } from "http";
37

48
describe("Retry", function () {
59
describe("REST", function () {
@@ -114,6 +118,55 @@ describe("Retry", function () {
114118
expect(ms2).toBeGreaterThan(80);
115119
});
116120

121+
it("Should not leak retryCount between requests", async function () {
122+
let counter = 1;
123+
124+
const server = createServer((req, res) => {
125+
if (counter++ % 3 === 0) {
126+
res
127+
.writeHead(200, { "Content-Type": "application/json" })
128+
.end(JSON.stringify({ message: "Success!" }));
129+
} else {
130+
res
131+
.writeHead(403, {
132+
"Content-Type": "application/json",
133+
"retry-after": "1",
134+
})
135+
.end(
136+
JSON.stringify({
137+
message: "You have exceeded a secondary rate limit",
138+
})
139+
);
140+
}
141+
});
142+
143+
server.listen(0);
144+
const { port } = server.address() as AddressInfo;
145+
146+
const ThrottledOctokit = Octokit.plugin(throttling);
147+
const octokit = new ThrottledOctokit({
148+
baseUrl: `http://localhost:${port}`,
149+
throttle: {
150+
minimumSecondaryRateRetryAfter: 0,
151+
retryAfterBaseValue: 50,
152+
onRateLimit: () => true,
153+
onSecondaryRateLimit: (retryAfter, options, octokit, retryCount) => {
154+
if (retryCount < 5) {
155+
return true;
156+
}
157+
},
158+
},
159+
});
160+
161+
try {
162+
await octokit.request("GET /nope-nope-ok");
163+
await octokit.request("GET /nope-nope-ok");
164+
await octokit.request("GET /nope-nope-ok");
165+
} finally {
166+
server.close();
167+
}
168+
});
169+
117170
it("Should retry 'rate-limit' and succeed", async function () {
118171
let eventCount = 0;
119172
const octokit = new TestOctokit({

0 commit comments

Comments
 (0)
Please sign in to comment.