diff --git a/index.js b/index.js index 507395f..431992e 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,21 @@ + module.exports = function(options) { - return new (require("./lib/manager.js"))(options); + var options = options || {}; + options.timeout = options.timeout || 10000; + options.strategy = options.strategy || "http-servers"; + + if (options.strategy === "http-servers") { + return new (require("./lib/manager-servers.js"))(options); + } + + if (options.strategy === "dedicated-process") { + return new (require("./lib/manager-processes.js"))(options); + } + + throw new Error("Unsupported scripts manager strategy: " + options.strategy); }; -module.exports.ScriptManager = require("./lib/manager.js"); \ No newline at end of file +module.exports.ScriptManager = require("./lib/manager-servers.js"); +module.exports.ScriptManagerOnHttpServers = module.exports.ScriptManager; + +module.exports.ScriptManagerOnProcesses = require("./lib/manager-processes.js"); diff --git a/lib/manager-processes.js b/lib/manager-processes.js new file mode 100644 index 0000000..ba0c9f5 --- /dev/null +++ b/lib/manager-processes.js @@ -0,0 +1,81 @@ +var childProcess = require("child_process"); +var path = require("path"); +var _ = require("underscore"); +var S = require("string"); + +var ScriptsManager = module.exports = function (options) { + this.options = options; + this.options.timeout = this.options.timeout || 10000; +}; + + +ScriptsManager.prototype.start = function (cb) { + cb(); +}; + +ScriptsManager.prototype.ensureStarted = function (cb) { + cb(); +}; + +ScriptsManager.prototype.execute = function (inputs, options, cb) { + var self = this; + var isDone = false; + + //fix freeze during debugging + process.execArgv = _.filter(process.execArgv, function (arg) { + return !S(arg).startsWith("--debug"); + }); + + process.execArgv.push("--expose-gc"); + + var worker = childProcess.fork(path.join(__dirname, "worker-processes.js")); + + worker.on('message', function (m) { + + + if (m.error) { + isDone = true; + return cb(new Error(m.error)); + } + + if (m.action === "process-response") { + isDone = true; + return cb(null, m.value); + } + + if (m.action === "callback") { + m.params.push(function(){ + var args = Array.prototype.slice.call(arguments); + if (args.length && args[0]) { + args[0] = args[0].message; + } + worker.send({ + action: "callback-response", + params: args + }) + }); + options.callback.apply(self, m.params); + //isDone = true; + //return cb(null, m); + } + }); + + worker.send({ + inputs: inputs, + options: options + }); + + setTimeout(function () { + if (isDone) + return; + + worker.kill(); + + cb(new Error("Timeout error during rendering")); + }, options.timeout || this.options.timeout); +}; + +ScriptsManager.prototype.kill = function () { +}; + + diff --git a/lib/manager.js b/lib/manager-servers.js similarity index 99% rename from lib/manager.js rename to lib/manager-servers.js index e02be2d..2ce4f94 100644 --- a/lib/manager.js +++ b/lib/manager-servers.js @@ -42,9 +42,9 @@ var findFreePortInRange = function (host, portLeftBoundary, portRightBoundary, c }; var ScriptsManager = module.exports = function (options) { - this.options = options || {}; - this.options.numberOfWorkers = this.options.numberOfWorkers || 1; + this.options = options; this.options.timeout = this.options.timeout || 10000; + this.options.numberOfWorkers = this.options.numberOfWorkers || 1; this.options.host = this.options.host || "127.0.0.1"; this._runningRequests = []; @@ -82,7 +82,7 @@ ScriptsManager.prototype.start = function (cb) { process.execArgv.push("--expose-gc"); - self.workersCluster = childProcess.fork(path.join(__dirname, "worker.js"), []); + self.workersCluster = childProcess.fork(path.join(__dirname, "worker-servers.js"), []); self.workersCluster.on("message", function (m) { if (m.action === "running") { self.isStarted = true; diff --git a/lib/worker-processes.js b/lib/worker-processes.js new file mode 100644 index 0000000..dbda37c --- /dev/null +++ b/lib/worker-processes.js @@ -0,0 +1,45 @@ +process.on('uncaughtException', function (err) { + process.send({ + error: err.message, + errorStack: err.stack + }); + + process.exit(); +}); + +var cb; + +function callback() { + cb = arguments[arguments.length - 1]; + var args = Array.prototype.slice.call(arguments); + args.pop(); + process.send({action: "callback", pid: process.pid, params: args.sort()}); +} + + +process.on('message', function (m) { + if (m.action === "callback-response") { + if (m.params.length) { + if (m.params[0]) { + m.params[0] = new Error(m.params[0]); + } + } + cb.apply(this, m.params); + } + + require(m.options.execModulePath)(m.inputs, callback, function (err, val) { + if (err) { + process.send({ + error: err.message, + errorStack: err.stack + }); + } else { + process.send({ + action: "process-response", + value: val + }); + } + + process.exit(); + }); +}); \ No newline at end of file diff --git a/lib/worker.js b/lib/worker-servers.js similarity index 100% rename from lib/worker.js rename to lib/worker-servers.js diff --git a/test/test.js b/test/test.js index fde468d..8c8be6a 100644 --- a/test/test.js +++ b/test/test.js @@ -1,96 +1,124 @@ var should = require("should"), path = require("path"), - fs = require("fs"), - ScriptsManager = require("../lib/manager.js"); + ScriptsManager = require("../lib/manager-servers.js"); + ScriptsManagerWithProcesses = require("../lib/manager-processes.js"); describe("scripts manager", function () { - var scriptsManager = new ScriptsManager({numberOfWorkers: 2}); + describe("servers", function () { - beforeEach(function (done) { - scriptsManager.ensureStarted(done); - }); + var scriptsManager = new ScriptsManager({numberOfWorkers: 2}); + beforeEach(function (done) { + scriptsManager.ensureStarted(done); + }); - afterEach(function () { - scriptsManager.kill(); - }); + afterEach(function () { + scriptsManager.kill(); + }); + + common(scriptsManager); + + it("should be able to set up on custom port", function (done) { + var scriptsManager2 = new ScriptsManager({numberOfWorkers: 1, portLeftBoundary: 10000, portRightBoundary: 11000}); + scriptsManager2.start(function(err) { + if (err) + return done(err); + + scriptsManager2.execute({foo: "foo"}, {execModulePath: path.join(__dirname, "scripts", "script.js")}, function (err, res) { + if (err) + return done(err); + + scriptsManager2.options.port.should.be.within(10000, 11000); + res.foo.should.be.eql("foo"); + done(); + }); + }); + }); - it("should be able to execute simple script", function (done) { - scriptsManager.execute({foo: "foo"}, {execModulePath: path.join(__dirname, "scripts", "script.js")}, function (err, res) { - if (err) - return done(err); + it("should be able to process high data volumes", function (done) { + this.timeout(7000); + var data = { foo: "foo", people: []}; + for (var i = 0; i < 2000000; i++) { + data.people.push(i); + } + scriptsManager.execute(data, {execModulePath: path.join(__dirname, "scripts", "script.js")}, function (err, res) { + if (err) + return done(err); - res.foo.should.be.eql("foo"); - done(); + res.foo.should.be.eql("foo"); + done(); + }); }); }); + describe("processes", function () { - it("should handle script error", function (done) { - scriptsManager.execute({foo: "foo"}, {execModulePath: path.join(__dirname, "scripts", "error.js")}, function (err, res) { - if (!err) - return done(new Error("It should have failed.")); + var scriptsManager = new ScriptsManagerWithProcesses({numberOfWorkers: 2}); + beforeEach(function (done) { + scriptsManager.ensureStarted(done); + }); - done(); + afterEach(function () { + scriptsManager.kill(); }); + + common(scriptsManager); }); - it("should handle timeouts", function (done) { - var timeouted = false; - scriptsManager.execute({foo: "foo"}, - { - execModulePath: path.join(__dirname, "scripts", "timeout.js"), - timeout: 10 - }, function (err, res) { - timeouted = true; - done(); - }); + function common(scriptsManager) { - setTimeout(function () { - if (!timeouted) - done(new Error("It should timeout")); + it("should be able to execute simple script", function (done) { + scriptsManager.execute({foo: "foo"}, {execModulePath: path.join(__dirname, "scripts", "script.js")}, function (err, res) { + if (err) + return done(err); - }, 500); - }); + res.foo.should.be.eql("foo"); + done(); + }); + }); - it("should handle unexpected error", function (done) { - scriptsManager.execute({foo: "foo"}, {execModulePath: path.join(__dirname, "scripts", "unexpectedError.js")}, function (err, res) { - if (err) - return done(); + it("should handle script error", function (done) { + scriptsManager.execute({foo: "foo"}, {execModulePath: path.join(__dirname, "scripts", "error.js")}, function (err, res) { + if (!err) + return done(new Error("It should have failed.")); - done(new Error("There should be an error")); + done(); + }); }); - }); - it("should be able to callback to the caller", function (done) { - function callback(str, cb) { - cb(null, str + "aaa"); - } + it("should handle timeouts", function (done) { + var timeouted = false; + scriptsManager.execute({foo: "foo"}, + { + execModulePath: path.join(__dirname, "scripts", "timeout.js"), + timeout: 10 + }, function (err, res) { + timeouted = true; + done(); + }); + + setTimeout(function () { + if (!timeouted) + done(new Error("It should timeout")); - scriptsManager.execute({}, { - execModulePath: path.join(__dirname, "scripts", "callback.js"), - callback: callback - }, function (err, res) { - if (err) - return done(err); + }, 500); + }); - res.test.should.be.eql("testaaa"); + it("should handle unexpected error", function (done) { + scriptsManager.execute({foo: "foo"}, {execModulePath: path.join(__dirname, "scripts", "unexpectedError.js")}, function (err, res) { + if (err) + return done(); - done(); + done(new Error("There should be an error")); + }); }); - }); - it("should be able to process parallel requests", function (done) { - function callback(str, cb) { - setTimeout(function() { + it("should be able to callback to the caller", function (done) { + function callback(str, cb) { cb(null, str + "aaa"); - }, 10); - } + } - var doneCounter = []; - - for (var i = 0; i < 20; i++) { scriptsManager.execute({}, { execModulePath: path.join(__dirname, "scripts", "callback.js"), callback: callback @@ -99,54 +127,46 @@ describe("scripts manager", function () { return done(err); res.test.should.be.eql("testaaa"); - doneCounter++; - if (doneCounter === 20) - done(); + done(); }); - } - }); - - it("should expose gc", function (done) { - scriptsManager.execute({foo: "foo"}, {execModulePath: path.join(__dirname, "scripts", "gc.js")}, function (err, res) { - if (err) - return done(err); - - res.foo.should.be.eql("foo"); - done(); }); - }); - it("should be able to set up on custom port", function (done) { - var scriptsManager2 = new ScriptsManager({numberOfWorkers: 1, portLeftBoundary: 10000, portRightBoundary: 11000}); - scriptsManager2.start(function(err) { - if (err) - return done(err); + it("should be able to process parallel requests", function (done) { + function callback(str, cb) { + setTimeout(function() { + cb(null, str + "aaa"); + }, 10); + } + + var doneCounter = []; + + for (var i = 0; i < 20; i++) { + scriptsManager.execute({}, { + execModulePath: path.join(__dirname, "scripts", "callback.js"), + callback: callback + }, function (err, res) { + if (err) + return done(err); + + res.test.should.be.eql("testaaa"); + doneCounter++; + + if (doneCounter === 20) + done(); + }); + } + }); - scriptsManager2.execute({foo: "foo"}, {execModulePath: path.join(__dirname, "scripts", "script.js")}, function (err, res) { + it("should expose gc", function (done) { + scriptsManager.execute({foo: "foo"}, {execModulePath: path.join(__dirname, "scripts", "gc.js")}, function (err, res) { if (err) return done(err); - scriptsManager2.options.port.should.be.within(10000, 11000); res.foo.should.be.eql("foo"); done(); }); }); - }); - - it("should be able to process high data volumes", function (done) { - this.timeout(7000); - var data = { foo: "foo", people: []}; - for (var i = 0; i < 2000000; i++) { - data.people.push(i); - } - scriptsManager.execute(data, {execModulePath: path.join(__dirname, "scripts", "script.js")}, function (err, res) { - if (err) - return done(err); - - res.foo.should.be.eql("foo"); - done(); - }); - }); + } });