diff --git a/src/core/security/securityCheck.ts b/src/core/security/securityCheck.ts index 3fffed911..840727ae2 100644 --- a/src/core/security/securityCheck.ts +++ b/src/core/security/securityCheck.ts @@ -55,10 +55,14 @@ export const runSecurityCheck = async ( } } + // Cap security worker pool to 1 thread to minimize CPU contention with the metrics + // worker pool. Security runs concurrently with file processing and completes well before + // metrics calculation, so extra threads only compete for CPU without improving throughput. const taskRunner = deps.initTaskRunner({ numOfTasks: rawFiles.length + gitDiffTasks.length + gitLogTasks.length, workerType: 'securityCheck', runtime: 'worker_threads', + maxThreads: 1, }); const fileTasks = rawFiles.map( (file) => diff --git a/src/shared/processConcurrency.ts b/src/shared/processConcurrency.ts index 8a2aebf72..5e3f3d346 100644 --- a/src/shared/processConcurrency.ts +++ b/src/shared/processConcurrency.ts @@ -12,6 +12,7 @@ export interface WorkerOptions { numOfTasks: number; workerType: WorkerType; runtime: WorkerRuntime; + maxThreads?: number; } /** @@ -62,8 +63,9 @@ export const getWorkerThreadCount = (numOfTasks: number): { minThreads: number; }; export const createWorkerPool = (options: WorkerOptions): Tinypool => { - const { numOfTasks, workerType, runtime = 'child_process' } = options; - const { minThreads, maxThreads } = getWorkerThreadCount(numOfTasks); + const { numOfTasks, workerType, runtime = 'child_process', maxThreads: maxThreadsOverride } = options; + const { minThreads, maxThreads: autoMaxThreads } = getWorkerThreadCount(numOfTasks); + const maxThreads = maxThreadsOverride != null ? Math.max(1, maxThreadsOverride) : autoMaxThreads; // Get worker path - uses unified worker in bundled env, individual files otherwise const workerPath = getWorkerPath(workerType); diff --git a/tests/shared/processConcurrency.test.ts b/tests/shared/processConcurrency.test.ts index 289406ff6..ada40a4ca 100644 --- a/tests/shared/processConcurrency.test.ts +++ b/tests/shared/processConcurrency.test.ts @@ -130,6 +130,26 @@ describe('processConcurrency', () => { }); expect(tinypool).toBeDefined(); }); + + it('should respect maxThreads override when provided', () => { + createWorkerPool({ numOfTasks: 500, workerType: 'securityCheck', runtime: 'worker_threads', maxThreads: 1 }); + + expect(Tinypool).toHaveBeenCalledWith( + expect.objectContaining({ + maxThreads: 1, + }), + ); + }); + + it('should clamp maxThreads override to minimum of 1', () => { + createWorkerPool({ numOfTasks: 500, workerType: 'securityCheck', runtime: 'worker_threads', maxThreads: 0 }); + + expect(Tinypool).toHaveBeenCalledWith( + expect.objectContaining({ + maxThreads: 1, + }), + ); + }); }); describe('initTaskRunner', () => {