summaryrefslogtreecommitdiff
path: root/archived/projt-launcher/ci/github-script/withRateLimit.js
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:51:45 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:51:45 +0300
commitd3261e64152397db2dca4d691a990c6bc2a6f4dd (patch)
treefac2f7be638651181a72453d714f0f96675c2b8b /archived/projt-launcher/ci/github-script/withRateLimit.js
parent31b9a8949ed0a288143e23bf739f2eb64fdc63be (diff)
downloadProject-Tick-d3261e64152397db2dca4d691a990c6bc2a6f4dd.tar.gz
Project-Tick-d3261e64152397db2dca4d691a990c6bc2a6f4dd.zip
NOISSUE add archived projects
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to 'archived/projt-launcher/ci/github-script/withRateLimit.js')
-rw-r--r--archived/projt-launcher/ci/github-script/withRateLimit.js86
1 files changed, 86 insertions, 0 deletions
diff --git a/archived/projt-launcher/ci/github-script/withRateLimit.js b/archived/projt-launcher/ci/github-script/withRateLimit.js
new file mode 100644
index 0000000000..e7fcfbb513
--- /dev/null
+++ b/archived/projt-launcher/ci/github-script/withRateLimit.js
@@ -0,0 +1,86 @@
+/**
+ * ProjT Launcher - Rate Limit Handler
+ * Manages GitHub API rate limiting for CI scripts
+ */
+
+module.exports = async ({ github, core, maxConcurrent = 1 }, callback) => {
+ let Bottleneck
+ try {
+ Bottleneck = require('bottleneck')
+ } catch (err) {
+ core?.warning?.('bottleneck not installed; running without explicit rate limiting')
+ Bottleneck = class {
+ constructor() {}
+ wrap(fn) {
+ return (...args) => fn(...args)
+ }
+ chain() {
+ return this
+ }
+ schedule(fn, ...args) {
+ return fn(...args)
+ }
+ updateSettings() {}
+ }
+ }
+
+ const stats = {
+ issues: 0,
+ prs: 0,
+ requests: 0,
+ artifacts: 0,
+ }
+
+ // Rate-Limiting and Throttling, see for details:
+ // https://github.com/octokit/octokit.js/issues/1069#throttling
+ // https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api
+ const allLimits = new Bottleneck({
+ // Avoid concurrent requests
+ maxConcurrent,
+ // Will be updated with first `updateReservoir()` call below.
+ reservoir: 0,
+ })
+ // Pause between mutative requests
+ const writeLimits = new Bottleneck({ minTime: 1000 }).chain(allLimits)
+ github.hook.wrap('request', async (request, options) => {
+ // Requests to a different host do not count against the rate limit.
+ if (options.url.startsWith('https://github.com')) return request(options)
+ // Requests to the /rate_limit endpoint do not count against the rate limit.
+ if (options.url === '/rate_limit') return request(options)
+ // Search requests are in a different resource group, which allows 30 requests / minute.
+ // We do less than a handful each run, so not implementing throttling for now.
+ if (options.url.startsWith('/search/')) return request(options)
+ stats.requests++
+ if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(options.method))
+ return writeLimits.schedule(request.bind(null, options))
+ else return allLimits.schedule(request.bind(null, options))
+ })
+
+ async function updateReservoir() {
+ let response
+ try {
+ response = await github.rest.rateLimit.get()
+ } catch (err) {
+ core.error(`Failed updating reservoir:\n${err}`)
+ // Keep retrying on failed rate limit requests instead of exiting the script early.
+ return
+ }
+ // Always keep 1000 spare requests for other jobs to do their regular duty.
+ // They normally use below 100, so 1000 is *plenty* of room to work with.
+ const reservoir = Math.max(0, response.data.resources.core.remaining - 1000)
+ core.info(`Updating reservoir to: ${reservoir}`)
+ allLimits.updateSettings({ reservoir })
+ }
+ await updateReservoir()
+ // Update remaining requests every minute to account for other jobs running in parallel.
+ const reservoirUpdater = setInterval(updateReservoir, 60 * 1000)
+
+ try {
+ await callback(stats)
+ } finally {
+ clearInterval(reservoirUpdater)
+ core.notice(
+ `Processed ${stats.prs} PRs, ${stats.issues} Issues, made ${stats.requests + stats.artifacts} API requests and downloaded ${stats.artifacts} artifacts.`,
+ )
+ }
+}