UNPKG

fair-queue

Version:

A fair, queue-based rate-limiting library with configurable selection modes, balancing queue size and wait time to prioritize task processing.

143 lines (142 loc) 4.09 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FairQueue = void 0; exports.createFairQueue = createFairQueue; function createFairQueue(config) { return new FairQueue(config); } class FairQueue { config; user_queues = new Map(); constructor(config) { this.config = config; } enqueue(user_id, task) { let user_queue = this.user_queues.get(user_id); if (!user_queue) { let last_served_at = Date.now() - this.config.initial_duration; user_queue = new UserQueue(user_id, last_served_at); this.user_queues.set(user_id, user_queue); } user_queue.enqueue(task); } dequeue() { if (this.config.mode == 'deterministic') { return this.dequeue_deterministic(); } else { return this.dequeue_randomized(); } } dequeue_randomized() { let now = Date.now(); let debug = this.config.debug; if (debug) { console.log(); console.log('dequeue (randomized)', { now }); } let total_weight = 0; for (let user_queue of this.user_queues.values()) { user_queue.calcWeight(now); total_weight += user_queue.weight; if (debug) { console.log({ user_id: user_queue.user_id, queue_size: user_queue.size, weight: user_queue.weight, }); } } if (total_weight == 0) return null; let dice = Math.random() * total_weight; if (debug) { console.log({ total_weight, dice }); } for (let user_queue of this.user_queues.values()) { let weight = user_queue.weight; if (dice < weight) { user_queue.last_served_at = now; return user_queue.dequeue(); } dice -= weight; } throw new Error('should not reach here'); } dequeue_deterministic() { let now = Date.now(); let debug = this.config.debug; if (debug) { console.log(); console.log('dequeue (deterministic)', { now }); } let max_weight = 0; let max_user_queue = null; for (let user_queue of this.user_queues.values()) { user_queue.calcWeight(now); let weight = user_queue.weight; if (debug) { console.log({ user_id: user_queue.user_id, queue_size: user_queue.size, weight: user_queue.weight, }); } if (weight > max_weight) { max_weight = weight; max_user_queue = user_queue; } } if (debug) { console.log({ max_weight }); } if (max_user_queue) { max_user_queue.last_served_at = now; return max_user_queue.dequeue(); } return null; } } exports.FairQueue = FairQueue; class UserQueue { user_id; last_served_at; size = 0; head = null; tail = null; weight = 0; constructor(user_id, last_served_at) { this.user_id = user_id; this.last_served_at = last_served_at; } calcWeight(now) { let queue_size = this.size; if (queue_size == 0) { this.weight = 0; } else { let duration = now - this.last_served_at || 1; this.weight = duration * (1 / queue_size); } } enqueue(task) { let new_node = { task, next: null }; let tail = this.tail; if (tail) { tail.next = new_node; } else { this.head = new_node; } this.size++; this.tail = new_node; } dequeue() { let head = this.head; if (!head) return null; this.head = head.next; this.size--; return head.task; } }