PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

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

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