crafity-core
Version:
Crafity Core Framework
276 lines (241 loc) • 7.58 kB
JavaScript
/*jslint node: true, bitwise: true, unparam: true, maxerr: 50, white: true, stupid: true */
;
/*!
* crafity-workerpool - Schedule a bunch of task and receive notifications
* Copyright(c) 2013 Crafity
* Copyright(c) 2013 Bart Riemens
* Copyright(c) 2013 Galina Slavova
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Event = require('./crafity.Event')
, EventEmitter = require('events').EventEmitter
, Domain = require('./crafity.Domain')
;
/**
*
* @param name
* @param async
* @param debug
* @param autostart
* @param timeout
*/
function Workerpool(name, async, debug, autostart, timeout) {
// Local Variables
var selfWorkerpool = this
, pool = []
, finished = true
, timeoutIdWorkerpool = null
, running = [];
async = async || false;
debug = debug || false;
function WorkerContext() {
var selfWorkerContext = this
, timeoutIdWorkerContext
, innerOnCompleted = new Event("sync")
, publicOnCompleteCallback
, PublicWorkerContext;
publicOnCompleteCallback = function (ex, result) {
if (ex) { throw ex; }
return result;
};
this.debug = debug || false;
this.isStillSync = true;
this.isAsync = false;
this.asyncTimeout = 1000;
this.onComplete = new Event("sync");
this.isCompleted = false;
PublicWorkerContext = function WorkerContext() {
this.async = function (timeout) {
if (selfWorkerContext.isAsync) {
throw new Error('Can not call async twice on a worker context');
}
if (selfWorkerContext.isCompleted === true) {
throw new Error('Can not call async after the work is done');
}
selfWorkerContext.isAsync = true;
selfWorkerContext.asyncTimeout = timeout || selfWorkerContext.asyncTimeout;
};
this.debug = function () {
selfWorkerContext.debug = true;
};
this.steps = function (steps) {
var eventEmitter = new EventEmitter();
function next() {
var _args = Array.prototype.slice.call(arguments);
process.nextTick(function () {
var step = steps.shift();
try {
if (step) { step.apply(this, [next].concat(_args)); } else { eventEmitter.emit("complete"); }
} catch (err) {
return eventEmitter.emit("complete", err);
}
});
}
next();
return eventEmitter;
};
this.complete = function (ex, args) {
if (!selfWorkerContext.isAsync) {
throw new Error('Can not call complete on a non async context');
}
if (selfWorkerContext.isStillSync === true) {
throw new Error('Can not call complete while still in sync');
}
if (selfWorkerContext.isCompleted === true) {
return;
}
args = Array.prototype.slice.call(arguments);
args.splice(0, 1);
innerOnCompleted.raise(ex, args);
};
this.onComplete = function (callback) {
if (!selfWorkerContext.isAsync) {
throw new Error('Can not register for oncomplete on a non async worker context');
}
if (selfWorkerContext.isCompleted === true) {
return;
}
publicOnCompleteCallback = callback;
};
};
PublicWorkerContext.prototype = EventEmitter.prototype;
this.run = function (work, callback) {
selfWorkerContext.onComplete.subscribe(callback);
var publicWorkerContext
, output;
function onCompleteHandler(ex, result) {
if (selfWorkerContext.debug) { console.log(work.report ? "C O M P L E T E " + work.report.name : ""); }
if (selfWorkerContext.isCompleted) {
throw new Error('Can not call the completed handler after completion');
}
selfWorkerContext.isCompleted = true;
innerOnCompleted.unsubscribe(onCompleteHandler);
clearTimeout(timeoutIdWorkerContext);
try {
result = publicOnCompleteCallback(ex, result);
publicWorkerContext.emit("complete", ex, result);
selfWorkerContext.onComplete.raise(null, result);
} catch (err) {
selfWorkerContext.onComplete.raise(err, null);
}
selfWorkerContext.onComplete.unsubscribe(callback);
}
innerOnCompleted.subscribe(onCompleteHandler);
try {
publicWorkerContext = new PublicWorkerContext();
output = work.call(publicWorkerContext, publicWorkerContext);
selfWorkerContext.isStillSync = false;
if (selfWorkerContext.debug) { console.log("Sychronous part is done", work.report.name, innerOnCompleted.listenerCount); }
if (!selfWorkerContext.isAsync) {
onCompleteHandler(null, output);
} else {
timeoutIdWorkerContext = setTimeout(function () {
timeoutIdWorkerContext = null;
if (selfWorkerContext.debug) { console.log("TIMEEEE OUTTTT....", work.report.name, innerOnCompleted.listenerCount); }
innerOnCompleted.raise(new Error("Work item exceeded the specified time out"), null);
}, selfWorkerContext.asyncTimeout);
if (selfWorkerContext.debug) { console.log("Time out is set", selfWorkerContext.asyncTimeout, timeoutIdWorkerContext.toString(), work.report.name, innerOnCompleted.listenerCount); }
}
} catch (ex) {
onCompleteHandler(ex, null);
}
};
}
WorkerContext.prototype = EventEmitter.prototype;
// Events
this.onWorkStopped = new Event("sync");
this.onWorkStarted = new Event("sync");
this.onWorkCompleted = new Event("sync");
this.onWorkItemStarted = new Event("sync");
this.onWorkItemCompleted = new Event("sync");
// Properties
this.working = false;
// Functions
this.add = function (work) {
if (work === undefined) {
throw new Error('Argument "work" is undefined');
}
finished = false;
pool.push(work);
};
this.work = function (timeout) {
selfWorkerpool.working = true;
selfWorkerpool.onWorkStarted.raise();
if (timeout > 0) {
timeoutIdWorkerpool = setTimeout(function () {
selfWorkerpool.stop();
selfWorkerpool.onWorkCompleted.raise(new Error("Work item exceeded the specified time out"));
}, timeout);
}
function processWorkItem() {
if (!selfWorkerpool.working) { return; }
if (pool.length > 0) {
var workItem = pool[0]
, workerContext;
running.push(workItem.report ? workItem.report.name : workItem);
pool.splice(0, 1);
workerContext = new WorkerContext();
selfWorkerpool.onWorkItemStarted.raise(null, workItem);
setTimeout(function () {
if (!selfWorkerpool.working) {
return;
}
var domain = Domain.createDomain();
domain.run(function () {
workerContext.run(workItem, function (err) {
running.pop();
selfWorkerpool.onWorkItemCompleted.raise(err, workItem);
if (!async) {
setTimeout(processWorkItem, 10);
}
});
});
domain.on("error", function (err) {
running.pop();
selfWorkerpool.onWorkItemCompleted.raise(err, workItem);
if (!async) {
setTimeout(processWorkItem, 10);
}
});
if (async) {
setTimeout(processWorkItem, 10);
}
}, 1);
} else {
if (!finished && running.length === 0) {
finished = true;
if (!selfWorkerpool.working) { return; }
selfWorkerpool.onWorkCompleted.raise();
}
if (!selfWorkerpool.working) { return; }
setTimeout(processWorkItem, 100);
}
}
setTimeout(processWorkItem, 1);
};
this.stop = function () {
clearTimeout(timeoutIdWorkerpool);
selfWorkerpool.working = false;
running = [];
selfWorkerpool.onWorkStopped.raise();
};
if (autostart) {
selfWorkerpool.work(timeout);
}
}
Workerpool.prototype = EventEmitter.prototype;
/**
* Initialize module
*/
module.exports = Workerpool;
/**
* Module name.
*/
exports.fullname = "crafity.Workerpool";
/**
* Module version.
*/
exports.version = '0.0.1';