Skip to content

Commit

Permalink
feat: add throttling plugin when retries is configured
Browse files Browse the repository at this point in the history
  • Loading branch information
jBouyoud committed Feb 28, 2024
1 parent 60a0d83 commit f5444a7
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 31 deletions.
39 changes: 29 additions & 10 deletions __test__/get-retry-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {getRetryOptions} from '../src/retry-options'

describe('getRequestOptions', () => {
test('retries disabled if retries == 0', async () => {
const [retryOptions, requestOptions] = getRetryOptions(
const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions(
0,
[400, 500, 502],
[]
Expand All @@ -14,10 +14,12 @@ describe('getRequestOptions', () => {
expect(retryOptions.doNotRetry).toBeFalsy()

expect(requestOptions?.retries).toBeFalsy()
expect(throttlingOptions?.onRateLimit).toBeFalsy()
expect(throttlingOptions?.onSecondaryRateLimit).toBeFalsy()
})

test('properties set if retries > 0', async () => {
const [retryOptions, requestOptions] = getRetryOptions(
const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions(
1,
[400, 500, 502],
[]
Expand All @@ -27,10 +29,12 @@ describe('getRequestOptions', () => {
expect(retryOptions.doNotRetry).toEqual([400, 500, 502])

expect(requestOptions?.retries).toEqual(1)
expect(throttlingOptions?.onRateLimit).toBeDefined()
expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined()
})

test('properties set if retries > 0', async () => {
const [retryOptions, requestOptions] = getRetryOptions(
const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions(
1,
[400, 500, 502],
[]
Expand All @@ -40,30 +44,45 @@ describe('getRequestOptions', () => {
expect(retryOptions.doNotRetry).toEqual([400, 500, 502])

expect(requestOptions?.retries).toEqual(1)
expect(throttlingOptions?.onRateLimit).toBeDefined()
expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined()
})

test('retryOptions.doNotRetry not set if exemptStatusCodes isEmpty', async () => {
const [retryOptions, requestOptions] = getRetryOptions(1, [], [])
const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions(
1,
[],
[]
)

expect(retryOptions.enabled).toBe(true)
expect(retryOptions.doNotRetry).toBeUndefined()

expect(requestOptions?.retries).toEqual(1)
expect(throttlingOptions?.onRateLimit).toBeDefined()
expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined()
})

test('requestOptions does not override defaults from @actions/github', async () => {
const [retryOptions, requestOptions] = getRetryOptions(1, [], {
request: {
agent: 'default-user-agent'
},
foo: 'bar'
})
const [retryOptions, requestOptions, throttlingOptions] = getRetryOptions(
1,
[],
{
request: {
agent: 'default-user-agent'
},
foo: 'bar'
}
)

expect(retryOptions.enabled).toBe(true)
expect(retryOptions.doNotRetry).toBeUndefined()

expect(requestOptions?.retries).toEqual(1)
expect(requestOptions?.agent).toEqual('default-user-agent')
expect(requestOptions?.foo).toBeUndefined() // this should not be in the `options.request` object, but at the same level as `request`

expect(throttlingOptions?.onRateLimit).toBeDefined()
expect(throttlingOptions?.onSecondaryRateLimit).toBeDefined()
})
})
53 changes: 39 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@octokit/core": "^5.0.1",
"@octokit/plugin-request-log": "^4.0.0",
"@octokit/plugin-retry": "^6.0.1",
"@octokit/plugin-throttling": "^8.2.0",
"@types/node": "^20.9.0"
},
"devDependencies": {
Expand All @@ -64,4 +65,4 @@
"ts-jest": "^29.1.1",
"typescript": "^5.2.2"
}
}
}
9 changes: 6 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as glob from '@actions/glob'
import * as io from '@actions/io'
import {requestLog} from '@octokit/plugin-request-log'
import {retry} from '@octokit/plugin-retry'
import {throttling, ThrottlingOptions} from '@octokit/plugin-throttling'
import {RequestRequestOptions} from '@octokit/types'
import {callAsyncFunction} from './async-function'
import {RetryOptions, getRetryOptions, parseNumberArray} from './retry-options'
Expand All @@ -20,6 +21,7 @@ type Options = {
baseUrl?: string
previews?: string[]
retry?: RetryOptions
throttle?: ThrottlingOptions
request?: RequestRequestOptions
}

Expand All @@ -33,7 +35,7 @@ async function main(): Promise<void> {
const exemptStatusCodes = parseNumberArray(
core.getInput('retry-exempt-status-codes')
)
const [retryOpts, requestOpts] = getRetryOptions(
const [retryOpts, requestOpts, throttleOpts] = getRetryOptions(
retries,
exemptStatusCodes,
defaultGitHubOptions
Expand All @@ -44,7 +46,8 @@ async function main(): Promise<void> {
userAgent: userAgent || undefined,
previews: previews ? previews.split(',') : undefined,
retry: retryOpts,
request: requestOpts
request: requestOpts,
throttle: throttleOpts
}

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

const github = getOctokit(token, opts, retry, requestLog)
const github = getOctokit(token, opts, retry, requestLog, throttling)
const script = core.getInput('script', {required: true})

// Using property/value shorthand on `require` (e.g. `{require}`) causes compilation errors.
Expand Down
26 changes: 23 additions & 3 deletions src/retry-options.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as core from '@actions/core'
import {OctokitOptions} from '@octokit/core/dist-types/types'
import {ThrottlingOptions} from '@octokit/plugin-throttling'
import {RequestRequestOptions} from '@octokit/types'

export type RetryOptions = {
Expand All @@ -11,9 +12,13 @@ export function getRetryOptions(
retries: number,
exemptStatusCodes: number[],
defaultOptions: OctokitOptions
): [RetryOptions, RequestRequestOptions | undefined] {
): [
RetryOptions,
RequestRequestOptions | undefined,
ThrottlingOptions | undefined
] {
if (retries <= 0) {
return [{enabled: false}, defaultOptions.request]
return [{enabled: false}, defaultOptions.request, undefined]
}

const retryOptions: RetryOptions = {
Expand All @@ -32,6 +37,21 @@ export function getRetryOptions(
retries
}

const throttleOptions: ThrottlingOptions = {
onRateLimit: (retryAfter, options, octokit, retryCount) => {
core.debug(
`Request quota exhausted for request ${options.method} ${options.url}`
)
return retryCount < retries
},
onSecondaryRateLimit: (retryAfter, options, octokit, retryCount) => {
core.debug(
`Secondary quota detected for request ${options.method} ${options.url}`
)
return retryCount < retries
}
}

core.debug(
`GitHub client configured with: (retries: ${
requestOptions.retries
Expand All @@ -40,7 +60,7 @@ export function getRetryOptions(
})`
)

return [retryOptions, requestOptions]
return [retryOptions, requestOptions, throttleOptions]
}

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

0 comments on commit f5444a7

Please sign in to comment.