const DEFAULT_MAX_CONCURRENT_IMAGE_JOBS = 1 const MAX_ALLOWED_IMAGE_JOBS = 2 function readMaxConcurrentImageJobs(): number { const configured = Number.parseInt(process.env.IMAGE_PROCESSING_MAX_CONCURRENT || '', 10) if (!Number.isFinite(configured)) { return DEFAULT_MAX_CONCURRENT_IMAGE_JOBS } return Math.max(1, Math.min(MAX_ALLOWED_IMAGE_JOBS, configured)) } const maxConcurrentImageJobs = readMaxConcurrentImageJobs() let activeImageJobs = 0 const waitingImageJobs: Array<() => void> = [] async function acquireImageProcessingSlot(): Promise { if (activeImageJobs < maxConcurrentImageJobs) { activeImageJobs += 1 return } await new Promise((resolve) => waitingImageJobs.push(resolve)) } function releaseImageProcessingSlot(): void { const next = waitingImageJobs.shift() if (next) { // 将当前占用的名额直接交给队首等待者。这里不能先减 active 再让等待者加回, // 否则两者之间的新请求可能抢到这个瞬时空位,突破配置的并发上限。 next() return } activeImageJobs = Math.max(0, activeImageJobs - 1) } /** * 跨请求限制 Sharp 任务数。单个请求内串行并不能阻止多个请求同时解码大图, * 所以白底图、预览图和长图处理都必须共用这个进程级门闩。 */ export async function withImageProcessingSlot(task: () => Promise): Promise { await acquireImageProcessingSlot() try { return await task() } finally { releaseImageProcessingSlot() } }