/** * Browser 并发槽位限流器:standard / real 各自独立队列 */ const { QUEUE_TIMEOUT_MS } = require('./constants'); class BrowserConcurrencyLimiter { /** * @param {number} max 最大并发 Browser 数 */ constructor(max) { this.max = max; this.active = 0; this.waiters = []; } /** @returns {{ max: number, active: number, queued: number }} */ getStats() { return { max: this.max, active: this.active, queued: this.waiters.length }; } /** * @param {string} taskName * @param {number} [timeoutMs] */ async acquire(taskName, timeoutMs = QUEUE_TIMEOUT_MS) { if (this.active < this.max) { this.active++; console.log(`[并发槽位] ${taskName} 立即获取 (${this.active}/${this.max})`); return; } console.log(`[并发槽位] ${taskName} 排队等待 (队列 ${this.waiters.length + 1})`); await new Promise((resolve, reject) => { const entry = { resolve: () => { this.active++; console.log(`[并发槽位] ${taskName} 出队执行 (${this.active}/${this.max})`); resolve(); }, reject, }; if (timeoutMs > 0) { entry.timer = setTimeout(() => { const idx = this.waiters.indexOf(entry); if (idx >= 0) { this.waiters.splice(idx, 1); reject(new Error('QUEUE_TIMEOUT')); } }, timeoutMs); } this.waiters.push(entry); }); } /** @param {string} taskName */ release(taskName) { this.active = Math.max(0, this.active - 1); const next = this.waiters.shift(); if (next) { if (next.timer) clearTimeout(next.timer); next.resolve(); } else { console.log(`[并发槽位] ${taskName} 释放 (${this.active}/${this.max})`); } } /** * @template T * @param {string} taskName * @param {() => Promise} fn * @returns {Promise} */ async run(taskName, fn) { await this.acquire(taskName); try { return await fn(); } finally { this.release(taskName); } } } module.exports = { BrowserConcurrencyLimiter };