diff --git a/src/services/imageCrop.js b/src/services/imageCrop.js index a30cfb9..9d68588 100644 --- a/src/services/imageCrop.js +++ b/src/services/imageCrop.js @@ -1,5 +1,6 @@ import { Worker } from 'worker_threads'; import { fileURLToPath } from 'url'; +import os from 'os'; const FETCH_TIMEOUT_MS = 15000; const MAX_BYTES = 50 * 1024 * 1024; // 50MB @@ -12,9 +13,13 @@ const MAX_BYTES = 50 * 1024 * 1024; // 50MB const WORKER_PATH = fileURLToPath(new URL('./imageCropWorker.js', import.meta.url)); const WORKER_BOOT_TIMEOUT_MS = 15000; const DEFAULT_JOB_TIMEOUT_MS = 30000; -// libvips keeps its own warm thread pool per process, so one worker per CPU is -// plenty; sharp's internal concurrency is what actually saturates a worker. -const POOL_SIZE = Math.max(1, Math.min(4, (process.availableParallelism?.() ?? 2) - 1)); +// libvips already saturates cores via its own per-process thread pool, so each worker +// is mostly IO/decode-bound rather than needing one thread per core. Cap at 3 to stay +// conservative on memory (each in-flight image can be tens of MB) while still covering +// the realistic burst. NOTE: use os.cpus(), not process.availableParallelism() — that +// helper is absent on some supported Node builds and silently collapsed this to 1. +const CPU_COUNT = Math.max(1, os.cpus?.()?.length || os.availableParallelism?.() || 2); +const POOL_SIZE = Math.max(1, Math.min(3, CPU_COUNT - 1)); /** @type {Array<{ worker: Worker, busy: boolean, id: number }>} */ const pool = [];