thread-pool
Version:
Simple thread pooling for Node.js.
149 lines (133 loc) • 4.03 kB
JavaScript
import cp from 'child_process';
import processBody from './process-body';
import symbols from './symbols';
/**
* Whenever this function is called, we should be confident that
* there is at least 1 child process waiting to receive a task.
*
* This function will check to see if there is a task queued to be
* performed and will call `assign` to hand it off to the waiting
* child process.
*
* The `assign` method will return a promise so when that promise
* resolves, we should pass the resolution on to the `resolve`
* function created when the waiting task was assigned. Same with
* errors.
*
* @param {Pool} pool An instance of a child process pool.
*
* @return {undefined}
*/
function checkTaskQueue(pool) {
if (pool[symbols.TASK_QUEUE].length) {
const task = pool[symbols.TASK_QUEUE].shift();
pool.assign(task.task)
.then(data => task.resolve(data))
.catch(data => task.catch(data));
} else {
console.log(`Thread Pool: 1 new idle thread. ${pool[symbols.WAITING].length}/${pool[symbols.MAX_SIZE]} threads idle.`);
}
}
/**
* Procedure for when a process completes a task.
*
* @param {Pool} pool A thread pool instance.
* @param {ChildProcess} child A thread.
* @param {Object} msg The message that came through from the child.
*
* @return {undefined}
*/
function onComplete(pool, child, msg) {
const resolver = child[symbols.RESOLVER];
/*
* Clear any running timers.
*/
child[symbols.TIMER] && clearTimeout(child[symbols.TIMER]);
/*
* Reset the resolver and rejecter.
*/
child[symbols.RESOLVER] = null;
child[symbols.REJECTER] = null;
child[symbols.TIMER] = null;
/*
* Put the child process back into the waiting pool.
*/
pool[symbols.WAITING].push(child);
/*
* Check to see if there are other tasks that need to
* be completed.
*/
checkTaskQueue(pool);
/*
* Resolve the promise with the data from the child process.
*/
resolver && resolver(msg.data);
}
/**
* Give users the ability to receive and reply to messages from
* child processes.
*
* @param {Pool} pool A thread pool instance.
* @param {ChildProcess} child A thread.
* @param {Object} msg The message that came through from the child.
*
* @return {undefined}
*/
function onMessage(pool, child, msg) {
const reply = data => child.send({ type: 'reply', id: msg.id, data: data });
pool[symbols.HANDLER] && pool[symbols.HANDLER](msg.data, reply);
}
/**
* Creates a new child process from the processBody string.
*
* @param {Pool} pool A thread pool instance.
*
* @return {ChildProcess}
*/
export default function spawn(pool) {
/*
* Create a child process.
*/
const child = cp.fork(null, [], {
execPath: 'node',
execArgv: ['-e', processBody]
});
/*
* Add the process to the list of total processes.
*/
pool[symbols.PIDS].push(child);
/*
* Set up 2 new properties to help work with promises.
*/
child[symbols.RESOLVER] = null;
child[symbols.REJECTER] = null;
child[symbols.TIMER] = null;
child[symbols.KILLSIGINT] = false;
/*
* When a child closes, fire a rejecter if we have one,
* remove the child from our list of total pool processes,
* and spawn a new child if we need it to resurrect.
*/
child.on('close', (data) => {
child[symbols.REJECTER] && child[symbols.REJECTER](data);
pool[symbols.PIDS].splice(pool[symbols.PIDS].indexOf(child), 1);
!child[symbols.KILLSIGINT] && spawn(pool, 'resurrect');
});
/*
* When the child process sends back a `done` message...
*/
child.on('message', msg => {
switch (msg.type) {
case 'done' : return onComplete(pool, child, msg);
case 'message' : return onMessage(pool, child, msg);
default : return;
}
});
/*
* Put the child process into the waiting pool and check to
* see if there are any tasks to be completed.
*/
pool[symbols.WAITING].push(child);
checkTaskQueue(pool);
return child;
}