From 997e6a6f43cf64817be9cb57333f5d8b66b4d8fa Mon Sep 17 00:00:00 2001 From: Thaddee Tyl Date: Thu, 23 Jun 2016 19:28:56 +0200 Subject: [PATCH] GitHub auth: maintain rate limit reset information, track token with highest remaining requests --- lib/github-auth.js | 62 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/lib/github-auth.js b/lib/github-auth.js index 52b02f5..eed29c4 100644 --- a/lib/github-auth.js +++ b/lib/github-auth.js @@ -115,36 +115,60 @@ function sendTokenToAllServers(token) { // Track rate limit requests remaining. -var reqRemaining = new Map(); -var lowestReqRemaining = Infinity, lowestReqRemainingToken; +var reqRemaining = new Map(); // From token to requests remaining. +var reqReset = new Map(); // From token to timestamp. +var highestReqRemaining = 0, highestReqRemainingToken; -// Set lowestReqRemaining* variables if the token / requests remaining -// combination passed as a parameter is lower than previously registered. -function setLowestReqRemaining(token, reqs) { - if (reqs <= lowestReqRemaining) { - lowestReqRemaining = reqs; - lowestReqRemainingToken = token; +// Set highestReqRemaining* variables if the token / requests remaining +// combination passed as a parameter is higher than previously registered. +function setHighestReqRemaining(token, reqs) { + // Equality is allowed to ensure that we have a token set as + // highestReqRemainingToken (or else there are no user tokens given). + if (reqs >= highestReqRemaining) { + highestReqRemaining = reqs; + highestReqRemainingToken = token; } } // token: client token as a string. // reqs: number of requests remaining. -function setReqRemaining(token, reqs) { - setLowestReqRemaining(token, reqs); +// reset: timestamp when the number of remaining requests is reset. +function setReqRemaining(token, reqs, reset) { + setHighestReqRemaining(token, reqs); reqRemaining.set(token, reqs); + reqReset.set(token, reset); +} + +// Retrieve a user token if there is one for which we believe there are requests +// remaining. Return undefined if we could not find one. +function getReqRemainingToken() { + if (highestReqRemaining > 0) { + return highestReqRemainingToken; + } else { + // Go through the user tokens, keep the first one which has reset. + var now = +new Date(); + for (var token of reqReset.keys()) { + if (reqReset.get(token) < now) { + // We are past the rate limit reset. + highestReqRemainingToken = token; + return highestReqRemainingToken + } + } + } } function rmReqRemaining(token) { reqRemaining.delete(token); - if (lowestReqRemainingToken === token) { - lowestReqRemaining = Infinity; - lowestReqRemainingToken = undefined; - reqRemaining.forEach(setLowestReqRemaining); + reqReset.delete(token); + if (highestReqRemainingToken === token) { + highestReqRemaining = 0; + highestReqRemainingToken = undefined; + reqRemaining.forEach(setHighestReqRemaining); } } function addGithubToken(token) { - setReqRemaining(token, Infinity); + setReqRemaining(token, 0, +new Date()); // Insert it only if it is not registered yet. if (githubUserTokens.data.indexOf(token) === -1) { githubUserTokens.data.push(token); @@ -177,10 +201,9 @@ function githubRequest(request, url, query, cb) { 'User-Agent': 'Shields.io', 'Accept': 'application/vnd.github.v3+json', }; - var githubToken; + var githubToken = getReqRemainingToken(); - if (lowestReqRemainingToken != null && lowestReqRemaining > 0) { - githubToken = lowestReqRemainingToken; + if (githubToken != null) { headers['Authorization'] = 'token ' + githubToken; } else if (serverSecrets && serverSecrets.gh_client_id) { // Using our OAuth App secret grants us 5000 req/hour @@ -197,7 +220,8 @@ function githubRequest(request, url, query, cb) { rmGithubToken(githubToken); } else { var remaining = +res.headers['x-ratelimit-remaining']; - setReqRemaining(githubToken, remaining); + var reset = +res.headers['x-ratelimit-reset']; + setReqRemaining(githubToken, remaining, reset); } } cb(err, res, buffer);