You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
4.7 KiB
201 lines
4.7 KiB
/**
|
|
* Master of child processes. Handles communication between the
|
|
* processor and the main process.
|
|
*
|
|
*/
|
|
'use strict';
|
|
|
|
let status;
|
|
let processor;
|
|
let currentJobPromise;
|
|
|
|
const { promisify } = require('util');
|
|
const { asyncSend } = require('./utils');
|
|
|
|
// https://stackoverflow.com/questions/18391212/is-it-not-possible-to-stringify-an-error-using-json-stringify
|
|
if (!('toJSON' in Error.prototype)) {
|
|
Object.defineProperty(Error.prototype, 'toJSON', {
|
|
value: function() {
|
|
const alt = {};
|
|
|
|
Object.getOwnPropertyNames(this).forEach(function(key) {
|
|
alt[key] = this[key];
|
|
}, this);
|
|
|
|
return alt;
|
|
},
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
}
|
|
|
|
async function waitForCurrentJobAndExit() {
|
|
status = 'TERMINATING';
|
|
try {
|
|
await currentJobPromise;
|
|
} finally {
|
|
// it's an exit handler
|
|
// eslint-disable-next-line no-process-exit
|
|
process.exit(process.exitCode || 0);
|
|
}
|
|
}
|
|
|
|
process.on('SIGTERM', waitForCurrentJobAndExit);
|
|
process.on('SIGINT', waitForCurrentJobAndExit);
|
|
|
|
process.on('message', msg => {
|
|
switch (msg.cmd) {
|
|
case 'init':
|
|
try {
|
|
processor = require(msg.value);
|
|
} catch (err) {
|
|
status = 'Errored';
|
|
err.message = `Error loading process file ${msg.value}. ${err.message}`;
|
|
return process.send({
|
|
cmd: 'error',
|
|
error: err
|
|
});
|
|
}
|
|
|
|
if (processor.default) {
|
|
// support es2015 module.
|
|
processor = processor.default;
|
|
}
|
|
if (processor.length > 1) {
|
|
processor = promisify(processor);
|
|
} else {
|
|
const origProcessor = processor;
|
|
processor = function() {
|
|
try {
|
|
return Promise.resolve(origProcessor.apply(null, arguments));
|
|
} catch (err) {
|
|
return Promise.reject(err);
|
|
}
|
|
};
|
|
}
|
|
status = 'IDLE';
|
|
process.send({
|
|
cmd: 'init-complete'
|
|
});
|
|
break;
|
|
|
|
case 'start':
|
|
if (status !== 'IDLE') {
|
|
return process.send({
|
|
cmd: 'error',
|
|
err: new Error('cannot start a not idling child process')
|
|
});
|
|
}
|
|
status = 'STARTED';
|
|
currentJobPromise = (async () => {
|
|
try {
|
|
const result = (await processor(wrapJob(msg.job))) || {};
|
|
await asyncSend(process, {
|
|
cmd: 'completed',
|
|
value: result
|
|
});
|
|
} catch (err) {
|
|
if (!err.message) {
|
|
// eslint-disable-next-line no-ex-assign
|
|
err = new Error(err);
|
|
}
|
|
await asyncSend(process, {
|
|
cmd: 'failed',
|
|
value: err
|
|
});
|
|
} finally {
|
|
status = 'IDLE';
|
|
currentJobPromise = null;
|
|
}
|
|
})();
|
|
break;
|
|
case 'stop':
|
|
break;
|
|
}
|
|
});
|
|
|
|
/*eslint no-process-exit: "off"*/
|
|
process.on('uncaughtException', err => {
|
|
if (!err.message) {
|
|
err = new Error(err);
|
|
}
|
|
process.send({
|
|
cmd: 'failed',
|
|
value: err
|
|
});
|
|
|
|
// An uncaughException leaves this process in a potentially undetermined state so
|
|
// we must exit
|
|
process.exit(-1);
|
|
});
|
|
|
|
/**
|
|
* Enhance the given job argument with some functions
|
|
* that can be called from the sandboxed job processor.
|
|
*
|
|
* Note, the `job` argument is a JSON deserialized message
|
|
* from the main node process to this forked child process,
|
|
* the functions on the original job object are not in tact.
|
|
* The wrapped job adds back some of those original functions.
|
|
*/
|
|
function wrapJob(job) {
|
|
/*
|
|
* Emulate the real job `progress` function.
|
|
* If no argument is given, it behaves as a sync getter.
|
|
* If an argument is given, it behaves as an async setter.
|
|
*/
|
|
let progressValue = job.progress;
|
|
job.progress = function(progress) {
|
|
if (progress) {
|
|
// Locally store reference to new progress value
|
|
// so that we can return it from this process synchronously.
|
|
progressValue = progress;
|
|
// Send message to update job progress.
|
|
return asyncSend(process, {
|
|
cmd: 'progress',
|
|
value: progress
|
|
});
|
|
} else {
|
|
// Return the last known progress value.
|
|
return progressValue;
|
|
}
|
|
};
|
|
/**
|
|
* Update job info
|
|
*/
|
|
job.update = function(data) {
|
|
process.send({
|
|
cmd: 'update',
|
|
value: data
|
|
});
|
|
};
|
|
/*
|
|
* Emulate the real job `log` function.
|
|
*/
|
|
job.log = function(row) {
|
|
return asyncSend(process, {
|
|
cmd: 'log',
|
|
value: row
|
|
});
|
|
};
|
|
/*
|
|
* Emulate the real job `update` function.
|
|
*/
|
|
job.update = function(data) {
|
|
process.send({
|
|
cmd: 'update',
|
|
value: data
|
|
});
|
|
job.data = data;
|
|
};
|
|
/*
|
|
* Emulate the real job `discard` function.
|
|
*/
|
|
job.discard = function() {
|
|
process.send({
|
|
cmd: 'discard'
|
|
});
|
|
};
|
|
return job;
|
|
}
|