at master 2.6 kB view raw
1module.exports = async ({ github, core }, callback) => { 2 const Bottleneck = require('bottleneck') 3 4 const stats = { 5 issues: 0, 6 prs: 0, 7 requests: 0, 8 artifacts: 0, 9 } 10 11 // Rate-Limiting and Throttling, see for details: 12 // https://github.com/octokit/octokit.js/issues/1069#throttling 13 // https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api 14 const allLimits = new Bottleneck({ 15 // Avoid concurrent requests 16 maxConcurrent: 1, 17 // Will be updated with first `updateReservoir()` call below. 18 reservoir: 0, 19 }) 20 // Pause between mutative requests 21 const writeLimits = new Bottleneck({ minTime: 1000 }).chain(allLimits) 22 github.hook.wrap('request', async (request, options) => { 23 // Requests to a different host do not count against the rate limit. 24 if (options.url.startsWith('https://github.com')) return request(options) 25 // Requests to the /rate_limit endpoint do not count against the rate limit. 26 if (options.url === '/rate_limit') return request(options) 27 // Search requests are in a different resource group, which allows 30 requests / minute. 28 // We do less than a handful each run, so not implementing throttling for now. 29 if (options.url.startsWith('/search/')) return request(options) 30 stats.requests++ 31 if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(options.method)) 32 return writeLimits.schedule(request.bind(null, options)) 33 else return allLimits.schedule(request.bind(null, options)) 34 }) 35 36 async function updateReservoir() { 37 let response 38 try { 39 response = await github.rest.rateLimit.get() 40 } catch (err) { 41 core.error(`Failed updating reservoir:\n${err}`) 42 // Keep retrying on failed rate limit requests instead of exiting the script early. 43 return 44 } 45 // Always keep 1000 spare requests for other jobs to do their regular duty. 46 // They normally use below 100, so 1000 is *plenty* of room to work with. 47 const reservoir = Math.max(0, response.data.resources.core.remaining - 1000) 48 core.info(`Updating reservoir to: ${reservoir}`) 49 allLimits.updateSettings({ reservoir }) 50 } 51 await updateReservoir() 52 // Update remaining requests every minute to account for other jobs running in parallel. 53 const reservoirUpdater = setInterval(updateReservoir, 60 * 1000) 54 55 try { 56 await callback(stats) 57 } finally { 58 clearInterval(reservoirUpdater) 59 core.notice( 60 `Processed ${stats.prs} PRs, ${stats.issues} Issues, made ${stats.requests + stats.artifacts} API requests and downloaded ${stats.artifacts} artifacts.`, 61 ) 62 } 63}