UNPKG

core

Version:

Core Libs

288 lines (236 loc) 8.49 kB
var Pool = require('../pattern/Pool.js').Pool; var PGClient = require('pg').native.Client; var CursorQueryWorker = require('./pg/CursorQueryWorker.js').CursorQueryWorker; var PGPool = function (config) { Pool.call(this); this._queuedQueries = []; if (config) { if (config.hasOwnProperty('num_connections')) { this.setMaxLength(config.num_connections); delete config.num_connections; } else { this.setMaxLength(PGPool.DEFAULT_CONN_COUNT); } this.setConnectionOptions(config); } }; PGPool.prototype = Object.create(Pool.prototype); PGPool.prototype.connect = function () { this._handleConnectionCount(); }; PGPool.prototype.getMaxLength = function () { return this._num_connections; }; PGPool.prototype.setMaxLength = function (count) { return this._num_connections = count; }; PGPool.prototype.getConnectionOptions = function () { return this._config; }; PGPool.prototype.setConnectionOptions = function (config) { return this._config = config; }; PGPool.prototype._handleConnectionCount = function () { var connections = this.getMaxLength() - this.getKnownLength(); if (connections > 0) { console.log('PGPool:', 'connection shortage:', connections); this._createConnections(connections); } else if (connections < 0) { connections = -connections; console.info('PGPool:', 'connection overhead:', connections); this._removeConnections(connections); } this._executeQueuedQueries(); }; PGPool.prototype._createConnections = function (n) { var toCount = n; var that = this; var isError = false; var onConnection = function (err) { if (err) { isError = true; } /* semafór */ if (--toCount) return; /* *** */ if (isError) { that.scheduleRenew(); } }; for (var i = 0; i < n; i++) { this._createConnection(this.getConnectionOptions(), onConnection); } }; PGPool.prototype._removeConnections = function (n) { var that = this; for (var i = 0; i < n; i++) { if (this.getKnownLength() == 0) { break; } this.get(function (client) { that._removeClient(client); }); } }; PGPool.prototype._createConnection = function (options, callback) { var host = options.host; console.log('PGPool:', 'creating connection to host:', host); var that = this; var client = new PGClient(options); /* timeout connectu do PostgreSQL */ var connectionTimeouted = false; var connectTimeout = setTimeout(function () { console.warn('PGPool:', 'connect timeout error', PGPool.CONNECT_TIMEOUT, 'ms', 'host:', host); connectionTimeouted = true; that._removeClient(client); callback(new Error('PostgreSQL Connection error - operation timeout')); }, PGPool.CONNECT_TIMEOUT); var connectError = function (err) { console.warn('PGPool:', 'connect host:', host, 'error:', err); /* sprawdzam czy jestem po timeoucie */ if (connectionTimeouted) return; /* czyszcze timeout - udalo sie zakonczyc faze polączenia */ clearTimeout(connectTimeout); that._removeClient(client); callback(err); }; var connectSuccess = function () { console.info('PGPool:', 'connection established host:', host); /* usuwam listener */ client.removeListener('error', connectError); /* sprawdzam czy jestem po timeoucie */ if (connectionTimeouted) return; /* czyszcze timeout - udalo sie zakonczyc faze polączenia */ clearTimeout(connectTimeout); client.on('error', function (err) { console.warn('PGPool:', 'error event', err); that._removeClient(client); }); callback(null); }; client.once('connect', connectSuccess); /* event połaczenia */ client.once('error' , connectError); /* event błedu połaczenia */ client.connect(); this.add(client); }; PGPool.prototype._removeClient = function (client) { client.end(); /* zamykam połaczenie */ this.markForDeletion(client); /* oznaczam do usunięcia */ this.release(client); /* zawalniam klienta */ this.scheduleRenew(); /* kolejkuje odswiezenie */ }; PGPool.prototype.scheduleRenew = function () { if (this._renewTimer) return; var that = this; this._renewTimer = setTimeout(function () { that._renewTimer = null; that._handleConnectionCount(); }, 1000); }; PGPool.prototype.query = function (sql, params, callback, retryCount) { retryCount = retryCount+1 || 1; var that = this; if (callback == undefined) { callback = params; params = null; } this.get(function (client) { client.query(sql, params, function (err, result) { if (err && that._isFatalError(err)) { console.warn('PGPool:', 'fatal query error', err); that._removeClient(client); if (retryCount < PGPool.QUERY_MAX_RETRY) { that._enqueueQuery([that.query, sql, params, callback, retryCount]); } else { callback(err, result); } } else { if (err) { console.info('PGPool:', 'query error', err); } that.release(client); callback(err, result); } }); }); }; PGPool.prototype.queryWithCursors = function (sql, params, cursors, callback, retryCount) { retryCount = retryCount+1 || 1; var that = this; this.get(function (client) { var worker = new CursorQueryWorker(client); worker.addEventListener(CursorQueryWorker.Event.LOADED, function (event) { that.release(client); /* zwalniam klienta */ callback(null, event.data); /* odsyłam dane */ }); worker.addEventListener(CursorQueryWorker.Event.ERROR, function (event) { var error = event.data; if (that._isFatalError(error)) { console.warn('PGPool:', 'fatal queryWithCursors error', error); that._removeClient(client); if (retryCount < PGPool.QUERY_MAX_RETRY) { that._enqueueQuery([that.queryWithCursors, sql, params, cursors, callback, retryCount]); } else { callback(error, null); } } else { console.info('PGPool:', 'queryWithCursors error', error); that.release(client); /* zwalniam klienta */ callback(error, null); } }); worker.query(sql, params, cursors); }); }; PGPool.prototype._enqueueQuery = function (params) { var that = this; this._queuedQueries.push(params); if (!this._queueTimeout) { this._queueTimeout = setTimeout(function () { that._executeQueuedQueries(); }, PGPool.QUERY_RETRY_TIME); } }; PGPool.prototype._isFatalError = function (error) { var fatals = { 'severity': ['FATAL'], 'code': ['EPIPE'], 'message': ['connection pointer is NULL\n'] } if (error) { for (var fatal in fatals) { if (fatals.hasOwnProperty(fatal)) { if (error.hasOwnProperty(fatal) && fatals[fatal].indexOf(error[fatal]) != -1) { return true; } } } } return false; }; PGPool.prototype._executeQueuedQueries = function () { var length = this._queuedQueries.length; var query; var fnc; if (this._queueTimeout) { clearTimeout(this._queueTimeout); this._queueTimeout = null; } if (length > 0) { length = length > this.getSpareLength() ? this.getSpareLength() : length; for (var i = 0; i < length; i++) { query = this._queuedQueries.shift(); fnc = query.shift(); fnc.apply(this, query); } } }; PGPool.prototype.destroy = function () { clearTimeout(this._queueTimeout); clearTimeout(this._renewTimer); }; PGPool.DEFAULT_CONN_COUNT = 5; PGPool.CONNECT_TIMEOUT = 1000; PGPool.QUERY_MAX_RETRY = 5; PGPool.QUERY_RETRY_TIME = 100; exports.PGPool = PGPool;