Skip to content

Commit f5444a7

Browse files
committed
feat: add throttling plugin when retries is configured
1 parent 60a0d83 commit f5444a7

File tree

5 files changed

+99
-31
lines changed

5 files changed

+99
-31
lines changed

__test__/get-retry-options.test.ts

+29-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {getRetryOptions} from '../src/retry-options'
44

55
describe('getRequestOptions', () => {
66
test('retries disabled if retries == 0', async () => {
7-
const [retryOptions, requestOptions] = getRetryOptions(
7+
const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions(
88
0,
99
[400, 500, 502],
1010
[]
@@ -14,10 +14,12 @@ describe('getRequestOptions', () => {
1414
expect(retryOptions.doNotRetry).toBeFalsy()
1515

1616
expect(requestOptions?.retries).toBeFalsy()
17+
expect(throttlingOptions?.onRateLimit).toBeFalsy()
18+
expect(throttlingOptions?.onSecondaryRateLimit).toBeFalsy()
1719
})
1820

1921
test('properties set if retries > 0', async () => {
20-
const [retryOptions, requestOptions] = getRetryOptions(
22+
const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions(
2123
1,
2224
[400, 500, 502],
2325
[]
@@ -27,10 +29,12 @@ describe('getRequestOptions', () => {
2729
expect(retryOptions.doNotRetry).toEqual([400, 500, 502])
2830

2931
expect(requestOptions?.retries).toEqual(1)
32+
expect(throttlingOptions?.onRateLimit).toBeDefined()
33+
expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined()
3034
})
3135

3236
test('properties set if retries > 0', async () => {
33-
const [retryOptions, requestOptions] = getRetryOptions(
37+
const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions(
3438
1,
3539
[400, 500, 502],
3640
[]
@@ -40,30 +44,45 @@ describe('getRequestOptions', () => {
4044
expect(retryOptions.doNotRetry).toEqual([400, 500, 502])
4145

4246
expect(requestOptions?.retries).toEqual(1)
47+
expect(throttlingOptions?.onRateLimit).toBeDefined()
48+
expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined()
4349
})
4450

4551
test('retryOptions.doNotRetry not set if exemptStatusCodes isEmpty', async () => {
46-
const [retryOptions, requestOptions] = getRetryOptions(1, [], [])
52+
const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions(
53+
1,
54+
[],
55+
[]
56+
)
4757

4858
expect(retryOptions.enabled).toBe(true)
4959
expect(retryOptions.doNotRetry).toBeUndefined()
5060

5161
expect(requestOptions?.retries).toEqual(1)
62+
expect(throttlingOptions?.onRateLimit).toBeDefined()
63+
expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined()
5264
})
5365

5466
test('requestOptions does not override defaults from @actions/github', async () => {
55-
const [retryOptions, requestOptions] = getRetryOptions(1, [], {
56-
request: {
57-
agent: 'default-user-agent'
58-
},
59-
foo: 'bar'
60-
})
67+
const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions(
68+
1,
69+
[],
70+
{
71+
request: {
72+
agent: 'default-user-agent'
73+
},
74+
foo: 'bar'
75+
}
76+
)
6177

6278
expect(retryOptions.enabled).toBe(true)
6379
expect(retryOptions.doNotRetry).toBeUndefined()
6480

6581
expect(requestOptions?.retries).toEqual(1)
6682
expect(requestOptions?.agent).toEqual('default-user-agent')
6783
expect(requestOptions?.foo).toBeUndefined() // this should not be in the `options.request` object, but at the same level as `request`
84+
85+
expect(throttlingOptions?.onRateLimit).toBeDefined()
86+
expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined()
6887
})
6988
})

package-lock.json

+39-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@octokit/core": "^5.0.1",
4848
"@octokit/plugin-request-log": "^4.0.0",
4949
"@octokit/plugin-retry": "^6.0.1",
50+
"@octokit/plugin-throttling": "^8.2.0",
5051
"@types/node": "^20.9.0"
5152
},
5253
"devDependencies": {
@@ -64,4 +65,4 @@
6465
"ts-jest": "^29.1.1",
6566
"typescript": "^5.2.2"
6667
}
67-
}
68+
}

src/main.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as glob from '@actions/glob'
66
import * as io from '@actions/io'
77
import {requestLog} from '@octokit/plugin-request-log'
88
import {retry} from '@octokit/plugin-retry'
9+
import {throttling, ThrottlingOptions} from '@octokit/plugin-throttling'
910
import {RequestRequestOptions} from '@octokit/types'
1011
import {callAsyncFunction} from './async-function'
1112
import {RetryOptions, getRetryOptions, parseNumberArray} from './retry-options'
@@ -20,6 +21,7 @@ type Options = {
2021
baseUrl?: string
2122
previews?: string[]
2223
retry?: RetryOptions
24+
throttle?: ThrottlingOptions
2325
request?: RequestRequestOptions
2426
}
2527

@@ -33,7 +35,7 @@ async function main(): Promise<void> {
3335
const exemptStatusCodes = parseNumberArray(
3436
core.getInput('retry-exempt-status-codes')
3537
)
36-
const [retryOpts, requestOpts] = getRetryOptions(
38+
const [retryOpts, requestOpts, throttleOpts] = getRetryOptions(
3739
retries,
3840
exemptStatusCodes,
3941
defaultGitHubOptions
@@ -44,7 +46,8 @@ async function main(): Promise<void> {
4446
userAgent: userAgent || undefined,
4547
previews: previews ? previews.split(',') : undefined,
4648
retry: retryOpts,
47-
request: requestOpts
49+
request: requestOpts,
50+
throttle: throttleOpts
4851
}
4952

5053
// Setting `baseUrl` to undefined will prevent the default value from being used
@@ -53,7 +56,7 @@ async function main(): Promise<void> {
5356
opts.baseUrl = baseUrl
5457
}
5558

56-
const github = getOctokit(token, opts, retry, requestLog)
59+
const github = getOctokit(token, opts, retry, requestLog, throttling)
5760
const script = core.getInput('script', {required: true})
5861

5962
// Using property/value shorthand on `require` (e.g. `{require}`) causes compilation errors.

src/retry-options.ts

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as core from '@actions/core'
22
import {OctokitOptions} from '@octokit/core/dist-types/types'
3+
import {ThrottlingOptions} from '@octokit/plugin-throttling'
34
import {RequestRequestOptions} from '@octokit/types'
45

56
export type RetryOptions = {
@@ -11,9 +12,13 @@ export function getRetryOptions(
1112
retries: number,
1213
exemptStatusCodes: number[],
1314
defaultOptions: OctokitOptions
14-
): [RetryOptions, RequestRequestOptions | undefined] {
15+
): [
16+
RetryOptions,
17+
RequestRequestOptions | undefined,
18+
ThrottlingOptions | undefined
19+
] {
1520
if (retries <= 0) {
16-
return [{enabled: false}, defaultOptions.request]
21+
return [{enabled: false}, defaultOptions.request, undefined]
1722
}
1823

1924
const retryOptions: RetryOptions = {
@@ -32,6 +37,21 @@ export function getRetryOptions(
3237
retries
3338
}
3439

40+
const throttleOptions: ThrottlingOptions = {
41+
onRateLimit: (retryAfter, options, octokit, retryCount) => {
42+
core.debug(
43+
`Request quota exhausted for request ${options.method} ${options.url}`
44+
)
45+
return retryCount < retries
46+
},
47+
onSecondaryRateLimit: (retryAfter, options, octokit, retryCount) => {
48+
core.debug(
49+
`Secondary quota detected for request ${options.method} ${options.url}`
50+
)
51+
return retryCount < retries
52+
}
53+
}
54+
3555
core.debug(
3656
`GitHub client configured with: (retries: ${
3757
requestOptions.retries
@@ -40,7 +60,7 @@ export function getRetryOptions(
4060
})`
4161
)
4262

43-
return [retryOptions, requestOptions]
63+
return [retryOptions, requestOptions, throttleOptions]
4464
}
4565

4666
export function parseNumberArray(listString: string): number[] {

0 commit comments

Comments
 (0)