UNPKG

mongoose-diff-tracking

Version:
276 lines (241 loc) 7.99 kB
"use strict"; var Logger = require('../../connection/logger') , EventEmitter = require('events').EventEmitter , inherits = require('util').inherits , f = require('util').format; /** * Creates a new Ping read preference strategy instance * @class * @param {number} [options.pingInterval=5000] Ping interval to check the response time to the different servers * @param {number} [options.acceptableLatency=250] Acceptable latency for selecting a server for reading (in milliseconds) * @return {Ping} A cursor instance */ var Ping = function(options) { // Add event listener EventEmitter.call(this); // Contains the ping state this.s = { // Contains all the ping data pings: {} // Set no options if none provided , options: options || {} // Logger , logger: Logger('Ping', options) // Ping interval , pingInterval: options.pingInterval || 10000 , acceptableLatency: options.acceptableLatency || 15 // Debug options , debug: typeof options.debug == 'boolean' ? options.debug : false // Index , index: 0 // Current ping time , lastPing: null } // Log the options set if(this.s.logger.isDebug()) this.s.logger.debug(f('ping strategy interval [%s], acceptableLatency [%s]', this.s.pingInterval, this.s.acceptableLatency)); // If we have enabled debug if(this.s.debug) { // Add access to the read Preference Strategies Object.defineProperty(this, 'data', { enumerable: true, get: function() { return this.s.pings; } }); } } inherits(Ping, EventEmitter); /** * @ignore */ var filterByTags = function(readPreference, servers) { if(readPreference.tags == null) return servers; var filteredServers = []; var tags = readPreference.tags; // Iterate over all the servers for(var i = 0; i < servers.length; i++) { var serverTag = servers[i].lastIsMaster().tags || {}; // Did we find the a matching server var found = true; // Check if the server is valid for(var name in tags) { if(serverTag[name] != tags[name]) found = false; } // Add to candidate list if(found) filteredServers.push(servers[i]); } // Returned filtered servers return filteredServers; } /** * Pick a server * @method * @param {State} set The current replicaset state object * @param {ReadPreference} readPreference The current readPreference object * @param {readPreferenceResultCallback} callback The callback to return the result from the function * @return {object} */ Ping.prototype.pickServer = function(set, readPreference) { var self = this; // Only get primary and secondaries as seeds var seeds = {}; var servers = []; if(set.primary) { servers.push(set.primary); } for(var i = 0; i < set.secondaries.length; i++) { servers.push(set.secondaries[i]); } // Filter by tags servers = filterByTags(readPreference, servers); // Transform the list var serverList = []; // for(var name in seeds) { for(var i = 0; i < servers.length; i++) { serverList.push({name: servers[i].name, time: self.s.pings[servers[i].name] || 0}); } // Sort by time serverList.sort(function(a, b) { return a.time > b.time; }); // Locate lowest time (picked servers are lowest time + acceptable Latency margin) var lowest = serverList.length > 0 ? serverList[0].time : 0; // Filter by latency serverList = serverList.filter(function(s) { return s.time <= lowest + self.s.acceptableLatency; }); // No servers, default to primary if(serverList.length == 0 && set.primary) { if(self.s.logger.isInfo()) self.s.logger.info(f('picked primary server [%s]', set.primary.name)); return set.primary; } else if(serverList.length == 0) { return null } // We picked first server if(self.s.logger.isInfo()) self.s.logger.info(f('picked server [%s] with ping latency [%s]', serverList[0].name, serverList[0].time)); // Add to the index self.s.index = self.s.index + 1; // Select the index self.s.index = self.s.index % serverList.length; // Return the first server of the sorted and filtered list return set.get(serverList[self.s.index].name); } /** * Start of an operation * @method * @param {Server} server The server the operation is running against * @param {object} query The operation running * @param {Date} date The start time of the operation * @return {object} */ Ping.prototype.startOperation = function(server, query, date) { } /** * End of an operation * @method * @param {Server} server The server the operation is running against * @param {error} err An error from the operation * @param {object} result The result from the operation * @param {Date} date The start time of the operation * @return {object} */ Ping.prototype.endOperation = function(server, err, result, date) { } /** * High availability process running * @method * @param {State} set The current replicaset state object * @param {resultCallback} callback The callback to return the result from the function * @return {object} */ Ping.prototype.ha = function(topology, state, callback) { var self = this; var servers = state.getAll(); var count = servers.length; // No servers return if(servers.length == 0) return callback(null, null); // Return if we have not yet reached the ping interval if(self.s.lastPing != null) { var diff = new Date().getTime() - self.s.lastPing.getTime(); if(diff < self.s.pingInterval) return callback(null, null); } // Execute operation var operation = function(_server) { var start = new Date(); // Execute ping against server _server.command('system.$cmd', {ismaster:1}, function(err, r) { count = count - 1; var time = new Date().getTime() - start.getTime(); self.s.pings[_server.name] = time; // Log info for debug if(self.s.logger.isDebug()) self.s.logger.debug(f('ha latency for server [%s] is [%s] ms', _server.name, time)); // We are done with all the servers if(count == 0) { // Emit ping event topology.emit('ping', err, r ? r.result : null); // Update the last ping time self.s.lastPing = new Date(); // Return callback(null, null); } }); } // Let's ping all servers while(servers.length > 0) { operation(servers.shift()); } } var removeServer = function(self, server) { delete self.s.pings[server.name]; } /** * Server connection closed * @method * @param {Server} server The server that closed */ Ping.prototype.close = function(server) { removeServer(this, server); } /** * Server connection errored out * @method * @param {Server} server The server that errored out */ Ping.prototype.error = function(server) { removeServer(this, server); } /** * Server connection timeout * @method * @param {Server} server The server that timed out */ Ping.prototype.timeout = function(server) { removeServer(this, server); } /** * Server connection happened * @method * @param {Server} server The server that connected * @param {resultCallback} callback The callback to return the result from the function */ Ping.prototype.connect = function(server, callback) { var self = this; // Get the command start date var start = new Date(); // Execute ping against server server.command('system.$cmd', {ismaster:1}, function(err, r) { var time = new Date().getTime() - start.getTime(); self.s.pings[server.name] = time; // Log info for debug if(self.s.logger.isDebug()) self.s.logger.debug(f('connect latency for server [%s] is [%s] ms', server.name, time)); // Set last ping self.s.lastPing = new Date(); // Done, return callback(null, null); }); } /** * This is a result from a readPreference strategy * * @callback readPreferenceResultCallback * @param {error} error An error object. Set to null if no error present * @param {Server} server The server picked by the strategy */ module.exports = Ping;