Files
links/patches/puppeteer-api/lib/concurrency.js
T
2026-06-20 04:47:34 +08:00

83 lines
2.4 KiB
JavaScript
Executable File

/**
* 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<T>} fn
* @returns {Promise<T>}
*/
async run(taskName, fn) {
await this.acquire(taskName);
try {
return await fn();
} finally {
this.release(taskName);
}
}
}
module.exports = { BrowserConcurrencyLimiter };