JSDoc comments for token pool (#3632)

This commit is contained in:
chris48s
2019-07-01 22:55:08 +01:00
committed by Paul Melnikow
parent ea865436a1
commit f29da0abdd

View File

@@ -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) {