50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
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<void> {
|
|
if (activeImageJobs < maxConcurrentImageJobs) {
|
|
activeImageJobs += 1
|
|
return
|
|
}
|
|
|
|
await new Promise<void>((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<T>(task: () => Promise<T>): Promise<T> {
|
|
await acquireImageProcessingSlot()
|
|
try {
|
|
return await task()
|
|
} finally {
|
|
releaseImageProcessingSlot()
|
|
}
|
|
}
|