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.

145 lines
3.4 KiB

1 month ago
'use strict';
const fork = require('child_process').fork;
const path = require('path');
const _ = require('lodash');
const getPort = require('get-port');
const { killAsync } = require('./utils');
const CHILD_KILL_TIMEOUT = 30000;
const ChildPool = function ChildPool() {
if (!(this instanceof ChildPool)) {
return new ChildPool();
}
this.retained = {};
this.free = {};
};
const convertExecArgv = function(execArgv) {
const standard = [];
const promises = [];
_.forEach(execArgv, arg => {
if (arg.indexOf('--inspect') === -1) {
standard.push(arg);
} else {
const argName = arg.split('=')[0];
promises.push(
getPort().then(port => {
return `${argName}=${port}`;
})
);
}
});
return Promise.all(promises).then(convertedArgs => {
return standard.concat(convertedArgs);
});
};
ChildPool.prototype.retain = function(processFile) {
const _this = this;
let child = _this.getFree(processFile).pop();
if (child) {
_this.retained[child.pid] = child;
return Promise.resolve(child);
}
return convertExecArgv(process.execArgv).then(execArgv => {
child = fork(path.join(__dirname, './master.js'), {
execArgv
});
child.processFile = processFile;
_this.retained[child.pid] = child;
child.on('exit', _this.remove.bind(_this, child));
return initChild(child, child.processFile)
.then(() => {
return child;
})
.catch(err => {
this.remove(child);
throw err;
});
});
};
ChildPool.prototype.release = function(child) {
delete this.retained[child.pid];
this.getFree(child.processFile).push(child);
};
ChildPool.prototype.remove = function(child) {
delete this.retained[child.pid];
const free = this.getFree(child.processFile);
const childIndex = free.indexOf(child);
if (childIndex > -1) {
free.splice(childIndex, 1);
}
};
ChildPool.prototype.kill = function(child, signal) {
this.remove(child);
return killAsync(child, signal || 'SIGKILL', CHILD_KILL_TIMEOUT);
};
ChildPool.prototype.clean = function() {
const children = _.values(this.retained).concat(this.getAllFree());
this.retained = {};
this.free = {};
const allKillPromises = [];
children.forEach(child => {
allKillPromises.push(this.kill(child, 'SIGTERM'));
});
return Promise.all(allKillPromises).then(() => {});
};
ChildPool.prototype.getFree = function(id) {
return (this.free[id] = this.free[id] || []);
};
ChildPool.prototype.getAllFree = function() {
return _.flatten(_.values(this.free));
};
async function initChild(child, processFile) {
const onComplete = new Promise((resolve, reject) => {
const onMessageHandler = msg => {
if (msg.cmd === 'init-complete') {
resolve();
} else if (msg.cmd === 'error') {
reject(msg.error);
}
child.off('message', onMessageHandler);
};
child.on('message', onMessageHandler);
});
await new Promise(resolve =>
child.send({ cmd: 'init', value: processFile }, resolve)
);
await onComplete;
}
function ChildPoolSingleton(isSharedChildPool = false) {
if (isSharedChildPool === false) {
return new ChildPool();
} else if (
!(this instanceof ChildPool) &&
ChildPoolSingleton.instance === undefined
) {
ChildPoolSingleton.instance = new ChildPool();
}
return ChildPoolSingleton.instance;
}
module.exports = ChildPoolSingleton;