/node_modules/mongodb/lib/mongodb/connection/repl_set/strategies/ping_strategy.js
JavaScript | 366 lines | 226 code | 65 blank | 75 comment | 77 complexity | 7e2c6760aeb40ea9282af4fb7200f497 MD5 | raw file
- var Server = require("../../server").Server
- , format = require('util').format;
- // The ping strategy uses pings each server and records the
- // elapsed time for the server so it can pick a server based on lowest
- // return time for the db command {ping:true}
- var PingStrategy = exports.PingStrategy = function(replicaset, secondaryAcceptableLatencyMS) {
- this.replicaset = replicaset;
- this.secondaryAcceptableLatencyMS = secondaryAcceptableLatencyMS;
- this.state = 'disconnected';
- // Interval of ping attempts
- this.pingInterval = replicaset.options.socketOptions.pingInterval || 5000;
- // Timeout for ping response, default - no timeout
- this.pingTimeout = replicaset.options.socketOptions.pingTimeout || null;
- // Class instance
- this.Db = require("../../../db").Db;
- // Active db connections
- this.dbs = {};
- // Current server index
- this.index = 0;
- // Logger api
- this.Logger = null;
- }
- // Starts any needed code
- PingStrategy.prototype.start = function(callback) {
- // already running?
- if ('connected' == this.state) return;
- this.state = 'connected';
- // Start ping server
- this._pingServer(callback);
- }
- // Stops and kills any processes running
- PingStrategy.prototype.stop = function(callback) {
- // Stop the ping process
- this.state = 'disconnected';
- // Stop all the server instances
- for(var key in this.dbs) {
- this.dbs[key].close();
- }
- // optional callback
- callback && callback(null, null);
- }
- PingStrategy.prototype.checkoutConnection = function(tags, secondaryCandidates) {
- // Servers are picked based on the lowest ping time and then servers that lower than that + secondaryAcceptableLatencyMS
- // Create a list of candidat servers, containing the primary if available
- var candidateServers = [];
- var self = this;
- // If we have not provided a list of candidate servers use the default setup
- if(!Array.isArray(secondaryCandidates)) {
- candidateServers = this.replicaset._state.master != null ? [this.replicaset._state.master] : [];
- // Add all the secondaries
- var keys = Object.keys(this.replicaset._state.secondaries);
- for(var i = 0; i < keys.length; i++) {
- candidateServers.push(this.replicaset._state.secondaries[keys[i]])
- }
- } else {
- candidateServers = secondaryCandidates;
- }
- // Final list of eligable server
- var finalCandidates = [];
- // If we have tags filter by tags
- if(tags != null && typeof tags == 'object') {
- // If we have an array or single tag selection
- var tagObjects = Array.isArray(tags) ? tags : [tags];
- // Iterate over all tags until we find a candidate server
- for(var _i = 0; _i < tagObjects.length; _i++) {
- // Grab a tag object
- var tagObject = tagObjects[_i];
- // Matching keys
- var matchingKeys = Object.keys(tagObject);
- // Remove any that are not tagged correctly
- for(var i = 0; i < candidateServers.length; i++) {
- var server = candidateServers[i];
- // If we have tags match
- if(server.tags != null) {
- var matching = true;
- // Ensure we have all the values
- for(var j = 0; j < matchingKeys.length; j++) {
- if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) {
- matching = false;
- break;
- }
- }
- // If we have a match add it to the list of matching servers
- if(matching) {
- finalCandidates.push(server);
- }
- }
- }
- }
- } else {
- // Final array candidates
- var finalCandidates = candidateServers;
- }
- // Filter out any non-connected servers
- finalCandidates = finalCandidates.filter(function(s) {
- return s.isConnected();
- })
- // Sort by ping time
- finalCandidates.sort(function(a, b) {
- return a.runtimeStats['pingMs'] > b.runtimeStats['pingMs'];
- });
- if(0 === finalCandidates.length)
- return new Error("No replica set members available for query");
- // find lowest server with a ping time
- var lowest = finalCandidates.filter(function (server) {
- return undefined != server.runtimeStats.pingMs;
- })[0];
- if(!lowest) {
- lowest = finalCandidates[0];
- }
- // convert to integer
- var lowestPing = lowest.runtimeStats.pingMs | 0;
-
- // determine acceptable latency
- var acceptable = lowestPing + this.secondaryAcceptableLatencyMS;
- // remove any server responding slower than acceptable
- var len = finalCandidates.length;
- while(len--) {
- if(finalCandidates[len].runtimeStats['pingMs'] > acceptable) {
- finalCandidates.splice(len, 1);
- }
- }
- if(self.logger && self.logger.debug) {
- self.logger.debug("Ping strategy selection order for tags", tags);
- finalCandidates.forEach(function(c) {
- self.logger.debug(format("%s:%s = %s ms", c.host, c.port, c.runtimeStats['pingMs']), null);
- })
- }
- // If no candidates available return an error
- if(finalCandidates.length == 0)
- return new Error("No replica set members available for query");
- // Ensure no we don't overflow
- this.index = this.index % finalCandidates.length
- // Pick a random acceptable server
- var connection = finalCandidates[this.index].checkoutReader();
- // Point to next candidate (round robin style)
- this.index = this.index + 1;
- if(self.logger && self.logger.debug) {
- if(connection)
- self.logger.debug(format("picked server %s:%s", connection.socketOptions.host, connection.socketOptions.port));
- }
- return connection;
- }
- PingStrategy.prototype._pingServer = function(callback) {
- var self = this;
- // Ping server function
- var pingFunction = function() {
- // Our state changed to disconnected or destroyed return
- if(self.state == 'disconnected' || self.state == 'destroyed') return;
- // If the replicaset is destroyed return
- if(self.replicaset.isDestroyed() || self.replicaset._serverState == 'disconnected') return
- // Create a list of all servers we can send the ismaster command to
- var allServers = self.replicaset._state.master != null ? [self.replicaset._state.master] : [];
- // Secondary keys
- var keys = Object.keys(self.replicaset._state.secondaries);
- // Add all secondaries
- for(var i = 0; i < keys.length; i++) {
- allServers.push(self.replicaset._state.secondaries[keys[i]]);
- }
- // Number of server entries
- var numberOfEntries = allServers.length;
- // We got keys
- for(var i = 0; i < allServers.length; i++) {
- // We got a server instance
- var server = allServers[i];
- // Create a new server object, avoid using internal connections as they might
- // be in an illegal state
- new function(serverInstance) {
- var _db = self.dbs[serverInstance.host + ":" + serverInstance.port];
- // If we have a db
- if(_db != null) {
- // Startup time of the command
- var startTime = Date.now();
- // Execute ping command in own scope
- var _ping = function(__db, __serverInstance) {
- // Server unavailable. Checks only if pingTimeout defined & greater than 0
- var _failTimer = self.pingTimeout ? setTimeout(function () {
- if(null != __serverInstance.runtimeStats && __serverInstance.isConnected()) {
- __serverInstance.close();
- }
- }, self.pingTimeout) : null;
- // Execute ping on this connection
- __db.executeDbCommand({ping:1}, {failFast:true}, function(err) {
- // Server available
- clearTimeout(_failTimer);
- // Emit the ping
- self.replicaset.emit("ping", err, serverInstance);
- if(err) {
- delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
- __db.close();
- return done();
- }
- if(null != __serverInstance.runtimeStats && __serverInstance.isConnected()) {
- __serverInstance.runtimeStats['pingMs'] = Date.now() - startTime;
- }
- __db.executeDbCommand({ismaster:1}, {failFast:true}, function(err, result) {
- // Emit the ping
- self.replicaset.emit("ping_ismaster", err, result, serverInstance);
- if(err) {
- delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
- __db.close();
- return done();
- }
- // Process the ismaster for the server
- if(result && result.documents && self.replicaset.processIsMaster) {
- self.replicaset.processIsMaster(__serverInstance, result.documents[0]);
- }
- // Done with the pinging
- done();
- });
- });
- };
- // Ping
- _ping(_db, serverInstance);
- } else {
- var connectTimeoutMS = self.replicaset.options.socketOptions
- ? self.replicaset.options.socketOptions.connectTimeoutMS : 0
- // Create a new master connection
- var _server = new Server(serverInstance.host, serverInstance.port, {
- auto_reconnect: false,
- returnIsMasterResults: true,
- slaveOk: true,
- poolSize: 1,
- socketOptions: { connectTimeoutMS: connectTimeoutMS },
- ssl: self.replicaset.options.ssl,
- sslValidate: self.replicaset.options.sslValidate,
- sslCA: self.replicaset.options.sslCA,
- sslCert: self.replicaset.options.sslCert,
- sslKey: self.replicaset.options.sslKey,
- sslPass: self.replicaset.options.sslPass
- });
- // Create Db instance
- var _db = new self.Db('local', _server, { safe: true });
- _db.on("close", function() {
- delete self.dbs[this.serverConfig.host + ":" + this.serverConfig.port];
- })
- var _ping = function(__db, __serverInstance) {
- if(self.state == 'disconnected') {
- self.stop();
- return;
- }
- __db.open(function(err, db) {
- // Emit ping connect
- self.replicaset.emit("ping_connect", err, __serverInstance);
- if(self.state == 'disconnected' && __db != null) {
- return __db.close();
- }
- if(err) {
- delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
- __db.close();
- return done();
- }
- // Save instance
- self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port] = __db;
- // Startup time of the command
- var startTime = Date.now();
- // Execute ping on this connection
- __db.executeDbCommand({ping:1}, {failFast:true}, function(err) {
- self.replicaset.emit("ping", err, __serverInstance);
-
- if(err) {
- delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
- __db.close();
- return done();
- }
- if(null != __serverInstance.runtimeStats && __serverInstance.isConnected()) {
- __serverInstance.runtimeStats['pingMs'] = Date.now() - startTime;
- }
- __db.executeDbCommand({ismaster:1}, {failFast:true}, function(err, result) {
- self.replicaset.emit("ping_ismaster", err, result, __serverInstance);
- if(err) {
- delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
- __db.close();
- return done();
- }
-
- // Process the ismaster for the server
- if(result && result.documents && self.replicaset.processIsMaster) {
- self.replicaset.processIsMaster(__serverInstance, result.documents[0]);
- }
- // Done with the pinging
- done();
- });
- });
- });
- };
- // Ping the server
- _ping(_db, serverInstance);
- }
- function done() {
- // Adjust the number of checks
- numberOfEntries--;
- // If we are done with all results coming back trigger ping again
- if(0 === numberOfEntries && 'connected' == self.state) {
- setTimeout(pingFunction, self.pingInterval);
- }
- }
- }(server);
- }
- }
- // Start pingFunction
- pingFunction();
- callback && callback(null);
- }