PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/node_modules/connect-mongo/node_modules/mongodb/lib/mongodb/connection/strategies/ping_strategy.js

https://bitbucket.org/ktkaushik/sqr
JavaScript | 290 lines | 185 code | 48 blank | 57 comment | 59 complexity | 034f2efd70ba919c1ec1bcfcd62d122a MD5 | raw file
Possible License(s): MIT
  1. var Server = require("../server").Server
  2. , format = require('util').format;
  3. // The ping strategy uses pings each server and records the
  4. // elapsed time for the server so it can pick a server based on lowest
  5. // return time for the db command {ping:true}
  6. var PingStrategy = exports.PingStrategy = function(replicaset, secondaryAcceptableLatencyMS) {
  7. this.replicaset = replicaset;
  8. this.secondaryAcceptableLatencyMS = secondaryAcceptableLatencyMS;
  9. this.state = 'disconnected';
  10. this.pingInterval = 5000;
  11. // Class instance
  12. this.Db = require("../../db").Db;
  13. // Active db connections
  14. this.dbs = {};
  15. // Logger api
  16. this.Logger = null;
  17. }
  18. // Starts any needed code
  19. PingStrategy.prototype.start = function(callback) {
  20. // already running?
  21. if ('connected' == this.state) return;
  22. this.state = 'connected';
  23. // Start ping server
  24. this._pingServer(callback);
  25. }
  26. // Stops and kills any processes running
  27. PingStrategy.prototype.stop = function(callback) {
  28. // Stop the ping process
  29. this.state = 'disconnected';
  30. // Stop all the server instances
  31. for(var key in this.dbs) {
  32. this.dbs[key].close();
  33. }
  34. // optional callback
  35. callback && callback(null, null);
  36. }
  37. PingStrategy.prototype.checkoutConnection = function(tags, secondaryCandidates) {
  38. // Servers are picked based on the lowest ping time and then servers that lower than that + secondaryAcceptableLatencyMS
  39. // Create a list of candidat servers, containing the primary if available
  40. var candidateServers = [];
  41. var self = this;
  42. // If we have not provided a list of candidate servers use the default setup
  43. if(!Array.isArray(secondaryCandidates)) {
  44. candidateServers = this.replicaset._state.master != null ? [this.replicaset._state.master] : [];
  45. // Add all the secondaries
  46. var keys = Object.keys(this.replicaset._state.secondaries);
  47. for(var i = 0; i < keys.length; i++) {
  48. candidateServers.push(this.replicaset._state.secondaries[keys[i]])
  49. }
  50. } else {
  51. candidateServers = secondaryCandidates;
  52. }
  53. // Final list of eligable server
  54. var finalCandidates = [];
  55. // If we have tags filter by tags
  56. if(tags != null && typeof tags == 'object') {
  57. // If we have an array or single tag selection
  58. var tagObjects = Array.isArray(tags) ? tags : [tags];
  59. // Iterate over all tags until we find a candidate server
  60. for(var _i = 0; _i < tagObjects.length; _i++) {
  61. // Grab a tag object
  62. var tagObject = tagObjects[_i];
  63. // Matching keys
  64. var matchingKeys = Object.keys(tagObject);
  65. // Remove any that are not tagged correctly
  66. for(var i = 0; i < candidateServers.length; i++) {
  67. var server = candidateServers[i];
  68. // If we have tags match
  69. if(server.tags != null) {
  70. var matching = true;
  71. // Ensure we have all the values
  72. for(var j = 0; j < matchingKeys.length; j++) {
  73. if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) {
  74. matching = false;
  75. break;
  76. }
  77. }
  78. // If we have a match add it to the list of matching servers
  79. if(matching) {
  80. finalCandidates.push(server);
  81. }
  82. }
  83. }
  84. }
  85. } else {
  86. // Final array candidates
  87. var finalCandidates = candidateServers;
  88. }
  89. // Sort by ping time
  90. finalCandidates.sort(function(a, b) {
  91. return a.runtimeStats['pingMs'] > b.runtimeStats['pingMs'];
  92. });
  93. if(0 === finalCandidates.length)
  94. return new Error("No replica set members available for query");
  95. // find lowest server with a ping time
  96. var lowest = finalCandidates.filter(function (server) {
  97. return undefined != server.runtimeStats.pingMs;
  98. })[0];
  99. if(!lowest) {
  100. lowest = finalCandidates[0];
  101. }
  102. // convert to integer
  103. var lowestPing = lowest.runtimeStats.pingMs | 0;
  104. // determine acceptable latency
  105. var acceptable = lowestPing + this.secondaryAcceptableLatencyMS;
  106. // remove any server responding slower than acceptable
  107. var len = finalCandidates.length;
  108. while(len--) {
  109. if(finalCandidates[len].runtimeStats['pingMs'] > acceptable) {
  110. finalCandidates.splice(len, 1);
  111. }
  112. }
  113. if(self.logger && self.logger.debug) {
  114. self.logger.debug("Ping strategy selection order for tags", tags);
  115. finalCandidates.forEach(function(c) {
  116. self.logger.debug(format("%s:%s = %s ms", c.host, c.port, c.runtimeStats['pingMs']), null);
  117. })
  118. }
  119. // If no candidates available return an error
  120. if(finalCandidates.length == 0)
  121. return new Error("No replica set members available for query");
  122. // Pick a random acceptable server
  123. var connection = finalCandidates[Math.round(Math.random(1000000) * (finalCandidates.length - 1))].checkoutReader();
  124. if(self.logger && self.logger.debug) {
  125. if(connection)
  126. self.logger.debug("picked server %s:%s", connection.socketOptions.host, connection.socketOptions.port);
  127. }
  128. return connection;
  129. }
  130. PingStrategy.prototype._pingServer = function(callback) {
  131. var self = this;
  132. // Ping server function
  133. var pingFunction = function() {
  134. if(self.state == 'disconnected') return;
  135. // Create a list of all servers we can send the ismaster command to
  136. var allServers = self.replicaset._state.master != null ? [self.replicaset._state.master] : [];
  137. // Secondary keys
  138. var keys = Object.keys(self.replicaset._state.secondaries);
  139. // Add all secondaries
  140. for(var i = 0; i < keys.length; i++) {
  141. allServers.push(self.replicaset._state.secondaries[keys[i]]);
  142. }
  143. // Number of server entries
  144. var numberOfEntries = allServers.length;
  145. // We got keys
  146. for(var i = 0; i < allServers.length; i++) {
  147. // We got a server instance
  148. var server = allServers[i];
  149. // Create a new server object, avoid using internal connections as they might
  150. // be in an illegal state
  151. new function(serverInstance) {
  152. var _db = self.dbs[serverInstance.host + ":" + serverInstance.port];
  153. // If we have a db
  154. if(_db != null) {
  155. // Startup time of the command
  156. var startTime = Date.now();
  157. // Execute ping command in own scope
  158. var _ping = function(__db, __serverInstance) {
  159. // Execute ping on this connection
  160. __db.executeDbCommand({ping:1}, {failFast:true}, function(err) {
  161. if(err) {
  162. delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
  163. __db.close();
  164. return done();
  165. }
  166. if(null != __serverInstance.runtimeStats && __serverInstance.isConnected()) {
  167. __serverInstance.runtimeStats['pingMs'] = Date.now() - startTime;
  168. }
  169. done();
  170. });
  171. };
  172. // Ping
  173. _ping(_db, serverInstance);
  174. } else {
  175. // Create a new master connection
  176. var _server = new Server(serverInstance.host, serverInstance.port, {
  177. auto_reconnect: false,
  178. returnIsMasterResults: true,
  179. slaveOk: true,
  180. poolSize: 1,
  181. socketOptions: { connectTimeoutMS: self.replicaset._connectTimeoutMS },
  182. ssl: self.replicaset.ssl,
  183. sslValidate: self.replicaset.sslValidate,
  184. sslCA: self.replicaset.sslCA,
  185. sslCert: self.replicaset.sslCert,
  186. sslKey: self.replicaset.sslKey,
  187. sslPass: self.replicaset.sslPass
  188. });
  189. // Create Db instance
  190. var _db = new self.Db(self.replicaset.db.databaseName, _server, { safe: true });
  191. _db.on("close", function() {
  192. delete self.dbs[this.serverConfig.host + ":" + this.serverConfig.port];
  193. })
  194. var _ping = function(__db, __serverInstance) {
  195. if(self.state == 'disconnected') {
  196. self.stop();
  197. return;
  198. }
  199. __db.open(function(err, db) {
  200. if(self.state == 'disconnected' && __db != null) {
  201. return __db.close();
  202. }
  203. if(err) {
  204. delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
  205. __db.close();
  206. return done();
  207. }
  208. // Save instance
  209. self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port] = __db;
  210. // Startup time of the command
  211. var startTime = Date.now();
  212. // Execute ping on this connection
  213. __db.executeDbCommand({ping:1}, {failFast:true}, function(err) {
  214. if(err) {
  215. delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port];
  216. __db.close();
  217. return done();
  218. }
  219. if(null != __serverInstance.runtimeStats && __serverInstance.isConnected()) {
  220. __serverInstance.runtimeStats['pingMs'] = Date.now() - startTime;
  221. }
  222. done();
  223. });
  224. });
  225. };
  226. _ping(_db, serverInstance);
  227. }
  228. function done() {
  229. // Adjust the number of checks
  230. numberOfEntries--;
  231. // If we are done with all results coming back trigger ping again
  232. if(0 === numberOfEntries && 'connected' == self.state) {
  233. setTimeout(pingFunction, self.pingInterval);
  234. }
  235. }
  236. }(server);
  237. }
  238. }
  239. // Start pingFunction
  240. pingFunction();
  241. callback && callback(null);
  242. }