diff --git a/README.md b/README.md index a2fa17f..11a164f 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ If you don't provide an `options` object then the following defaults will be use , maxConcurrentCalls : Infinity , maxCallTime : Infinity , maxRetries : Infinity + , autoStart : false } ``` @@ -124,6 +125,8 @@ If you don't provide an `options` object then the following defaults will be use * **maxRetries** allows you to control the max number of call requeues after worker termination (unexpected or timeout). By default this option is set to `Infinity` which means that each call of each terminated worker will always be auto requeued. When the number of retries exceeds `maxRetries` value, the job callback will be executed with a `ProcessTerminatedError`. Note that if you are running with finite `maxCallTime` and `maxConcurrentCallsPerWorkers` greater than `1` then any `TimeoutError` will increase the retries counter *for each* concurrent call of the terminated worker. + * **autoStart** when set to `true` will start the workers as early as possible. Use this when your workers have to do expensive initialization. That way they'll be ready when the first request comes through. + ### workerFarm.end(farm) Child processes stay alive waiting for jobs indefinitely and your farm manager will stay alive managing its workers, so if you need it to stop then you have to do so explicitly. If you send your farm API to `workerFarm.end()` then it'll cleanly end your worker processes. Note though that it's a *soft* ending so it'll wait for child processes to finish what they are working on before asking them to die. diff --git a/lib/farm.js b/lib/farm.js index bd932ec..55697be 100644 --- a/lib/farm.js +++ b/lib/farm.js @@ -6,6 +6,7 @@ const DEFAULT_OPTIONS = { , maxCallTime : Infinity // exceed this and the whole worker is terminated , maxRetries : Infinity , forcedKillTime : 100 + , autoStart : false } const extend = require('xtend') @@ -57,6 +58,11 @@ Farm.prototype.setup = function (methods) { this.activeChildren = 0 this.callQueue = [] + if (this.options.autoStart) { + while (this.activeChildren < this.options.maxConcurrentWorkers) + this.startChild() + } + return iface } diff --git a/tests/child.js b/tests/child.js index 39b2146..a73ad25 100644 --- a/tests/child.js +++ b/tests/child.js @@ -49,4 +49,9 @@ module.exports.stubborn = function (path, callback) { fs.writeFileSync(path, String(retry + 1)) process.exit(-1) } -} \ No newline at end of file +} + +var started = Date.now() +module.exports.uptime = function(callback) { + callback(null, Date.now() - started) +} diff --git a/tests/index.js b/tests/index.js index e984ff8..82c5a6b 100644 --- a/tests/index.js +++ b/tests/index.js @@ -118,6 +118,26 @@ tape('many workers', function (t) { }) }) +tape('auto start workers', function (t) { + t.plan(4) + + var child = workerFarm({ maxConcurrentWorkers: 3, autoStart: true }, childPath, ['uptime']) + , pids = [] + , i = 3 + , delay = 150 + + setTimeout(function() { + while (i--) + child.uptime(function (err, uptime) { + t.ok(uptime > 10, 'child has been up before the request') + }) + + workerFarm.end(child, function () { + t.ok(true, 'workerFarm ended') + }) + }, delay) +}) + // use the returned pids to check that we're using a child process per // call when we set maxCallsPerWorker = 1 even when we have maxConcurrentWorkers = 1 tape('single call per worker', function (t) {