PageRenderTime 131ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/node_modules/mongodb/lib/mongodb/connection/repl_set/strategies/ping_strategy.js

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