childprocess-queue
Version:
Use `childprocess` module freely while limiting the amount of concurrent processes
275 lines (209 loc) • 7.37 kB
JavaScript
;
const ChildProcess = require('child_process');
let last_unique_id = 0;
const generateUniqueId = function () {
last_unique_id++;
return '_' + last_unique_id;
};
/**
* @returns {{
* setMaxProcesses: (function(int)),
* getMaxProcesses: (function(): int),
* getCurrentProcessCount: (function(): int),
* getCurrentProcesses: (function(): Array.<ChildProcess>),
* getCurrentQueueSize: (function(): int),
* removeFromQueue: (function(String): Boolean),
* fork: (function(...[*]): String),
* spawn: (function(...[*]): String),
* exec: (function(...[*]): String),
* exec1: (function(...[*]): String),
* execFile: (function(...[*]): String),
* newQueue: newQueue
* }}
*/
const newQueue = function newQueue() {
let MAX_PROCESSES = 5;
let QUEUE = [];
let MAP = new Map();
let PROCESSES = [];
let tryToReleaseQueue = function () {
if (PROCESSES.length >= MAX_PROCESSES || !QUEUE.length) {
return false;
}
let next = QUEUE.shift();
MAP.delete(next.id);
let args = next.args;
let process;
if (next.hasTerminateCallback) {
let oldTerminateCallback;
if (typeof args[args.length - 1] === 'function') {
oldTerminateCallback = args[args.length - 1];
}
//noinspection UnnecessaryLocalVariableJS
let terminateCallback = function () {
removeTerminatedProcess(process);
if (oldTerminateCallback) {
oldTerminateCallback.apply(this, arguments);
}
};
args[oldTerminateCallback ? args.length - 1 : args.length] = terminateCallback;
}
process = ChildProcess[next.func].apply(ChildProcess, args);
PROCESSES.push(process);
process.on('exit', function () {
removeTerminatedProcess(process);
});
if (next.callback) {
next.callback(process);
}
return true;
};
let removeTerminatedProcess = function (process) {
for (let i = 0; i < PROCESSES.length; i++) {
if (PROCESSES[i] === process) {
PROCESSES.splice(i, 1);
break;
}
}
setImmediate(tryToReleaseQueue);
};
let cloneObject = function (o) {
let clone = {};
if (o === null || typeof o !== 'object') {
return clone;
}
let keys = Object.keys(o);
for (let i = keys.length - 1; i >= 0; i--) {
clone[keys[i]] = o[keys[i]];
}
return clone;
};
let extractOnCreateFromArgs = function (args) {
let i = 0;
let length = args.length;
for (; i < length; i++) {
if (Array.isArray(args[i])) {
continue;
}
if (typeof args[i] !== 'object') {
continue;
}
let options = cloneObject(args[i]);
let onCreate = options['onCreate'];
delete options['onCreate'];
args[i] = options;
return onCreate;
}
return null;
};
//noinspection JSUnusedGlobalSymbols
let improvedChildProcess = {
setMaxProcesses: function setMaxProcesses(max) {
MAX_PROCESSES = max || 5;
tryToReleaseQueue();
return this;
},
getMaxProcesses: function getMaxProcesses() {
return MAX_PROCESSES;
},
getCurrentProcessCount: function getCurrentProcessCount() {
return PROCESSES.length;
},
getCurrentProcesses: function getCurrentProcesses() {
return PROCESSES.slice(0);
},
getCurrentQueueSize: function getCurrentQueueSize() {
return QUEUE.length;
},
removeFromQueue: function (id) {
if (MAP.has(id)) {
let item = MAP.get(id);
MAP.delete(id);
let index = QUEUE.indexOf(item);
if (index !== -1)
QUEUE.splice(index, 1);
return true;
}
return false;
},
fork: function fork(modulePath /*, args, options*/) {
let args = Array.prototype.slice.call(arguments, 0);
let onCreate = extractOnCreateFromArgs(args);
let task = {
func: 'fork',
args: args,
callback: onCreate,
hasTerminateCallback: false,
id: generateUniqueId()
};
QUEUE.push(task);
MAP.set(task.id, task);
tryToReleaseQueue();
return task.id;
},
spawn: function spawn(command /*, args, options*/) {
let args = Array.prototype.slice.call(arguments, 0);
let onCreate = extractOnCreateFromArgs(args);
let task = {
func: 'spawn',
args: args,
callback: onCreate,
hasTerminateCallback: false,
id: generateUniqueId()
};
QUEUE.push(task);
MAP.set(task.id, task);
tryToReleaseQueue();
return task.id;
},
exec: function exec(command /*, options, callback*/) {
let args = Array.prototype.slice.call(arguments, 0);
let onCreate = extractOnCreateFromArgs(args);
let task = {
func: 'exec',
args: args,
callback: onCreate,
hasTerminateCallback: true,
id: generateUniqueId()
};
QUEUE.push(task);
MAP.set(task.id, task);
tryToReleaseQueue();
return task.id;
},
execFile: function execFile(file /*, args, options, callback*/) {
let args = Array.prototype.slice.call(arguments, 0);
let onCreate = extractOnCreateFromArgs(args);
let task = {
func: 'exec',
args: args,
callback: onCreate,
hasTerminateCallback: true,
id: generateUniqueId()
};
QUEUE.push(task);
MAP.set(task.id, task);
tryToReleaseQueue();
return task.id;
}
};
improvedChildProcess.newQueue = newQueue;
//noinspection JSValidateTypes
return improvedChildProcess;
};
/**
* @type {{
* setMaxProcesses: (function(int)),
* getMaxProcesses: (function(): int),
* getCurrentProcessCount: (function(): int),
* getCurrentProcesses: (function(): Array.<ChildProcess>),
* getCurrentQueueSize: (function(): int),
* removeFromQueue: (function(String): Boolean),
* fork: (function(...[*]): String),
* spawn: (function(...[*]): String),
* exec: (function(...[*]): String),
* execFile: (function(...[*]): String),
* newQueue: newQueue
* }}
*/
module.exports = newQueue();