Skip to content

Commit 2ff9c54

Browse files
Simon Grondingr2m
authored andcommitted
feat: Throttle search routes, allow parallel GET/HEAD
1 parent 626b516 commit 2ff9c54

File tree

3 files changed

+50
-1
lines changed

3 files changed

+50
-1
lines changed

lib/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ const groups = {}
1414
const createGroups = function (Bottleneck, common) {
1515
groups.global = new Bottleneck.Group({
1616
id: 'octokit-global',
17+
maxConcurrent: 10,
18+
...common
19+
})
20+
groups.search = new Bottleneck.Group({
21+
id: 'octokit-search',
1722
maxConcurrent: 1,
23+
minTime: 2000,
1824
...common
1925
})
2026
groups.write = new Bottleneck.Group({

lib/wrap-request.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ function wrapRequest (state, request, options) {
88

99
async function doRequest (state, request, options) {
1010
const isWrite = options.method !== 'GET' && options.method !== 'HEAD'
11+
const isSearch = options.method === 'GET' && options.url.startsWith('/search/')
12+
1113
const retryCount = ~~options.request.retryCount
1214
const jobOptions = retryCount > 0 ? { priority: 0, weight: 0 } : {}
1315
if (state.clustering) {
@@ -26,5 +28,9 @@ async function doRequest (state, request, options) {
2628
await state.notifications.key(state.id).schedule(jobOptions, noop)
2729
}
2830

31+
if (isSearch) {
32+
await state.search.key(state.id).schedule(jobOptions, noop)
33+
}
34+
2935
return state.global.key(state.id).schedule(jobOptions, request, options)
3036
}

test/integration/index.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('General', function () {
4949
})
5050

5151
describe('Github API best practices', function () {
52-
it('Should not allow more than 1 request concurrently', async function () {
52+
it('Should linearize requests', async function () {
5353
const octokit = new Octokit({ throttle: { onAbuseLimit: () => 1, onRateLimit: () => 1 } })
5454
const req1 = octokit.request('GET /route1', {
5555
request: {
@@ -172,6 +172,43 @@ describe('Github API best practices', function () {
172172
expect(plugin.triggersNotification('/repos/:foo/:bar/issues')).to.equal(true)
173173
})
174174

175+
it('Should maintain 2000ms between search requests', async function () {
176+
const octokit = new Octokit({
177+
throttle: {
178+
search: new Bottleneck.Group({ minTime: 50 }),
179+
onAbuseLimit: () => 1,
180+
onRateLimit: () => 1
181+
}
182+
})
183+
184+
const req1 = octokit.request('GET /search/route1', {
185+
request: {
186+
responses: [{ status: 201, headers: {}, data: {} }]
187+
}
188+
})
189+
const req2 = octokit.request('GET /route2', {
190+
request: {
191+
responses: [{ status: 202, headers: {}, data: {} }]
192+
}
193+
})
194+
const req3 = octokit.request('GET /search/route3', {
195+
request: {
196+
responses: [{ status: 203, headers: {}, data: {} }]
197+
}
198+
})
199+
200+
await Promise.all([req1, req2, req3])
201+
expect(octokit.__requestLog).to.deep.equal([
202+
'START GET /route2',
203+
'END GET /route2',
204+
'START GET /search/route1',
205+
'END GET /search/route1',
206+
'START GET /search/route3',
207+
'END GET /search/route3'
208+
])
209+
expect(octokit.__requestTimings[4] - octokit.__requestTimings[2]).to.be.closeTo(50, 20)
210+
})
211+
175212
it('Should optimize throughput rather than maintain ordering', async function () {
176213
const octokit = new Octokit({
177214
throttle: {

0 commit comments

Comments
 (0)