fix(ngcc): handle `ENOMEM` errors in worker processes (#36626)
When running in parallel mode, worker processes forward errors thrown during task processing to the master process, which in turn exits with an error. However, there are cases where the error is not directly related to processing the entry-point. One such case is when there is not enough memory (for example, due to all the other tasks being processed simultaneously). Previously, an `ENOMEM` error thrown on a worker process would propagate to the master process, eventually causing ngcc to exit with an error. Example failure: https://circleci.com/gh/angular/angular/682198 This commit improves handling of these low-memory situations by detecting `ENOMEM` errors and killing the worker process, thus allowing the master process to decide how to handle that. The master process will put the task back into the tasks queue and continue processing tasks with the rest of the worker processes (and thus with lower memory pressure). PR Close #36626
This commit is contained in:
parent
793cb328de
commit
4779c4b94a
|
@ -81,10 +81,21 @@ export async function startWorker(logger: Logger, createCompileFn: CreateCompile
|
|||
`[Worker #${cluster.worker.id}] Invalid message received: ${JSON.stringify(msg)}`);
|
||||
}
|
||||
} catch (err) {
|
||||
await sendMessageToMaster({
|
||||
type: 'error',
|
||||
error: (err instanceof Error) ? (err.stack || err.message) : err,
|
||||
});
|
||||
switch (err && err.code) {
|
||||
case 'ENOMEM':
|
||||
// Not being able to allocate enough memory is not necessarily a problem with processing
|
||||
// the current task. It could just mean that there are too many tasks being processed
|
||||
// simultaneously.
|
||||
//
|
||||
// Exit with an error and let the cluster master decide how to handle this.
|
||||
logger.warn(`[Worker #${cluster.worker.id}] ${err.stack || err.message}`);
|
||||
return process.exit(1);
|
||||
default:
|
||||
await sendMessageToMaster({
|
||||
type: 'error',
|
||||
error: (err instanceof Error) ? (err.stack || err.message) : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import {startWorker} from '../../../src/execution/cluster/worker';
|
|||
import {Task, TaskCompletedCallback, TaskProcessingOutcome} from '../../../src/execution/tasks/api';
|
||||
import {FileToWrite} from '../../../src/rendering/utils';
|
||||
import {MockLogger} from '../../helpers/mock_logger';
|
||||
import {mockProperty} from '../../helpers/spy_utils';
|
||||
import {mockProperty, spyProperty} from '../../helpers/spy_utils';
|
||||
|
||||
|
||||
describe('startWorker()', () => {
|
||||
|
@ -163,6 +163,38 @@ describe('startWorker()', () => {
|
|||
.toHaveBeenCalledWith({type: 'error', error: err.stack}, jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('should exit on `ENOMEM` errors during task processing', () => {
|
||||
const processExitSpy = jasmine.createSpy('process.exit');
|
||||
const {
|
||||
setMockValue: mockProcessExit,
|
||||
installSpies: installProcessExitSpies,
|
||||
uninstallSpies: uninstallProcessExitSpies,
|
||||
} = spyProperty(process, 'exit');
|
||||
|
||||
try {
|
||||
installProcessExitSpies();
|
||||
mockProcessExit(processExitSpy as unknown as typeof process.exit);
|
||||
|
||||
const mockTask = {
|
||||
entryPoint: {name: 'foo'},
|
||||
formatProperty: 'es2015',
|
||||
processDts: true,
|
||||
} as unknown as Task;
|
||||
|
||||
const noMemError = Object.assign(new Error('ENOMEM: not enough memory'), {code: 'ENOMEM'});
|
||||
compileFnSpy.and.throwError(noMemError);
|
||||
|
||||
startWorker(mockLogger, createCompileFnSpy);
|
||||
cluster.worker.emit('message', {type: 'process-task', task: mockTask});
|
||||
|
||||
expect(mockLogger.logs.warn).toEqual([[`[Worker #42] ${noMemError.stack}`]]);
|
||||
expect(processExitSpy).toHaveBeenCalledWith(1);
|
||||
expect(processSendSpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
uninstallProcessExitSpies();
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw, when an unknown message type is received', () => {
|
||||
startWorker(mockLogger, createCompileFnSpy);
|
||||
cluster.worker.emit('message', {type: 'unknown', foo: 'bar'});
|
||||
|
|
Loading…
Reference in New Issue