mongoose-diff-tracking
Version:
Manage Mongo Collection diff History and versions
615 lines (527 loc) • 19.6 kB
JavaScript
"use strict";
var inherits = require('util').inherits
, EventEmitter = require('events').EventEmitter
, Connection = require('./connection')
, Query = require('./commands').Query
, Logger = require('./logger')
, f = require('util').format;
var DISCONNECTED = 'disconnected';
var CONNECTING = 'connecting';
var CONNECTED = 'connected';
var DESTROYED = 'destroyed';
var _id = 0;
/**
* Creates a new Pool instance
* @class
* @param {string} options.host The server host
* @param {number} options.port The server port
* @param {number} [options.size=1] Max server connection pool size
* @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
* @param {number} [options.keepAliveInitialDelay=0] Initial delay before TCP keep alive enabled
* @param {boolean} [options.noDelay=true] TCP Connection no delay
* @param {number} [options.connectionTimeout=0] TCP Connection timeout setting
* @param {number} [options.socketTimeout=0] TCP Socket timeout setting
* @param {boolean} [options.singleBufferSerializtion=true] Serialize into single buffer, trade of peak memory for serialization speed
* @param {boolean} [options.ssl=false] Use SSL for connection
* @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
* @param {Buffer} [options.ca] SSL Certificate store binary buffer
* @param {Buffer} [options.cert] SSL Certificate binary buffer
* @param {Buffer} [options.key] SSL Key file binary buffer
* @param {string} [options.passPhrase] SSL Certificate pass phrase
* @param {boolean} [options.rejectUnauthorized=false] Reject unauthorized server certificates
* @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
* @fires Pool#connect
* @fires Pool#close
* @fires Pool#error
* @fires Pool#timeout
* @fires Pool#parseError
* @return {Pool} A cursor instance
*/
var Pool = function(options) {
var self = this;
// Add event listener
EventEmitter.call(this);
// Set empty if no options passed
this.options = options || {};
this.size = typeof options.size == 'number' && !isNaN(options.size) ? options.size : 5;
this.waitMS = typeof options.waitMS == 'number' && !isNaN(options.waitMS) ? options.waitMS : 1000;
// Save host and port
this.host = options.host;
this.port = options.port;
// Message handler
this.messageHandler = options.messageHandler;
// No bson parser passed in
if(!options.bson) throw new Error("must pass in valid bson parser");
// Contains all connections
this.connections = [];
// Contains all available connections
this.availableConnections = [];
this.inUseConnections = [];
this.newConnections = [];
this.connectingConnections = [];
// Current status of the pool
this.state = DISCONNECTED;
// Round robin index
this.index = 0;
this.dead = false;
// Logger instance
this.logger = Logger('Pool', options);
// If we are monitoring this server we will create an exclusive reserved socket for that
this.monitoring = typeof options.monitoring == 'boolean' ? options.monitoring : false;
// Maintain the monitoring connection
this.monitorConnection = null;
// Pool id
this.id = _id++;
// Grouping tag used for debugging purposes
this.tag = options.tag;
// Operation work queue
this.queue = [];
// Currently executing
this.executing = false;
}
inherits(Pool, EventEmitter);
var removeConnection = function(self, connection) {
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! number Of connection :: " + self.getAll().length)
var remove = function(connections) {
for(var i = 0; i < connections.length; i++) {
if(connections[i] === connection) {
// console.log("!!!!!!!!!!!!! removing the connection")
connections.splice(i, 1);
return true;
}
}
}
// self.availableConnections.forEach(function(x) {
// console.log(x === connection)
// })
//
// console.log("availableConnections :: " + self.availableConnections.length);
// console.log("inUseConnections :: " + self.inUseConnections.length);
// console.log("newConnections :: " + self.newConnections.length);
// console.log("connectingConnections :: " + self.connectingConnections.length);
// Set the monitoring connection to undefined
if(self.monitorConnection === connection) {
self.monitorConnection = null;
}
// Clean out the connection
if(remove(self.availableConnections)) return;
if(remove(self.inUseConnections)) return;
if(remove(self.newConnections)) return;
if(remove(self.connectingConnections)) return;
}
var errorHandler = function(self) {
return function(err, connection) {
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! error POOL")
if(self.logger.isDebug()) self.logger.debug(f('pool [%s] errored out [%s] with connection [%s]', this.dead, JSON.stringify(err), JSON.stringify(connection)));
// Remove the connection
removeConnection(self, connection);
// Emit error
if(self.listeners('error').length > 0) {
self.emit('error', err, connection);
}
}
}
var timeoutHandler = function(self) {
return function(err, connection) {
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! timeout POOL :: " + self.port)
// console.log("----------------------- pool.write _execute 0 :: availableConnections = " + self.availableConnections.length)
// console.log("----------------------- pool.write _execute 0 :: inUseConnections = " + self.inUseConnections.length)
// console.log("----------------------- pool.write _execute 0 :: newConnections = " + self.newConnections.length)
// console.log("----------------------- pool.write _execute 0 :: queue = " + self.queue.length)
// // Get all connections
// self.availableConnections.forEach(function(x) {
// if(x === self.monitorConnection) {
// // console.log("!!!!!!!!!!!!!!!!!!!!!!!!! MONITORING CONNECTION")
// }
// })
if(self.logger.isDebug()) self.logger.debug(f('pool [%s] timed out [%s] with connection [%s]', this.dead, JSON.stringify(err), JSON.stringify(connection)));
// Remove the connection
removeConnection(self, connection);
// Emit connection timeout to server instance
self.emit('timeout', err, connection);
}
}
var closeHandler = function(self) {
return function(err, connection) {
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! close POOL")
// if(connection === self.monitorConnection){
// console.log(" monitorConnection closed")
// }
if(self.logger.isDebug()) self.logger.debug(f('pool [%s] closed [%s] with connection [%s]', this.dead, JSON.stringify(err), JSON.stringify(connection)));
// Remove the connection
removeConnection(self, connection);
// Emit connection close to server instance
self.emit('close', err, connection);
}
}
var parseErrorHandler = function(self) {
return function(err, connection) {
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! parseError POOL")
if(self.logger.isDebug()) self.logger.debug(f('pool [%s] errored out [%s] with connection [%s]', this.dead, JSON.stringify(err), JSON.stringify(connection)));
// Remove the connection
removeConnection(self, connection);
// Emit error to server instance
self.emit('parseError', err, connection);
}
}
/**
* Destroy pool
* @method
*/
Pool.prototype.destroy = function() {
this.state = DESTROYED;
// Set dead
this.dead = true;
// Get all the connections
var connections = this.getAll();
// Destroy all the connections
connections.forEach(function(c) {
// Destroy all event emitters
["close", "message", "error", "timeout", "parseError", "connect"].forEach(function(e) {
c.removeAllListeners(e);
});
// Destroy the connection
c.destroy();
});
// Do we have a monitoring connection
if(this.monitorConnection) {
this.monitorConnection.destroy();
}
}
var execute = null;
try {
execute = setImmediate;
} catch(err) {
execute = process.nextTick;
}
// execute = function(x) {
// setTimeout(x, 1)
// }
// execute = setTimeout;
/**
* Connect pool
* @method
*/
Pool.prototype.connect = function(_options) {
// console.log("---------------------- CONNECT")
var self = this;
// Set to connecting
this.state = CONNECTING
// No dead
this.dead = false;
// Ensure we allow for a little time to setup connections
var wait = 1;
// Number of initial connections to perform
var numberOfConnections = this.monitoring ? 2 : 1;
// Connect all sockets
for(var i = 0; i < numberOfConnections; i++) {
setTimeout(function() {
execute(function() {
self.options.messageHandler = self.messageHandler;
var connection = new Connection(self.options);
// Add all handlers
connection.once('close', closeHandler(self));
connection.once('error', errorHandler(self));
connection.once('timeout', timeoutHandler(self));
connection.once('parseError', parseErrorHandler(self));
connection.on('connect', function(connection) {
// Add the connection to the list of available connections
self.availableConnections.push(connection);
// Have we finished the initial connection
if(self.availableConnections.length == numberOfConnections) {
// Reserve a monitoring socket
if(self.monitoring) {
self.monitorConnection = self.availableConnections.pop();
}
// Done connecting
self.emit("connect", self);
}
});
// Start connection
connection.connect(_options);
});
}, wait);
// wait for 1 miliseconds before attempting to connect, spacing out connections
wait = wait + 1;
}
}
var _createConnection = function(self) {
self.options.messageHandler = self.messageHandler;
var connection = new Connection(self.options);
// Push the connection
self.connectingConnections.push(connection);
// Handle any errors
var tempErrorHandler = function(err) {
// self.emit('error', err);
}
// All event handlers
var handlers = ["close", "message", "error", "timeout", "parseError", "connect"];
// Handle successful connection
var tempConnectHandler = function() {
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! tempConnectHandler 0")
// Destroy all event emitters
handlers.forEach(function(e) {
connection.removeAllListeners(e);
});
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! tempConnectHandler 1")
// Add the final handlers
connection.once('close', closeHandler(self));
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! tempConnectHandler 3")
connection.once('error', errorHandler(self));
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! tempConnectHandler 4")
connection.once('timeout', timeoutHandler(self));
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! tempConnectHandler 5")
connection.once('parseError', parseErrorHandler(self));
// Remove the connection from the connectingConnections
var index = self.connectingConnections.indexOf(connection);
if(index != -1) {
self.connectingConnections.splice(index, 1);
}
// connection.destroy()
// Add to queue of new connection
self.newConnections.push(connection);
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! tempConnectHandler 8")
// Emit connection to server instance
// alowing it to apply any needed authentication
self.emit('connection', connection);
// Execute any work waiting
_execute(self)();
}
// Add all handlers
connection.once('close', tempErrorHandler);
connection.once('error', tempErrorHandler);
connection.once('timeout', tempErrorHandler);
connection.once('parseError', tempErrorHandler);
connection.once('connect', tempConnectHandler);
// Start connection
connection.connect();
}
var _execute = function(self) {
return function() {
if(self.state == 'DESTROYED') return;
// Already executing, skip
if(self.executing) return;
// Set pool as executing
self.executing = true;
// console.log("----------------------- _execute")
// console.log("----------------------- pool.write _execute 0 :: availableConnections = " + self.availableConnections.length)
// console.log("----------------------- pool.write _execute 0 :: inUseConnections = " + self.inUseConnections.length)
// console.log("----------------------- pool.write _execute 0 :: newConnections = " + self.newConnections.length)
// console.log("----------------------- pool.write _execute 0 :: queue = " + self.queue.length)
// Total availble connections
var totalConnections = self.availableConnections.length
+ self.connectingConnections.length
+ self.inUseConnections.length
+ self.newConnections.length;
// Have we not reached the max connection size yet
if(self.availableConnections.length == 0
&& self.connectingConnections.length == 0
&& totalConnections < self.size
&& self.queue.length > 0) {
// Create a new connection
_createConnection(self);
// Attempt to execute again
self.executing = false;
return;
}
// Number of ops to do
var numberOfOps = self.availableConnections.length > self.queue.length
? self.queue.length : self.availableConnections.length;
// As long as we have available connections
while(true) {
// No available connections available
if(self.availableConnections.length == 0) break;
if(self.queue.length == 0) break;
// Get a connection
var connection = self.availableConnections.pop();
if(connection.isConnected()) {
var workItem = self.queue.shift();
// Add connection to callback so we can flush out
// only ops for that connection on a socket closure
if(workItem.cb) {
workItem.cb.connection = connection;
}
// Get actual binary commands
var buffer = workItem.buffer;
// Add connection to workers in flight
if(connection !== self.monitorConnection) {
self.inUseConnections.push(connection);
}
// console.log("---------- write to connection " + connection.id)
if(Array.isArray(buffer)) {
for(var i = 0; i < buffer.length; i++) {
connection.write(buffer[i]);
}
} else {
connection.write(buffer);
}
// Fire and forgot message
if(workItem.immediateRelease) {
self.availableConnections.push(connection);
}
}
}
self.executing = false;
}
}
/**
* Write a message to MongoDB
* @method
* @return {Connection}
*/
Pool.prototype.write = function(buffer, cb, options) {
// We have a monitoring operation and need to use
// the dedicated connection
if(options && options.monitoring && !this.monitorConnection) {
this.monitorConnection = this.availableConnections.pop();
}
if(options && options.monitoring && this.monitorConnection) {
return this.monitorConnection.write(buffer);
}
// Do we have an operation
var operation = {buffer:buffer, cb: cb};
// Do we immediately release the connection back to available (fire and forget)
if(options && options.immediateRelease) {
operation.immediateRelease = true;
}
// Push the operation to the queue of operations in progress
this.queue.push(operation);
// Attempt to write all buffers out
_execute(this)();
}
/**
* Make a passed connection available
* @method
* @return {Connection}
*/
Pool.prototype.connectionAvailable = function(connection) {
// console.log("@@@@@@ connectionAvailable :: " + connection.id)
// Get the connection from the newConnections
var index = this.newConnections.indexOf(connection);
if(index != -1) {
this.newConnections.splice(index, 1);
}
// If it's in the inUseConnections
index = this.inUseConnections.indexOf(connection);
if(index != -1) {
this.inUseConnections.splice(index, 1);
}
// Add the connection to available connections if it's not a monitoring threads
if(this.availableConnections.indexOf(connection) == -1
&& connection !== this.monitorConnection) {
this.availableConnections.push(connection);
}
// Fire execute loop
_execute(this)();
}
/**
* Get a pool connection (round-robin)
* @method
* @return {Connection}
*/
Pool.prototype.get = function(options) {
options = options || {};
// Set the current index
this.index = this.index + 1;
// Get all connections
var connections = this.availableConnections.slice(0);
if(connections.length == 1) {
return connections[0];
} else {
this.index = this.index % connections.length;
return connections[this.index];
}
}
/**
* Reduce the poolSize to the provided max connections value
* @method
* @param {number} maxConnections reduce the poolsize to maxConnections
*/
Pool.prototype.capConnections = function(maxConnections) {
// Do we have more connections than specified slice it
if(this.connections.length > maxConnections) {
// Get the rest of the connections
var connections = this.connections.slice(maxConnections);
// Cap the active connections
this.connections = this.connections.slice(0, maxConnections);
if (this.index >= maxConnections){
// Go back to the beggining of the pool if capping connections
this.index = 0;
}
// Remove all listeners
for(var i = 0; i < connections.length; i++) {
connections[i].removeAllListeners('close');
connections[i].removeAllListeners('error');
connections[i].removeAllListeners('timeout');
connections[i].removeAllListeners('parseError');
connections[i].removeAllListeners('connect');
connections[i].destroy();
}
}
}
/**
* Get all pool connections
* @method
* @return {array}
*/
Pool.prototype.getAll = function() {
return this.availableConnections
.concat(this.inUseConnections)
.concat(this.connectingConnections)
.concat(this.newConnections);
}
/**
* Is the pool connected
* @method
* @return {boolean}
*/
Pool.prototype.isConnected = function() {
// Available connections
for(var i = 0; i < this.availableConnections.length; i++) {
if(this.availableConnections[i].isConnected()) return true;
}
// inUseConnections
for(var i = 0; i < this.inUseConnections.length; i++) {
if(this.inUseConnections[i].isConnected()) return true;
}
return this.state == CONNECTED;
}
/**
* Was the pool destroyed
* @method
* @return {boolean}
*/
Pool.prototype.isDestroyed = function() {
return this.state == DESTROYED;
}
/**
* A server connect event, used to verify that the connection is up and running
*
* @event Pool#connect
* @type {Pool}
*/
/**
* The server connection closed, all pool connections closed
*
* @event Pool#close
* @type {Pool}
*/
/**
* The server connection caused an error, all pool connections closed
*
* @event Pool#error
* @type {Pool}
*/
/**
* The server connection timed out, all pool connections closed
*
* @event Pool#timeout
* @type {Pool}
*/
/**
* The driver experienced an invalid message, all pool connections closed
*
* @event Pool#parseError
* @type {Pool}
*/
module.exports = Pool;