JSDoc comments for token pool (#3632)
This commit is contained in:
@@ -3,7 +3,11 @@
|
||||
const crypto = require('crypto')
|
||||
const PriorityQueue = require('priorityqueuejs')
|
||||
|
||||
// Compute a one-way hash of the input string.
|
||||
/**
|
||||
* Compute a one-way hash of the input string.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
function sanitizeToken(id) {
|
||||
return crypto
|
||||
.createHash('sha256')
|
||||
@@ -15,11 +19,23 @@ function getUtcEpochSeconds() {
|
||||
return (Date.now() / 1000) >>> 0
|
||||
}
|
||||
|
||||
// Encapsulate a rate-limited token, with a user-provided ID and user-provided data.
|
||||
//
|
||||
// Each token has a notion of the number of uses remaining until exhausted,
|
||||
// and the next reset time, when it can be used again even if it's exhausted.
|
||||
/**
|
||||
* Encapsulate a rate-limited token, with a user-provided ID and user-provided data.
|
||||
*
|
||||
* Each token has a notion of the number of uses remaining until exhausted,
|
||||
* and the next reset time, when it can be used again even if it's exhausted.
|
||||
*/
|
||||
class Token {
|
||||
/**
|
||||
* Token Constructor
|
||||
*
|
||||
* @param {string} id token string
|
||||
* @param data
|
||||
* @param {number} usesRemaining
|
||||
* Number of uses remaining until the token is exhausted
|
||||
* @param {number} nextReset
|
||||
* Time when the token can be used again even if it's exhausted (unix timestamp)
|
||||
*/
|
||||
constructor(id, data, usesRemaining, nextReset) {
|
||||
// Use underscores to avoid conflict with property accessors.
|
||||
Object.assign(this, {
|
||||
@@ -59,7 +75,14 @@ class Token {
|
||||
return this.usesRemaining <= 0 && !this.hasReset
|
||||
}
|
||||
|
||||
// Update the uses remaining and next reset time for a token.
|
||||
/**
|
||||
* Update the uses remaining and next reset time for a token.
|
||||
*
|
||||
* @param {number} usesRemaining
|
||||
* Number of uses remaining until the token is exhausted
|
||||
* @param {number} nextReset
|
||||
* Time when the token can be used again even if it's exhausted (unix timestamp)
|
||||
*/
|
||||
update(usesRemaining, nextReset) {
|
||||
if (!Number.isInteger(usesRemaining)) {
|
||||
throw Error('usesRemaining must be an integer')
|
||||
@@ -89,18 +112,24 @@ class Token {
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate that the token should no longer be used.
|
||||
/**
|
||||
* Indicate that the token should no longer be used.
|
||||
*/
|
||||
invalidate() {
|
||||
this._isValid = false
|
||||
}
|
||||
|
||||
// Freeze the uses remaining and next reset values. Helpful for keeping
|
||||
// stable ordering for a valid priority queue.
|
||||
/**
|
||||
* Freeze the uses remaining and next reset values. Helpful for keeping
|
||||
* stable ordering for a valid priority queue.
|
||||
*/
|
||||
freeze() {
|
||||
this._isFrozen = true
|
||||
}
|
||||
|
||||
// Unfreeze the uses remaining and next reset values.
|
||||
/**
|
||||
* Unfreeze the uses remaining and next reset values.
|
||||
*/
|
||||
unfreeze() {
|
||||
this._isFrozen = false
|
||||
}
|
||||
@@ -126,14 +155,21 @@ class Token {
|
||||
// Large sentinel value which means "never reset".
|
||||
Token.nextResetNever = Number.MAX_SAFE_INTEGER
|
||||
|
||||
// Encapsulate a collection of rate-limited tokens and choose the next
|
||||
// available token when one is needed.
|
||||
//
|
||||
// Designed for the Github API, though may be also useful with other rate-
|
||||
// limited APIs.
|
||||
/**
|
||||
* Encapsulate a collection of rate-limited tokens and choose the next
|
||||
* available token when one is needed.
|
||||
*
|
||||
* Designed for the Github API, though may be also useful with other rate-
|
||||
* limited APIs.
|
||||
*/
|
||||
class TokenPool {
|
||||
// batchSize: The maximum number of times we use each token before moving
|
||||
// on to the next one.
|
||||
/**
|
||||
* TokenPool Constructor
|
||||
*
|
||||
* @param {number} batchSize
|
||||
* The maximum number of times we use each token before moving
|
||||
* on to the next one.
|
||||
*/
|
||||
constructor({ batchSize = 1 } = {}) {
|
||||
this.batchSize = batchSize
|
||||
|
||||
@@ -147,16 +183,32 @@ class TokenPool {
|
||||
this.priorityQueue = new PriorityQueue(this.constructor.compareTokens)
|
||||
}
|
||||
|
||||
// Use the token whose current rate allotment is expiring soonest.
|
||||
/**
|
||||
* compareTokens
|
||||
*
|
||||
* @param {Token} first
|
||||
* @param {Token} second
|
||||
* @return {Token} The token whose current rate allotment is expiring soonest.
|
||||
*/
|
||||
static compareTokens(first, second) {
|
||||
return second.nextReset - first.nextReset
|
||||
}
|
||||
|
||||
// Add a token with user-provided ID and data.
|
||||
//
|
||||
// The ID can be a primitive value or an object reference, and is used (with
|
||||
// `Set`) for deduplication. If a token already exists with a given id, it
|
||||
// will be ignored.
|
||||
/**
|
||||
* Add a token with user-provided ID and data.
|
||||
*
|
||||
* @param id
|
||||
* The ID can be a primitive value or an object reference, and is used
|
||||
* (with `Set`) for deduplication. If a token already exists with a
|
||||
* given id, it will be ignored.
|
||||
* @param data
|
||||
* @param {number} usesRemaining
|
||||
* Number of uses remaining until the token is exhausted
|
||||
* @param {number} nextReset
|
||||
* Time when the token can be used again even if it's exhausted (unix timestamp)
|
||||
*
|
||||
* @return {boolean} Was the token added to the pool?
|
||||
*/
|
||||
add(id, data, usesRemaining, nextReset) {
|
||||
if (this.tokenIds.has(id)) {
|
||||
return false
|
||||
@@ -210,31 +262,35 @@ class TokenPool {
|
||||
throw Error('Token pool is exhausted')
|
||||
}
|
||||
|
||||
// Obtain the next available token, returning `null` if no tokens are
|
||||
// available.
|
||||
//
|
||||
// Tokens are initially pulled from a FIFO queue. The first token is used
|
||||
// for a batch of requests, then returned to the queue to give those
|
||||
// requests the opportunity to complete. The next token is used for the next
|
||||
// batch of requests.
|
||||
//
|
||||
// This strategy allows a token to be used for concurrent requests, not just
|
||||
// sequential request, and simplifies token recovery after errored and timed
|
||||
// out requests.
|
||||
//
|
||||
// By the time the original token re-emerges, its requests should have long
|
||||
// since completed. Even if a couple them are still running, they can
|
||||
// reasonably be ignored. The uses remaining won't be 100% correct, but
|
||||
// that's fine, because Shields uses only 75%
|
||||
//
|
||||
// The process continues until an exhausted token is pulled from the FIFO
|
||||
// queue. At that time it's placed in the priority queue based on its
|
||||
// scheduled reset time. To ensure the priority queue works as intended,
|
||||
// the scheduled reset time is frozen then.
|
||||
//
|
||||
// After obtaining a token using `next()`, invoke `update()` on it to set a
|
||||
// new use-remaining count and next-reset time. Invoke `invalidate()` to
|
||||
// indicate it should not be reused.
|
||||
/**
|
||||
* Obtain the next available token, returning `null` if no tokens are
|
||||
* available.
|
||||
*
|
||||
* Tokens are initially pulled from a FIFO queue. The first token is used
|
||||
* for a batch of requests, then returned to the queue to give those
|
||||
* requests the opportunity to complete. The next token is used for the next
|
||||
* batch of requests.
|
||||
*
|
||||
* This strategy allows a token to be used for concurrent requests, not just
|
||||
* sequential request, and simplifies token recovery after errored and timed
|
||||
* out requests.
|
||||
*
|
||||
* By the time the original token re-emerges, its requests should have long
|
||||
* since completed. Even if a couple them are still running, they can
|
||||
* reasonably be ignored. The uses remaining won't be 100% correct, but
|
||||
* that's fine, because Shields uses only 75%
|
||||
*
|
||||
* The process continues until an exhausted token is pulled from the FIFO
|
||||
* queue. At that time it's placed in the priority queue based on its
|
||||
* scheduled reset time. To ensure the priority queue works as intended,
|
||||
* the scheduled reset time is frozen then.
|
||||
*
|
||||
* After obtaining a token using `next()`, invoke `update()` on it to set a
|
||||
* new use-remaining count and next-reset time. Invoke `invalidate()` to
|
||||
* indicate it should not be reused.
|
||||
*
|
||||
* @return {Token}
|
||||
*/
|
||||
next() {
|
||||
let token = this.currentBatch.token
|
||||
const remaining = this.currentBatch.remaining
|
||||
@@ -255,7 +311,11 @@ class TokenPool {
|
||||
return token
|
||||
}
|
||||
|
||||
// Iterate over all valid tokens.
|
||||
/**
|
||||
* Iterate over all valid tokens.
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
forEach(callback) {
|
||||
function visit(item) {
|
||||
if (item.isValid) {
|
||||
|
||||
Reference in New Issue
Block a user