UNPKG

node-pool-cluster

Version:

Pooling generic resources for multi-server environment

186 lines (174 loc) 7.79 kB
var GenericPool = require('generic-pool'); var _ = require('underscore'); module.exports = { /** * Create a cluster of pools with a specified poolFactoryOpts. * * poolFactoryOpts object is similar to generic-pool module factory object, * except that it doesn't include create method. * poolFactoryOpts docs is a replica of generic-pool module docs: * * @param {Object} poolFactoryOpts * Factory to be used for generating and destorying the items. * @param {String} poolFactoryOpts.name * Name of the factory. Serves only logging purposes. * @param {Function} poolFactoryOpts.destroy * Should gently close any resources that the item is using. * Called before the items is destroyed. * @param {Function} poolFactoryOpts.validate * Should return true if connection is still valid and false * If it should be removed from pool. Called before item is * acquired from pool. * @param {Number} poolFactoryOpts.max * Maximum number of items that can exist at the same time. Default: 1. * Any further acquire requests will be pushed to the waiting list. * @param {Number} poolFactoryOpts.min * Minimum number of items in pool (including in-use). Default: 0. * When the pool is created, or a resource destroyed, this minimum will * be checked. If the pool resource count is below the minimum, a new * resource will be created and added to the pool. * @param {Number} poolFactoryOpts.idleTimeoutMillis * Delay in milliseconds after the idle items in the pool will be destroyed. * And idle item is that is not acquired yet. Waiting items doesn't count here. * @param {Number} poolFactoryOpts.reapIntervalMillis * Cleanup is scheduled in every `factory.reapIntervalMillis` milliseconds. * @param {Boolean|Function} poolFactoryOpts.log * Whether the pool should log activity. If function is specified, * that will be used instead. The function expects the arguments msg, loglevel * @param {Number} poolFactoryOpts.priorityRange * The range from 1 to be treated as a valid priority * @param {RefreshIdle} poolFactoryOpts.refreshIdle * Should idle resources be destroyed and recreated every idleTimeoutMillis? Default: true. * @param {Boolean} acquireNewPoolOnError * Should Optional check for other pools in cluster when error occurs acquiring connection in current pool. * @param {Boolean} roundRobin * pools rotated in roundRobin fashion. * * @returns {Object} A Cluster Pool object. */ initCluster: function (poolFactoryOpts, acquireNewPoolOnError, roundRobin) { var _this = {}, _cluster = []; /** * Adds server to the cluster. * * @param {Function} createClient Should create the item to be acquired, * and call it's first callback argument with the generated item as its * argument. */ _this.addPool = function (createClient) { var _pool, _poolOptions = _.clone(poolFactoryOpts); _poolOptions.create = createClient; _pool = GenericPool.Pool(_poolOptions); _cluster.push(_pool); return _pool; }; /** * Removes server from the cluster. * * @param {Object} pool it should be the same server pool, which * was returned when adding the server to the cluster. * @return {Object} true, if server is successfully removed from cluster, else false. */ _this.removePool = function (pool) { var index = _.indexOf(_cluster, pool); if (index != -1) { _cluster.splice(index, 1); pool.drain(function () { pool.destroyAllNow(); }); return true; } return false; }; /** * Return total number of created clients in all pools. * * @return {Number} Number of client connections. */ _this.getPoolSize = function () { var count = 0; _cluster.forEach(function (pool) { count += pool.getPoolSize(); }); return count; }; /** * Removes all the server pools from cluster */ _this.clear = function () { _cluster.forEach(function (pool) { pool.drain(function () { pool.destroyAllNow(); }); }); _cluster = []; }; /** * Drains all the pools in the cluster */ _this.drainAll = function () { _cluster.forEach(function (pool) { pool.drain(function () { pool.destroyAllNow(); }); }); }; /** * Requests a new client from cluster pool. The callback will be called * when a new client will be available. * Selected pool will also be passed so that you can release the client * to the pool after use. * * @param {Function} callback(err, client, pool) * Callback function to be called after the acquire is successful. * @returns {Object} true if pools are not fully utilized, false otherwise. */ _this.acquire = function (callback) { if (_cluster.length === 0) { return callback(new Error('No pool server added yet')); } var _poolIndex = 0, _currentPool, _poolMinWaitClient; if (roundRobin && _this.__lastIndex) { _poolIndex = _this.__lastIndex % _cluster.length; } var _deadPools = []; if(acquireNewPoolOnError && arguments.length >=2 ){ _deadPools = arguments[1]; } /** * Selecting suitable pool to handle request. * If there's no waiting queue of requests awaiting to acquire connection * then pools are round-robin rotated. * If there's a queue, pool with the shortest queue is selected. */ while (_poolIndex < _cluster.length && _cluster.length > _deadPools.length) { _currentPool = _cluster[_poolIndex]; if (_deadPools.indexOf(_currentPool) == -1) { if (roundRobin || _currentPool.waitingClientsCount() == 0) { _poolMinWaitClient = _currentPool; _poolIndex++; break; } else { if (!_poolMinWaitClient || (_currentPool.waitingClientsCount() < _poolMinWaitClient.waitingClientsCount())) { _poolMinWaitClient = _currentPool; //pool with shortest queue } } } _poolIndex++; } _this.__lastIndex = _poolIndex; return !!_poolMinWaitClient && _poolMinWaitClient.acquire(function (err, client) { // Optional check for other pools in cluster // when error occurs acquiring connection in current pool if(acquireNewPoolOnError && !!err && _cluster.length > 1){ _deadPools.push(_poolMinWaitClient); _poolMinWaitClient.release(client); return _this.acquire(callback, _deadPools); } return callback(err, client, _poolMinWaitClient); }); }; return _this; } };