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

https://bitbucket.org/travis_dunn/trail-of-secrets · JavaScript · 188 lines · 107 code · 36 blank · 45 comment · 36 complexity · f23fab7b885b8387705f53e37550c757 MD5 · raw file

  1. var Server = require("../server").Server;
  2. // The ping strategy uses pings each server and records the
  3. // elapsed time for the server so it can pick a server based on lowest
  4. // return time for the db command {ping:true}
  5. var PingStrategy = exports.PingStrategy = function(replicaset, secondaryAcceptableLatencyMS) {
  6. this.replicaset = replicaset;
  7. this.secondaryAcceptableLatencyMS = secondaryAcceptableLatencyMS;
  8. this.state = 'disconnected';
  9. this.pingInterval = 5000;
  10. // Class instance
  11. this.Db = require("../../db").Db;
  12. }
  13. // Starts any needed code
  14. PingStrategy.prototype.start = function(callback) {
  15. // already running?
  16. if ('connected' == this.state) return;
  17. this.state = 'connected';
  18. // Start ping server
  19. this._pingServer(callback);
  20. }
  21. // Stops and kills any processes running
  22. PingStrategy.prototype.stop = function(callback) {
  23. // Stop the ping process
  24. this.state = 'disconnected';
  25. // optional callback
  26. callback && callback(null, null);
  27. }
  28. PingStrategy.prototype.checkoutSecondary = function(tags, secondaryCandidates) {
  29. // Servers are picked based on the lowest ping time and then servers that lower than that + secondaryAcceptableLatencyMS
  30. // Create a list of candidat servers, containing the primary if available
  31. var candidateServers = [];
  32. // If we have not provided a list of candidate servers use the default setup
  33. if(!Array.isArray(secondaryCandidates)) {
  34. candidateServers = this.replicaset._state.master != null ? [this.replicaset._state.master] : [];
  35. // Add all the secondaries
  36. var keys = Object.keys(this.replicaset._state.secondaries);
  37. for(var i = 0; i < keys.length; i++) {
  38. candidateServers.push(this.replicaset._state.secondaries[keys[i]])
  39. }
  40. } else {
  41. candidateServers = secondaryCandidates;
  42. }
  43. // Final list of eligable server
  44. var finalCandidates = [];
  45. // If we have tags filter by tags
  46. if(tags != null && typeof tags == 'object') {
  47. // If we have an array or single tag selection
  48. var tagObjects = Array.isArray(tags) ? tags : [tags];
  49. // Iterate over all tags until we find a candidate server
  50. for(var _i = 0; _i < tagObjects.length; _i++) {
  51. // Grab a tag object
  52. var tagObject = tagObjects[_i];
  53. // Matching keys
  54. var matchingKeys = Object.keys(tagObject);
  55. // Remove any that are not tagged correctly
  56. for(var i = 0; i < candidateServers.length; i++) {
  57. var server = candidateServers[i];
  58. // If we have tags match
  59. if(server.tags != null) {
  60. var matching = true;
  61. // Ensure we have all the values
  62. for(var j = 0; j < matchingKeys.length; j++) {
  63. if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) {
  64. matching = false;
  65. break;
  66. }
  67. }
  68. // If we have a match add it to the list of matching servers
  69. if(matching) {
  70. finalCandidates.push(server);
  71. }
  72. }
  73. }
  74. }
  75. } else {
  76. // Final array candidates
  77. var finalCandidates = candidateServers;
  78. }
  79. // Sort by ping time
  80. finalCandidates.sort(function(a, b) {
  81. return a.runtimeStats['pingMs'] > b.runtimeStats['pingMs'];
  82. });
  83. if(0 === finalCandidates.length)
  84. return new Error("No replica set members available for query");
  85. // handle undefined pingMs
  86. var lowestPing = finalCandidates[0].runtimeStats['pingMs'] | 0;
  87. // determine acceptable latency
  88. var acceptable = lowestPing + this.secondaryAcceptableLatencyMS;
  89. // remove any server responding slower than acceptable
  90. var len = finalCandidates.length;
  91. while(len--) {
  92. if(finalCandidates[len].runtimeStats['pingMs'] > acceptable) {
  93. finalCandidates.splice(len, 1);
  94. }
  95. }
  96. // If no candidates available return an error
  97. if(finalCandidates.length == 0)
  98. return new Error("No replica set members available for query");
  99. // Pick a random acceptable server
  100. return finalCandidates[Math.round(Math.random(1000000) * (finalCandidates.length - 1))].checkoutReader();
  101. }
  102. PingStrategy.prototype._pingServer = function(callback) {
  103. var self = this;
  104. // Ping server function
  105. var pingFunction = function() {
  106. if(self.state == 'disconnected') return;
  107. var addresses = self.replicaset._state.addresses;
  108. // Grab all servers
  109. var serverKeys = Object.keys(addresses);
  110. // Number of server entries
  111. var numberOfEntries = serverKeys.length;
  112. // We got keys
  113. for(var i = 0; i < serverKeys.length; i++) {
  114. // We got a server instance
  115. var server = addresses[serverKeys[i]];
  116. // Create a new server object, avoid using internal connections as they might
  117. // be in an illegal state
  118. new function(serverInstance) {
  119. var options = { poolSize: 1, timeout: 500, auto_reconnect: false };
  120. var server = new Server(serverInstance.host, serverInstance.port, options);
  121. var db = new self.Db(self.replicaset.db.databaseName, server, { safe: true });
  122. db.on("error", done);
  123. // Open the db instance
  124. db.open(function(err, _db) {
  125. if(err) return done(err, _db);
  126. // Startup time of the command
  127. var startTime = Date.now();
  128. // Execute ping on this connection
  129. db.executeDbCommand({ping:1}, {failFast:true}, function() {
  130. if(null != serverInstance.runtimeStats && serverInstance.isConnected()) {
  131. serverInstance.runtimeStats['pingMs'] = Date.now() - startTime;
  132. }
  133. done(null, _db);
  134. })
  135. })
  136. function done (err, _db) {
  137. // Close connection
  138. if (_db) _db.close(true);
  139. // Adjust the number of checks
  140. numberOfEntries--;
  141. // If we are done with all results coming back trigger ping again
  142. if(0 === numberOfEntries && 'connected' == self.state) {
  143. setTimeout(pingFunction, self.pingInterval);
  144. }
  145. }
  146. }(server);
  147. }
  148. }
  149. // Start pingFunction
  150. setTimeout(pingFunction, 1000);
  151. callback && callback(null);
  152. }