Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

pool.js 52KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657
  1. 'use strict';
  2. const inherits = require('util').inherits;
  3. const EventEmitter = require('events').EventEmitter;
  4. const Connection = require('./connection');
  5. const MongoError = require('../error').MongoError;
  6. const MongoNetworkError = require('../error').MongoNetworkError;
  7. const MongoWriteConcernError = require('../error').MongoWriteConcernError;
  8. const Logger = require('./logger');
  9. const f = require('util').format;
  10. const Query = require('./commands').Query;
  11. const CommandResult = require('./command_result');
  12. const MESSAGE_HEADER_SIZE = require('../wireprotocol/shared').MESSAGE_HEADER_SIZE;
  13. const opcodes = require('../wireprotocol/shared').opcodes;
  14. const compress = require('../wireprotocol/compression').compress;
  15. const compressorIDs = require('../wireprotocol/compression').compressorIDs;
  16. const uncompressibleCommands = require('../wireprotocol/compression').uncompressibleCommands;
  17. const resolveClusterTime = require('../topologies/shared').resolveClusterTime;
  18. const apm = require('./apm');
  19. const defaultAuthProviders = require('../auth/defaultAuthProviders').defaultAuthProviders;
  20. const Buffer = require('safe-buffer').Buffer;
  21. var DISCONNECTED = 'disconnected';
  22. var CONNECTING = 'connecting';
  23. var CONNECTED = 'connected';
  24. var DESTROYING = 'destroying';
  25. var DESTROYED = 'destroyed';
  26. var _id = 0;
  27. function hasSessionSupport(topology) {
  28. if (topology == null) return false;
  29. return topology.ismaster == null ? false : topology.ismaster.maxWireVersion >= 6;
  30. }
  31. /**
  32. * Creates a new Pool instance
  33. * @class
  34. * @param {string} options.host The server host
  35. * @param {number} options.port The server port
  36. * @param {number} [options.size=5] Max server connection pool size
  37. * @param {number} [options.minSize=0] Minimum server connection pool size
  38. * @param {boolean} [options.reconnect=true] Server will attempt to reconnect on loss of connection
  39. * @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
  40. * @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
  41. * @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
  42. * @param {number} [options.keepAliveInitialDelay=300000] Initial delay before TCP keep alive enabled
  43. * @param {boolean} [options.noDelay=true] TCP Connection no delay
  44. * @param {number} [options.connectionTimeout=30000] TCP Connection timeout setting
  45. * @param {number} [options.socketTimeout=360000] TCP Socket timeout setting
  46. * @param {number} [options.monitoringSocketTimeout=30000] TCP Socket timeout setting for replicaset monitoring socket
  47. * @param {boolean} [options.ssl=false] Use SSL for connection
  48. * @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
  49. * @param {Buffer} [options.ca] SSL Certificate store binary buffer
  50. * @param {Buffer} [options.crl] SSL Certificate revocation store binary buffer
  51. * @param {Buffer} [options.cert] SSL Certificate binary buffer
  52. * @param {Buffer} [options.key] SSL Key file binary buffer
  53. * @param {string} [options.passPhrase] SSL Certificate pass phrase
  54. * @param {boolean} [options.rejectUnauthorized=false] Reject unauthorized server certificates
  55. * @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
  56. * @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
  57. * @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
  58. * @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
  59. * @fires Pool#connect
  60. * @fires Pool#close
  61. * @fires Pool#error
  62. * @fires Pool#timeout
  63. * @fires Pool#parseError
  64. * @return {Pool} A cursor instance
  65. */
  66. var Pool = function(topology, options) {
  67. // Add event listener
  68. EventEmitter.call(this);
  69. // Store topology for later use
  70. this.topology = topology;
  71. // Add the options
  72. this.options = Object.assign(
  73. {
  74. // Host and port settings
  75. host: 'localhost',
  76. port: 27017,
  77. // Pool default max size
  78. size: 5,
  79. // Pool default min size
  80. minSize: 0,
  81. // socket settings
  82. connectionTimeout: 30000,
  83. socketTimeout: 360000,
  84. keepAlive: true,
  85. keepAliveInitialDelay: 300000,
  86. noDelay: true,
  87. // SSL Settings
  88. ssl: false,
  89. checkServerIdentity: true,
  90. ca: null,
  91. crl: null,
  92. cert: null,
  93. key: null,
  94. passPhrase: null,
  95. rejectUnauthorized: false,
  96. promoteLongs: true,
  97. promoteValues: true,
  98. promoteBuffers: false,
  99. // Reconnection options
  100. reconnect: true,
  101. reconnectInterval: 1000,
  102. reconnectTries: 30,
  103. // Enable domains
  104. domainsEnabled: false
  105. },
  106. options
  107. );
  108. // Identification information
  109. this.id = _id++;
  110. // Current reconnect retries
  111. this.retriesLeft = this.options.reconnectTries;
  112. this.reconnectId = null;
  113. // No bson parser passed in
  114. if (
  115. !options.bson ||
  116. (options.bson &&
  117. (typeof options.bson.serialize !== 'function' ||
  118. typeof options.bson.deserialize !== 'function'))
  119. ) {
  120. throw new Error('must pass in valid bson parser');
  121. }
  122. // Logger instance
  123. this.logger = Logger('Pool', options);
  124. // Pool state
  125. this.state = DISCONNECTED;
  126. // Connections
  127. this.availableConnections = [];
  128. this.inUseConnections = [];
  129. this.connectingConnections = [];
  130. // Currently executing
  131. this.executing = false;
  132. // Operation work queue
  133. this.queue = [];
  134. // All the authProviders
  135. this.authProviders = options.authProviders || defaultAuthProviders(options.bson);
  136. // Contains the reconnect connection
  137. this.reconnectConnection = null;
  138. // Are we currently authenticating
  139. this.authenticating = false;
  140. this.loggingout = false;
  141. this.nonAuthenticatedConnections = [];
  142. this.authenticatingTimestamp = null;
  143. // Number of consecutive timeouts caught
  144. this.numberOfConsecutiveTimeouts = 0;
  145. // Current pool Index
  146. this.connectionIndex = 0;
  147. };
  148. inherits(Pool, EventEmitter);
  149. Object.defineProperty(Pool.prototype, 'size', {
  150. enumerable: true,
  151. get: function() {
  152. return this.options.size;
  153. }
  154. });
  155. Object.defineProperty(Pool.prototype, 'minSize', {
  156. enumerable: true,
  157. get: function() {
  158. return this.options.minSize;
  159. }
  160. });
  161. Object.defineProperty(Pool.prototype, 'connectionTimeout', {
  162. enumerable: true,
  163. get: function() {
  164. return this.options.connectionTimeout;
  165. }
  166. });
  167. Object.defineProperty(Pool.prototype, 'socketTimeout', {
  168. enumerable: true,
  169. get: function() {
  170. return this.options.socketTimeout;
  171. }
  172. });
  173. function stateTransition(self, newState) {
  174. var legalTransitions = {
  175. disconnected: [CONNECTING, DESTROYING, DISCONNECTED],
  176. connecting: [CONNECTING, DESTROYING, CONNECTED, DISCONNECTED],
  177. connected: [CONNECTED, DISCONNECTED, DESTROYING],
  178. destroying: [DESTROYING, DESTROYED],
  179. destroyed: [DESTROYED]
  180. };
  181. // Get current state
  182. var legalStates = legalTransitions[self.state];
  183. if (legalStates && legalStates.indexOf(newState) !== -1) {
  184. self.emit('stateChanged', self.state, newState);
  185. self.state = newState;
  186. } else {
  187. self.logger.error(
  188. f(
  189. 'Pool with id [%s] failed attempted illegal state transition from [%s] to [%s] only following state allowed [%s]',
  190. self.id,
  191. self.state,
  192. newState,
  193. legalStates
  194. )
  195. );
  196. }
  197. }
  198. function authenticate(pool, auth, connection, cb) {
  199. if (auth[0] === undefined) return cb(null);
  200. // We need to authenticate the server
  201. var mechanism = auth[0];
  202. var db = auth[1];
  203. // Validate if the mechanism exists
  204. if (!pool.authProviders[mechanism]) {
  205. throw new MongoError(f('authMechanism %s not supported', mechanism));
  206. }
  207. // Get the provider
  208. var provider = pool.authProviders[mechanism];
  209. // Authenticate using the provided mechanism
  210. provider.auth.apply(provider, [write(pool), [connection], db].concat(auth.slice(2)).concat([cb]));
  211. }
  212. // The write function used by the authentication mechanism (bypasses external)
  213. function write(self) {
  214. return function(connection, command, callback) {
  215. // Get the raw buffer
  216. // Ensure we stop auth if pool was destroyed
  217. if (self.state === DESTROYED || self.state === DESTROYING) {
  218. return callback(new MongoError('pool destroyed'));
  219. }
  220. // Set the connection workItem callback
  221. connection.workItems.push({
  222. cb: callback,
  223. command: true,
  224. requestId: command.requestId
  225. });
  226. // Write the buffer out to the connection
  227. connection.write(command.toBin());
  228. };
  229. }
  230. function reauthenticate(pool, connection, cb) {
  231. // Authenticate
  232. function authenticateAgainstProvider(pool, connection, providers, cb) {
  233. // Finished re-authenticating against providers
  234. if (providers.length === 0) return cb();
  235. // Get the provider name
  236. var provider = pool.authProviders[providers.pop()];
  237. // Auth provider
  238. provider.reauthenticate(write(pool), [connection], function(err) {
  239. // We got an error return immediately
  240. if (err) return cb(err);
  241. // Continue authenticating the connection
  242. authenticateAgainstProvider(pool, connection, providers, cb);
  243. });
  244. }
  245. // Start re-authenticating process
  246. authenticateAgainstProvider(pool, connection, Object.keys(pool.authProviders), cb);
  247. }
  248. function connectionFailureHandler(self, event) {
  249. return function(err) {
  250. if (this._connectionFailHandled) return;
  251. this._connectionFailHandled = true;
  252. // Destroy the connection
  253. this.destroy();
  254. // Remove the connection
  255. removeConnection(self, this);
  256. // Flush all work Items on this connection
  257. while (this.workItems.length > 0) {
  258. var workItem = this.workItems.shift();
  259. if (workItem.cb) workItem.cb(err);
  260. }
  261. // Did we catch a timeout, increment the numberOfConsecutiveTimeouts
  262. if (event === 'timeout') {
  263. self.numberOfConsecutiveTimeouts = self.numberOfConsecutiveTimeouts + 1;
  264. // Have we timed out more than reconnectTries in a row ?
  265. // Force close the pool as we are trying to connect to tcp sink hole
  266. if (self.numberOfConsecutiveTimeouts > self.options.reconnectTries) {
  267. self.numberOfConsecutiveTimeouts = 0;
  268. // Destroy all connections and pool
  269. self.destroy(true);
  270. // Emit close event
  271. return self.emit('close', self);
  272. }
  273. }
  274. // No more socket available propegate the event
  275. if (self.socketCount() === 0) {
  276. if (self.state !== DESTROYED && self.state !== DESTROYING) {
  277. stateTransition(self, DISCONNECTED);
  278. }
  279. // Do not emit error events, they are always close events
  280. // do not trigger the low level error handler in node
  281. event = event === 'error' ? 'close' : event;
  282. self.emit(event, err);
  283. }
  284. // Start reconnection attempts
  285. if (!self.reconnectId && self.options.reconnect) {
  286. self.reconnectId = setTimeout(attemptReconnect(self), self.options.reconnectInterval);
  287. }
  288. // Do we need to do anything to maintain the minimum pool size
  289. const totalConnections =
  290. self.availableConnections.length +
  291. self.connectingConnections.length +
  292. self.inUseConnections.length;
  293. if (totalConnections < self.minSize) {
  294. _createConnection(self);
  295. }
  296. };
  297. }
  298. function attemptReconnect(self) {
  299. return function() {
  300. self.emit('attemptReconnect', self);
  301. if (self.state === DESTROYED || self.state === DESTROYING) return;
  302. // We are connected do not try again
  303. if (self.isConnected()) {
  304. self.reconnectId = null;
  305. return;
  306. }
  307. // If we have failure schedule a retry
  308. function _connectionFailureHandler(self) {
  309. return function() {
  310. if (this._connectionFailHandled) return;
  311. this._connectionFailHandled = true;
  312. // Destroy the connection
  313. this.destroy();
  314. // Count down the number of reconnects
  315. self.retriesLeft = self.retriesLeft - 1;
  316. // How many retries are left
  317. if (self.retriesLeft <= 0) {
  318. // Destroy the instance
  319. self.destroy();
  320. // Emit close event
  321. self.emit(
  322. 'reconnectFailed',
  323. new MongoNetworkError(
  324. f(
  325. 'failed to reconnect after %s attempts with interval %s ms',
  326. self.options.reconnectTries,
  327. self.options.reconnectInterval
  328. )
  329. )
  330. );
  331. } else {
  332. self.reconnectId = setTimeout(attemptReconnect(self), self.options.reconnectInterval);
  333. }
  334. };
  335. }
  336. // Got a connect handler
  337. function _connectHandler(self) {
  338. return function() {
  339. // Assign
  340. var connection = this;
  341. // Pool destroyed stop the connection
  342. if (self.state === DESTROYED || self.state === DESTROYING) {
  343. return connection.destroy();
  344. }
  345. // Clear out all handlers
  346. handlers.forEach(function(event) {
  347. connection.removeAllListeners(event);
  348. });
  349. // Reset reconnect id
  350. self.reconnectId = null;
  351. // Apply pool connection handlers
  352. connection.on('error', connectionFailureHandler(self, 'error'));
  353. connection.on('close', connectionFailureHandler(self, 'close'));
  354. connection.on('timeout', connectionFailureHandler(self, 'timeout'));
  355. connection.on('parseError', connectionFailureHandler(self, 'parseError'));
  356. // Apply any auth to the connection
  357. reauthenticate(self, this, function() {
  358. // Reset retries
  359. self.retriesLeft = self.options.reconnectTries;
  360. // Push to available connections
  361. self.availableConnections.push(connection);
  362. // Set the reconnectConnection to null
  363. self.reconnectConnection = null;
  364. // Emit reconnect event
  365. self.emit('reconnect', self);
  366. // Trigger execute to start everything up again
  367. _execute(self)();
  368. });
  369. };
  370. }
  371. // Create a connection
  372. self.reconnectConnection = new Connection(messageHandler(self), self.options);
  373. // Add handlers
  374. self.reconnectConnection.on('close', _connectionFailureHandler(self, 'close'));
  375. self.reconnectConnection.on('error', _connectionFailureHandler(self, 'error'));
  376. self.reconnectConnection.on('timeout', _connectionFailureHandler(self, 'timeout'));
  377. self.reconnectConnection.on('parseError', _connectionFailureHandler(self, 'parseError'));
  378. // On connection
  379. self.reconnectConnection.on('connect', _connectHandler(self));
  380. // Attempt connection
  381. self.reconnectConnection.connect();
  382. };
  383. }
  384. function moveConnectionBetween(connection, from, to) {
  385. var index = from.indexOf(connection);
  386. // Move the connection from connecting to available
  387. if (index !== -1) {
  388. from.splice(index, 1);
  389. to.push(connection);
  390. }
  391. }
  392. function messageHandler(self) {
  393. return function(message, connection) {
  394. // workItem to execute
  395. var workItem = null;
  396. // Locate the workItem
  397. for (var i = 0; i < connection.workItems.length; i++) {
  398. if (connection.workItems[i].requestId === message.responseTo) {
  399. // Get the callback
  400. workItem = connection.workItems[i];
  401. // Remove from list of workItems
  402. connection.workItems.splice(i, 1);
  403. }
  404. }
  405. // Reset timeout counter
  406. self.numberOfConsecutiveTimeouts = 0;
  407. // Reset the connection timeout if we modified it for
  408. // this operation
  409. if (workItem && workItem.socketTimeout) {
  410. connection.resetSocketTimeout();
  411. }
  412. // Log if debug enabled
  413. if (self.logger.isDebug()) {
  414. self.logger.debug(
  415. f(
  416. 'message [%s] received from %s:%s',
  417. message.raw.toString('hex'),
  418. self.options.host,
  419. self.options.port
  420. )
  421. );
  422. }
  423. // Authenticate any straggler connections
  424. function authenticateStragglers(self, connection, callback) {
  425. // Get any non authenticated connections
  426. var connections = self.nonAuthenticatedConnections.slice(0);
  427. var nonAuthenticatedConnections = self.nonAuthenticatedConnections;
  428. self.nonAuthenticatedConnections = [];
  429. // Establish if the connection need to be authenticated
  430. // Add to authentication list if
  431. // 1. we were in an authentication process when the operation was executed
  432. // 2. our current authentication timestamp is from the workItem one, meaning an auth has happened
  433. if (
  434. connection.workItems.length === 1 &&
  435. (connection.workItems[0].authenticating === true ||
  436. (typeof connection.workItems[0].authenticatingTimestamp === 'number' &&
  437. connection.workItems[0].authenticatingTimestamp !== self.authenticatingTimestamp))
  438. ) {
  439. // Add connection to the list
  440. connections.push(connection);
  441. }
  442. // No connections need to be re-authenticated
  443. if (connections.length === 0) {
  444. // Release the connection back to the pool
  445. moveConnectionBetween(connection, self.inUseConnections, self.availableConnections);
  446. // Finish
  447. return callback();
  448. }
  449. // Apply re-authentication to all connections before releasing back to pool
  450. var connectionCount = connections.length;
  451. // Authenticate all connections
  452. for (var i = 0; i < connectionCount; i++) {
  453. reauthenticate(self, connections[i], function() {
  454. connectionCount = connectionCount - 1;
  455. if (connectionCount === 0) {
  456. // Put non authenticated connections in available connections
  457. self.availableConnections = self.availableConnections.concat(
  458. nonAuthenticatedConnections
  459. );
  460. // Release the connection back to the pool
  461. moveConnectionBetween(connection, self.inUseConnections, self.availableConnections);
  462. // Return
  463. callback();
  464. }
  465. });
  466. }
  467. }
  468. function handleOperationCallback(self, cb, err, result) {
  469. // No domain enabled
  470. if (!self.options.domainsEnabled) {
  471. return process.nextTick(function() {
  472. return cb(err, result);
  473. });
  474. }
  475. // Domain enabled just call the callback
  476. cb(err, result);
  477. }
  478. authenticateStragglers(self, connection, function() {
  479. // Keep executing, ensure current message handler does not stop execution
  480. if (!self.executing) {
  481. process.nextTick(function() {
  482. _execute(self)();
  483. });
  484. }
  485. // Time to dispatch the message if we have a callback
  486. if (workItem && !workItem.immediateRelease) {
  487. try {
  488. // Parse the message according to the provided options
  489. message.parse(workItem);
  490. } catch (err) {
  491. return handleOperationCallback(self, workItem.cb, new MongoError(err));
  492. }
  493. // Look for clusterTime, and operationTime and update them if necessary
  494. if (message.documents[0]) {
  495. if (message.documents[0].$clusterTime) {
  496. const $clusterTime = message.documents[0].$clusterTime;
  497. self.topology.clusterTime = $clusterTime;
  498. if (workItem.session != null) {
  499. resolveClusterTime(workItem.session, $clusterTime);
  500. }
  501. }
  502. if (
  503. message.documents[0].operationTime &&
  504. workItem.session &&
  505. workItem.session.supports.causalConsistency
  506. ) {
  507. workItem.session.advanceOperationTime(message.documents[0].operationTime);
  508. }
  509. }
  510. // Establish if we have an error
  511. if (workItem.command && message.documents[0]) {
  512. const responseDoc = message.documents[0];
  513. if (responseDoc.ok === 0 || responseDoc.$err || responseDoc.errmsg || responseDoc.code) {
  514. return handleOperationCallback(self, workItem.cb, new MongoError(responseDoc));
  515. }
  516. if (responseDoc.writeConcernError) {
  517. const err =
  518. responseDoc.ok === 1
  519. ? new MongoWriteConcernError(responseDoc.writeConcernError, responseDoc)
  520. : new MongoWriteConcernError(responseDoc.writeConcernError);
  521. return handleOperationCallback(self, workItem.cb, err);
  522. }
  523. }
  524. // Add the connection details
  525. message.hashedName = connection.hashedName;
  526. // Return the documents
  527. handleOperationCallback(
  528. self,
  529. workItem.cb,
  530. null,
  531. new CommandResult(
  532. workItem.fullResult ? message : message.documents[0],
  533. connection,
  534. message
  535. )
  536. );
  537. }
  538. });
  539. };
  540. }
  541. /**
  542. * Return the total socket count in the pool.
  543. * @method
  544. * @return {Number} The number of socket available.
  545. */
  546. Pool.prototype.socketCount = function() {
  547. return this.availableConnections.length + this.inUseConnections.length;
  548. // + this.connectingConnections.length;
  549. };
  550. /**
  551. * Return all pool connections
  552. * @method
  553. * @return {Connection[]} The pool connections
  554. */
  555. Pool.prototype.allConnections = function() {
  556. return this.availableConnections.concat(this.inUseConnections).concat(this.connectingConnections);
  557. };
  558. /**
  559. * Get a pool connection (round-robin)
  560. * @method
  561. * @return {Connection}
  562. */
  563. Pool.prototype.get = function() {
  564. return this.allConnections()[0];
  565. };
  566. /**
  567. * Is the pool connected
  568. * @method
  569. * @return {boolean}
  570. */
  571. Pool.prototype.isConnected = function() {
  572. // We are in a destroyed state
  573. if (this.state === DESTROYED || this.state === DESTROYING) {
  574. return false;
  575. }
  576. // Get connections
  577. var connections = this.availableConnections.concat(this.inUseConnections);
  578. // Check if we have any connected connections
  579. for (var i = 0; i < connections.length; i++) {
  580. if (connections[i].isConnected()) return true;
  581. }
  582. // Might be authenticating, but we are still connected
  583. if (connections.length === 0 && this.authenticating) {
  584. return true;
  585. }
  586. // Not connected
  587. return false;
  588. };
  589. /**
  590. * Was the pool destroyed
  591. * @method
  592. * @return {boolean}
  593. */
  594. Pool.prototype.isDestroyed = function() {
  595. return this.state === DESTROYED || this.state === DESTROYING;
  596. };
  597. /**
  598. * Is the pool in a disconnected state
  599. * @method
  600. * @return {boolean}
  601. */
  602. Pool.prototype.isDisconnected = function() {
  603. return this.state === DISCONNECTED;
  604. };
  605. /**
  606. * Connect pool
  607. * @method
  608. */
  609. Pool.prototype.connect = function() {
  610. if (this.state !== DISCONNECTED) {
  611. throw new MongoError('connection in unlawful state ' + this.state);
  612. }
  613. var self = this;
  614. // Transition to connecting state
  615. stateTransition(this, CONNECTING);
  616. // Create an array of the arguments
  617. var args = Array.prototype.slice.call(arguments, 0);
  618. // Create a connection
  619. var connection = new Connection(messageHandler(self), this.options);
  620. // Add to list of connections
  621. this.connectingConnections.push(connection);
  622. // Add listeners to the connection
  623. connection.once('connect', function(connection) {
  624. if (self.state === DESTROYED || self.state === DESTROYING) return self.destroy();
  625. // If we are in a topology, delegate the auth to it
  626. // This is to avoid issues where we would auth against an
  627. // arbiter
  628. if (self.options.inTopology) {
  629. // Set connected mode
  630. stateTransition(self, CONNECTED);
  631. // Move the active connection
  632. moveConnectionBetween(connection, self.connectingConnections, self.availableConnections);
  633. // Emit the connect event
  634. return self.emit('connect', self);
  635. }
  636. // Apply any store credentials
  637. reauthenticate(self, connection, function(err) {
  638. if (self.state === DESTROYED || self.state === DESTROYING) return self.destroy();
  639. // We have an error emit it
  640. if (err) {
  641. // Destroy the pool
  642. self.destroy();
  643. // Emit the error
  644. return self.emit('error', err);
  645. }
  646. // Authenticate
  647. authenticate(self, args, connection, function(err) {
  648. if (self.state === DESTROYED || self.state === DESTROYING) return self.destroy();
  649. // We have an error emit it
  650. if (err) {
  651. // Destroy the pool
  652. self.destroy();
  653. // Emit the error
  654. return self.emit('error', err);
  655. }
  656. // Set connected mode
  657. stateTransition(self, CONNECTED);
  658. // Move the active connection
  659. moveConnectionBetween(connection, self.connectingConnections, self.availableConnections);
  660. // if we have a minPoolSize, create a connection
  661. if (self.minSize) {
  662. for (let i = 0; i < self.minSize; i++) _createConnection(self);
  663. }
  664. // Emit the connect event
  665. self.emit('connect', self);
  666. });
  667. });
  668. });
  669. // Add error handlers
  670. connection.once('error', connectionFailureHandler(this, 'error'));
  671. connection.once('close', connectionFailureHandler(this, 'close'));
  672. connection.once('timeout', connectionFailureHandler(this, 'timeout'));
  673. connection.once('parseError', connectionFailureHandler(this, 'parseError'));
  674. try {
  675. connection.connect();
  676. } catch (err) {
  677. // SSL or something threw on connect
  678. process.nextTick(function() {
  679. self.emit('error', err);
  680. });
  681. }
  682. };
  683. /**
  684. * Authenticate using a specified mechanism
  685. * @method
  686. * @param {string} mechanism The Auth mechanism we are invoking
  687. * @param {string} db The db we are invoking the mechanism against
  688. * @param {...object} param Parameters for the specific mechanism
  689. * @param {authResultCallback} callback A callback function
  690. */
  691. Pool.prototype.auth = function(mechanism) {
  692. var self = this;
  693. var args = Array.prototype.slice.call(arguments, 0);
  694. var callback = args.pop();
  695. // If we don't have the mechanism fail
  696. if (self.authProviders[mechanism] == null && mechanism !== 'default') {
  697. throw new MongoError(f('auth provider %s does not exist', mechanism));
  698. }
  699. // Signal that we are authenticating a new set of credentials
  700. this.authenticating = true;
  701. this.authenticatingTimestamp = new Date().getTime();
  702. // Authenticate all live connections
  703. function authenticateLiveConnections(self, args, cb) {
  704. // Get the current viable connections
  705. var connections = self.allConnections();
  706. // Allow nothing else to use the connections while we authenticate them
  707. self.availableConnections = [];
  708. self.inUseConnections = [];
  709. self.connectingConnections = [];
  710. var connectionsCount = connections.length;
  711. var error = null;
  712. // No connections available, return
  713. if (connectionsCount === 0) {
  714. self.authenticating = false;
  715. return callback(null);
  716. }
  717. // Authenticate the connections
  718. for (var i = 0; i < connections.length; i++) {
  719. authenticate(self, args, connections[i], function(err, result) {
  720. connectionsCount = connectionsCount - 1;
  721. // Store the error
  722. if (err) error = err;
  723. // Processed all connections
  724. if (connectionsCount === 0) {
  725. // Auth finished
  726. self.authenticating = false;
  727. // Add the connections back to available connections
  728. self.availableConnections = self.availableConnections.concat(connections);
  729. // We had an error, return it
  730. if (error) {
  731. // Log the error
  732. if (self.logger.isError()) {
  733. self.logger.error(
  734. f(
  735. '[%s] failed to authenticate against server %s:%s',
  736. self.id,
  737. self.options.host,
  738. self.options.port
  739. )
  740. );
  741. }
  742. return cb(error, result);
  743. }
  744. cb(null, result);
  745. }
  746. });
  747. }
  748. }
  749. // Wait for a logout in process to happen
  750. function waitForLogout(self, cb) {
  751. if (!self.loggingout) return cb();
  752. setTimeout(function() {
  753. waitForLogout(self, cb);
  754. }, 1);
  755. }
  756. // Wait for loggout to finish
  757. waitForLogout(self, function() {
  758. // Authenticate all live connections
  759. authenticateLiveConnections(self, args, function(err, result) {
  760. // Credentials correctly stored in auth provider if successful
  761. // Any new connections will now reauthenticate correctly
  762. self.authenticating = false;
  763. // Return after authentication connections
  764. callback(err, result);
  765. });
  766. });
  767. };
  768. /**
  769. * Logout all users against a database
  770. * @method
  771. * @param {string} dbName The database name
  772. * @param {authResultCallback} callback A callback function
  773. */
  774. Pool.prototype.logout = function(dbName, callback) {
  775. var self = this;
  776. if (typeof dbName !== 'string') {
  777. throw new MongoError('logout method requires a db name as first argument');
  778. }
  779. if (typeof callback !== 'function') {
  780. throw new MongoError('logout method requires a callback');
  781. }
  782. // Indicate logout in process
  783. this.loggingout = true;
  784. // Get all relevant connections
  785. var connections = self.availableConnections.concat(self.inUseConnections);
  786. var count = connections.length;
  787. // Store any error
  788. var error = null;
  789. // Send logout command over all the connections
  790. for (var i = 0; i < connections.length; i++) {
  791. write(self)(
  792. connections[i],
  793. new Query(
  794. this.options.bson,
  795. f('%s.$cmd', dbName),
  796. { logout: 1 },
  797. { numberToSkip: 0, numberToReturn: 1 }
  798. ),
  799. function(err) {
  800. count = count - 1;
  801. if (err) error = err;
  802. if (count === 0) {
  803. self.loggingout = false;
  804. callback(error);
  805. }
  806. }
  807. );
  808. }
  809. };
  810. /**
  811. * Unref the pool
  812. * @method
  813. */
  814. Pool.prototype.unref = function() {
  815. // Get all the known connections
  816. var connections = this.availableConnections
  817. .concat(this.inUseConnections)
  818. .concat(this.connectingConnections);
  819. connections.forEach(function(c) {
  820. c.unref();
  821. });
  822. };
  823. // Events
  824. var events = ['error', 'close', 'timeout', 'parseError', 'connect'];
  825. // Destroy the connections
  826. function destroy(self, connections) {
  827. // Destroy all connections
  828. connections.forEach(function(c) {
  829. // Remove all listeners
  830. for (var i = 0; i < events.length; i++) {
  831. c.removeAllListeners(events[i]);
  832. }
  833. // Destroy connection
  834. c.destroy();
  835. });
  836. // Zero out all connections
  837. self.inUseConnections = [];
  838. self.availableConnections = [];
  839. self.nonAuthenticatedConnections = [];
  840. self.connectingConnections = [];
  841. // Set state to destroyed
  842. stateTransition(self, DESTROYED);
  843. }
  844. /**
  845. * Destroy pool
  846. * @method
  847. */
  848. Pool.prototype.destroy = function(force) {
  849. var self = this;
  850. // Do not try again if the pool is already dead
  851. if (this.state === DESTROYED || self.state === DESTROYING) return;
  852. // Set state to destroyed
  853. stateTransition(this, DESTROYING);
  854. // Are we force closing
  855. if (force) {
  856. // Get all the known connections
  857. var connections = self.availableConnections
  858. .concat(self.inUseConnections)
  859. .concat(self.nonAuthenticatedConnections)
  860. .concat(self.connectingConnections);
  861. // Flush any remaining work items with
  862. // an error
  863. while (self.queue.length > 0) {
  864. var workItem = self.queue.shift();
  865. if (typeof workItem.cb === 'function') {
  866. workItem.cb(new MongoError('Pool was force destroyed'));
  867. }
  868. }
  869. // Destroy the topology
  870. return destroy(self, connections);
  871. }
  872. // Clear out the reconnect if set
  873. if (this.reconnectId) {
  874. clearTimeout(this.reconnectId);
  875. }
  876. // If we have a reconnect connection running, close
  877. // immediately
  878. if (this.reconnectConnection) {
  879. this.reconnectConnection.destroy();
  880. }
  881. // Wait for the operations to drain before we close the pool
  882. function checkStatus() {
  883. flushMonitoringOperations(self.queue);
  884. if (self.queue.length === 0) {
  885. // Get all the known connections
  886. var connections = self.availableConnections
  887. .concat(self.inUseConnections)
  888. .concat(self.nonAuthenticatedConnections)
  889. .concat(self.connectingConnections);
  890. // Check if we have any in flight operations
  891. for (var i = 0; i < connections.length; i++) {
  892. // There is an operation still in flight, reschedule a
  893. // check waiting for it to drain
  894. if (connections[i].workItems.length > 0) {
  895. return setTimeout(checkStatus, 1);
  896. }
  897. }
  898. destroy(self, connections);
  899. // } else if (self.queue.length > 0 && !this.reconnectId) {
  900. } else {
  901. // Ensure we empty the queue
  902. _execute(self)();
  903. // Set timeout
  904. setTimeout(checkStatus, 1);
  905. }
  906. }
  907. // Initiate drain of operations
  908. checkStatus();
  909. };
  910. // Prepare the buffer that Pool.prototype.write() uses to send to the server
  911. var serializeCommands = function(self, commands, result, callback) {
  912. // Base case when there are no more commands to serialize
  913. if (commands.length === 0) return callback(null, result);
  914. // Pop off the zeroth command and serialize it
  915. var thisCommand = commands.shift();
  916. var originalCommandBuffer = thisCommand.toBin();
  917. // Check whether we and the server have agreed to use a compressor
  918. if (self.options.agreedCompressor && !hasUncompressibleCommands(thisCommand)) {
  919. // Transform originalCommandBuffer into OP_COMPRESSED
  920. var concatenatedOriginalCommandBuffer = Buffer.concat(originalCommandBuffer);
  921. var messageToBeCompressed = concatenatedOriginalCommandBuffer.slice(MESSAGE_HEADER_SIZE);
  922. // Extract information needed for OP_COMPRESSED from the uncompressed message
  923. var originalCommandOpCode = concatenatedOriginalCommandBuffer.readInt32LE(12);
  924. // Compress the message body
  925. compress(self, messageToBeCompressed, function(err, compressedMessage) {
  926. if (err) return callback(err, null);
  927. // Create the msgHeader of OP_COMPRESSED
  928. var msgHeader = Buffer.alloc(MESSAGE_HEADER_SIZE);
  929. msgHeader.writeInt32LE(MESSAGE_HEADER_SIZE + 9 + compressedMessage.length, 0); // messageLength
  930. msgHeader.writeInt32LE(thisCommand.requestId, 4); // requestID
  931. msgHeader.writeInt32LE(0, 8); // responseTo (zero)
  932. msgHeader.writeInt32LE(opcodes.OP_COMPRESSED, 12); // opCode
  933. // Create the compression details of OP_COMPRESSED
  934. var compressionDetails = Buffer.alloc(9);
  935. compressionDetails.writeInt32LE(originalCommandOpCode, 0); // originalOpcode
  936. compressionDetails.writeInt32LE(messageToBeCompressed.length, 4); // Size of the uncompressed compressedMessage, excluding the MsgHeader
  937. compressionDetails.writeUInt8(compressorIDs[self.options.agreedCompressor], 8); // compressorID
  938. // Push the concatenation of the OP_COMPRESSED message onto results
  939. result.push(Buffer.concat([msgHeader, compressionDetails, compressedMessage]));
  940. // Continue recursing through the commands array
  941. serializeCommands(self, commands, result, callback);
  942. });
  943. } else {
  944. // Push the serialization of the command onto results
  945. result.push(originalCommandBuffer);
  946. // Continue recursing through the commands array
  947. serializeCommands(self, commands, result, callback);
  948. }
  949. };
  950. /**
  951. * Write a message to MongoDB
  952. * @method
  953. * @return {Connection}
  954. */
  955. Pool.prototype.write = function(commands, options, cb) {
  956. var self = this;
  957. // Ensure we have a callback
  958. if (typeof options === 'function') {
  959. cb = options;
  960. }
  961. // Always have options
  962. options = options || {};
  963. // We need to have a callback function unless the message returns no response
  964. if (!(typeof cb === 'function') && !options.noResponse) {
  965. throw new MongoError('write method must provide a callback');
  966. }
  967. // Pool was destroyed error out
  968. if (this.state === DESTROYED || this.state === DESTROYING) {
  969. // Callback with an error
  970. if (cb) {
  971. try {
  972. cb(new MongoError('pool destroyed'));
  973. } catch (err) {
  974. process.nextTick(function() {
  975. throw err;
  976. });
  977. }
  978. }
  979. return;
  980. }
  981. if (this.options.domainsEnabled && process.domain && typeof cb === 'function') {
  982. // if we have a domain bind to it
  983. var oldCb = cb;
  984. cb = process.domain.bind(function() {
  985. // v8 - argumentsToArray one-liner
  986. var args = new Array(arguments.length);
  987. for (var i = 0; i < arguments.length; i++) {
  988. args[i] = arguments[i];
  989. }
  990. // bounce off event loop so domain switch takes place
  991. process.nextTick(function() {
  992. oldCb.apply(null, args);
  993. });
  994. });
  995. }
  996. // Do we have an operation
  997. var operation = {
  998. cb: cb,
  999. raw: false,
  1000. promoteLongs: true,
  1001. promoteValues: true,
  1002. promoteBuffers: false,
  1003. fullResult: false
  1004. };
  1005. // Set the options for the parsing
  1006. operation.promoteLongs = typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true;
  1007. operation.promoteValues =
  1008. typeof options.promoteValues === 'boolean' ? options.promoteValues : true;
  1009. operation.promoteBuffers =
  1010. typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false;
  1011. operation.raw = typeof options.raw === 'boolean' ? options.raw : false;
  1012. operation.immediateRelease =
  1013. typeof options.immediateRelease === 'boolean' ? options.immediateRelease : false;
  1014. operation.documentsReturnedIn = options.documentsReturnedIn;
  1015. operation.command = typeof options.command === 'boolean' ? options.command : false;
  1016. operation.fullResult = typeof options.fullResult === 'boolean' ? options.fullResult : false;
  1017. operation.noResponse = typeof options.noResponse === 'boolean' ? options.noResponse : false;
  1018. operation.session = options.session || null;
  1019. // Optional per operation socketTimeout
  1020. operation.socketTimeout = options.socketTimeout;
  1021. operation.monitoring = options.monitoring;
  1022. // Custom socket Timeout
  1023. if (options.socketTimeout) {
  1024. operation.socketTimeout = options.socketTimeout;
  1025. }
  1026. // Ensure commands is an array
  1027. if (!Array.isArray(commands)) {
  1028. commands = [commands];
  1029. }
  1030. // Get the requestId
  1031. operation.requestId = commands[commands.length - 1].requestId;
  1032. if (hasSessionSupport(this.topology)) {
  1033. let sessionOptions = {};
  1034. if (this.topology.clusterTime) {
  1035. sessionOptions = { $clusterTime: this.topology.clusterTime };
  1036. }
  1037. if (operation.session) {
  1038. // TODO: reenable when sessions development is complete
  1039. // if (operation.session.topology !== this.topology) {
  1040. // return cb(
  1041. // new MongoError('Sessions may only be used with the client they were created from')
  1042. // );
  1043. // }
  1044. if (operation.session.hasEnded) {
  1045. return cb(new MongoError('Use of expired sessions is not permitted'));
  1046. }
  1047. if (
  1048. operation.session.clusterTime &&
  1049. operation.session.clusterTime.clusterTime.greaterThan(
  1050. sessionOptions.$clusterTime.clusterTime
  1051. )
  1052. ) {
  1053. sessionOptions.$clusterTime = operation.session.clusterTime;
  1054. }
  1055. sessionOptions.lsid = operation.session.id;
  1056. // update the `lastUse` of the acquired ServerSession
  1057. operation.session.serverSession.lastUse = Date.now();
  1058. }
  1059. // decorate the commands with session-specific details
  1060. commands.forEach(command => {
  1061. if (command instanceof Query) {
  1062. if (command.query.$query) {
  1063. Object.assign(command.query.$query, sessionOptions);
  1064. } else {
  1065. Object.assign(command.query, sessionOptions);
  1066. }
  1067. } else {
  1068. Object.assign(command, sessionOptions);
  1069. }
  1070. });
  1071. }
  1072. // If command monitoring is enabled we need to modify the callback here
  1073. if (self.options.monitorCommands) {
  1074. // NOTE: there is only ever a single command, for some legacy reason I am unaware of we
  1075. // treat this as a potential array of commands
  1076. const command = commands[0];
  1077. this.emit('commandStarted', new apm.CommandStartedEvent(this, command));
  1078. operation.started = process.hrtime();
  1079. operation.cb = (err, reply) => {
  1080. if (err) {
  1081. self.emit(
  1082. 'commandFailed',
  1083. new apm.CommandFailedEvent(this, command, err, operation.started)
  1084. );
  1085. } else {
  1086. if (reply && reply.result && (reply.result.ok === 0 || reply.result.$err)) {
  1087. self.emit(
  1088. 'commandFailed',
  1089. new apm.CommandFailedEvent(this, command, reply.result, operation.started)
  1090. );
  1091. } else {
  1092. self.emit(
  1093. 'commandSucceeded',
  1094. new apm.CommandSucceededEvent(this, command, reply, operation.started)
  1095. );
  1096. }
  1097. }
  1098. if (typeof cb === 'function') cb(err, reply);
  1099. };
  1100. }
  1101. // Prepare the operation buffer
  1102. serializeCommands(self, commands, [], function(err, serializedCommands) {
  1103. if (err) throw err;
  1104. // Set the operation's buffer to the serialization of the commands
  1105. operation.buffer = serializedCommands;
  1106. // If we have a monitoring operation schedule as the very first operation
  1107. // Otherwise add to back of queue
  1108. if (options.monitoring) {
  1109. self.queue.unshift(operation);
  1110. } else {
  1111. self.queue.push(operation);
  1112. }
  1113. // Attempt to execute the operation
  1114. if (!self.executing) {
  1115. process.nextTick(function() {
  1116. _execute(self)();
  1117. });
  1118. }
  1119. });
  1120. };
  1121. // Return whether a command contains an uncompressible command term
  1122. // Will return true if command contains no uncompressible command terms
  1123. var hasUncompressibleCommands = function(command) {
  1124. return uncompressibleCommands.some(function(cmd) {
  1125. return command.query.hasOwnProperty(cmd);
  1126. });
  1127. };
  1128. // Remove connection method
  1129. function remove(connection, connections) {
  1130. for (var i = 0; i < connections.length; i++) {
  1131. if (connections[i] === connection) {
  1132. connections.splice(i, 1);
  1133. return true;
  1134. }
  1135. }
  1136. }
  1137. function removeConnection(self, connection) {
  1138. if (remove(connection, self.availableConnections)) return;
  1139. if (remove(connection, self.inUseConnections)) return;
  1140. if (remove(connection, self.connectingConnections)) return;
  1141. if (remove(connection, self.nonAuthenticatedConnections)) return;
  1142. }
  1143. // All event handlers
  1144. var handlers = ['close', 'message', 'error', 'timeout', 'parseError', 'connect'];
  1145. function _createConnection(self) {
  1146. if (self.state === DESTROYED || self.state === DESTROYING) {
  1147. return;
  1148. }
  1149. var connection = new Connection(messageHandler(self), self.options);
  1150. // Push the connection
  1151. self.connectingConnections.push(connection);
  1152. // Handle any errors
  1153. var tempErrorHandler = function(_connection) {
  1154. return function() {
  1155. // Destroy the connection
  1156. _connection.destroy();
  1157. // Remove the connection from the connectingConnections list
  1158. removeConnection(self, _connection);
  1159. // Start reconnection attempts
  1160. if (!self.reconnectId && self.options.reconnect) {
  1161. self.reconnectId = setTimeout(attemptReconnect(self), self.options.reconnectInterval);
  1162. }
  1163. };
  1164. };
  1165. // Handle successful connection
  1166. var tempConnectHandler = function(_connection) {
  1167. return function() {
  1168. // Destroyed state return
  1169. if (self.state === DESTROYED || self.state === DESTROYING) {
  1170. // Remove the connection from the list
  1171. removeConnection(self, _connection);
  1172. return _connection.destroy();
  1173. }
  1174. // Destroy all event emitters
  1175. handlers.forEach(function(e) {
  1176. _connection.removeAllListeners(e);
  1177. });
  1178. // Add the final handlers
  1179. _connection.once('close', connectionFailureHandler(self, 'close'));
  1180. _connection.once('error', connectionFailureHandler(self, 'error'));
  1181. _connection.once('timeout', connectionFailureHandler(self, 'timeout'));
  1182. _connection.once('parseError', connectionFailureHandler(self, 'parseError'));
  1183. // Signal
  1184. reauthenticate(self, _connection, function(err) {
  1185. if (self.state === DESTROYED || self.state === DESTROYING) {
  1186. return _connection.destroy();
  1187. }
  1188. // Remove the connection from the connectingConnections list
  1189. removeConnection(self, _connection);
  1190. // Handle error
  1191. if (err) {
  1192. return _connection.destroy();
  1193. }
  1194. // If we are c at the moment
  1195. // Do not automatially put in available connections
  1196. // As we need to apply the credentials first
  1197. if (self.authenticating) {
  1198. self.nonAuthenticatedConnections.push(_connection);
  1199. } else {
  1200. // Push to available
  1201. self.availableConnections.push(_connection);
  1202. // Execute any work waiting
  1203. _execute(self)();
  1204. }
  1205. });
  1206. };
  1207. };
  1208. // Add all handlers
  1209. connection.once('close', tempErrorHandler(connection));
  1210. connection.once('error', tempErrorHandler(connection));
  1211. connection.once('timeout', tempErrorHandler(connection));
  1212. connection.once('parseError', tempErrorHandler(connection));
  1213. connection.once('connect', tempConnectHandler(connection));
  1214. // Start connection
  1215. connection.connect();
  1216. }
  1217. function flushMonitoringOperations(queue) {
  1218. for (var i = 0; i < queue.length; i++) {
  1219. if (queue[i].monitoring) {
  1220. var workItem = queue[i];
  1221. queue.splice(i, 1);
  1222. workItem.cb(
  1223. new MongoError({ message: 'no connection available for monitoring', driver: true })
  1224. );
  1225. }
  1226. }
  1227. }
  1228. function _execute(self) {
  1229. return function() {
  1230. if (self.state === DESTROYED) return;
  1231. // Already executing, skip
  1232. if (self.executing) return;
  1233. // Set pool as executing
  1234. self.executing = true;
  1235. // Wait for auth to clear before continuing
  1236. function waitForAuth(cb) {
  1237. if (!self.authenticating) return cb();
  1238. // Wait for a milisecond and try again
  1239. setTimeout(function() {
  1240. waitForAuth(cb);
  1241. }, 1);
  1242. }
  1243. // Block on any auth in process
  1244. waitForAuth(function() {
  1245. // New pool connections are in progress, wait them to finish
  1246. // before executing any more operation to ensure distribution of
  1247. // operations
  1248. if (self.connectingConnections.length > 0) {
  1249. return;
  1250. }
  1251. // As long as we have available connections
  1252. // eslint-disable-next-line
  1253. while (true) {
  1254. // Total availble connections
  1255. var totalConnections =
  1256. self.availableConnections.length +
  1257. self.connectingConnections.length +
  1258. self.inUseConnections.length;
  1259. // No available connections available, flush any monitoring ops
  1260. if (self.availableConnections.length === 0) {
  1261. // Flush any monitoring operations
  1262. flushMonitoringOperations(self.queue);
  1263. break;
  1264. }
  1265. // No queue break
  1266. if (self.queue.length === 0) {
  1267. break;
  1268. }
  1269. // Get a connection
  1270. var connection = null;
  1271. // Locate all connections that have no work
  1272. var connections = [];
  1273. // Get a list of all connections
  1274. for (var i = 0; i < self.availableConnections.length; i++) {
  1275. if (self.availableConnections[i].workItems.length === 0) {
  1276. connections.push(self.availableConnections[i]);
  1277. }
  1278. }
  1279. // No connection found that has no work on it, just pick one for pipelining
  1280. if (connections.length === 0) {
  1281. connection =
  1282. self.availableConnections[self.connectionIndex++ % self.availableConnections.length];
  1283. } else {
  1284. connection = connections[self.connectionIndex++ % connections.length];
  1285. }
  1286. // Is the connection connected
  1287. if (connection.isConnected()) {
  1288. // Get the next work item
  1289. var workItem = self.queue.shift();
  1290. // If we are monitoring we need to use a connection that is not
  1291. // running another operation to avoid socket timeout changes
  1292. // affecting an existing operation
  1293. if (workItem.monitoring) {
  1294. var foundValidConnection = false;
  1295. for (i = 0; i < self.availableConnections.length; i++) {
  1296. // If the connection is connected
  1297. // And there are no pending workItems on it
  1298. // Then we can safely use it for monitoring.
  1299. if (
  1300. self.availableConnections[i].isConnected() &&
  1301. self.availableConnections[i].workItems.length === 0
  1302. ) {
  1303. foundValidConnection = true;
  1304. connection = self.availableConnections[i];
  1305. break;
  1306. }
  1307. }
  1308. // No safe connection found, attempt to grow the connections
  1309. // if possible and break from the loop
  1310. if (!foundValidConnection) {
  1311. // Put workItem back on the queue
  1312. self.queue.unshift(workItem);
  1313. // Attempt to grow the pool if it's not yet maxsize
  1314. if (totalConnections < self.options.size && self.queue.length > 0) {
  1315. // Create a new connection
  1316. _createConnection(self);
  1317. }
  1318. // Re-execute the operation
  1319. setTimeout(function() {
  1320. _execute(self)();
  1321. }, 10);
  1322. break;
  1323. }
  1324. }
  1325. // Don't execute operation until we have a full pool
  1326. if (totalConnections < self.options.size) {
  1327. // Connection has work items, then put it back on the queue
  1328. // and create a new connection
  1329. if (connection.workItems.length > 0) {
  1330. // Lets put the workItem back on the list
  1331. self.queue.unshift(workItem);
  1332. // Create a new connection
  1333. _createConnection(self);
  1334. // Break from the loop
  1335. break;
  1336. }
  1337. }
  1338. // Get actual binary commands
  1339. var buffer = workItem.buffer;
  1340. // Set current status of authentication process
  1341. workItem.authenticating = self.authenticating;
  1342. workItem.authenticatingTimestamp = self.authenticatingTimestamp;
  1343. // If we are monitoring take the connection of the availableConnections
  1344. if (workItem.monitoring) {
  1345. moveConnectionBetween(connection, self.availableConnections, self.inUseConnections);
  1346. }
  1347. // Track the executing commands on the mongo server
  1348. // as long as there is an expected response
  1349. if (!workItem.noResponse) {
  1350. connection.workItems.push(workItem);
  1351. }
  1352. // We have a custom socketTimeout
  1353. if (!workItem.immediateRelease && typeof workItem.socketTimeout === 'number') {
  1354. connection.setSocketTimeout(workItem.socketTimeout);
  1355. }
  1356. // Capture if write was successful
  1357. var writeSuccessful = true;
  1358. // Put operation on the wire
  1359. if (Array.isArray(buffer)) {
  1360. for (i = 0; i < buffer.length; i++) {
  1361. writeSuccessful = connection.write(buffer[i]);
  1362. }
  1363. } else {
  1364. writeSuccessful = connection.write(buffer);
  1365. }
  1366. // if the command is designated noResponse, call the callback immeditely
  1367. if (workItem.noResponse && typeof workItem.cb === 'function') {
  1368. workItem.cb(null, null);
  1369. }
  1370. if (writeSuccessful && workItem.immediateRelease && self.authenticating) {
  1371. removeConnection(self, connection);
  1372. self.nonAuthenticatedConnections.push(connection);
  1373. } else if (writeSuccessful === false) {
  1374. // If write not successful put back on queue
  1375. self.queue.unshift(workItem);
  1376. // Remove the disconnected connection
  1377. removeConnection(self, connection);
  1378. // Flush any monitoring operations in the queue, failing fast
  1379. flushMonitoringOperations(self.queue);
  1380. }
  1381. } else {
  1382. // Remove the disconnected connection
  1383. removeConnection(self, connection);
  1384. // Flush any monitoring operations in the queue, failing fast
  1385. flushMonitoringOperations(self.queue);
  1386. }
  1387. }
  1388. });
  1389. self.executing = false;
  1390. };
  1391. }
  1392. // Make execution loop available for testing
  1393. Pool._execute = _execute;
  1394. /**
  1395. * A server connect event, used to verify that the connection is up and running
  1396. *
  1397. * @event Pool#connect
  1398. * @type {Pool}
  1399. */
  1400. /**
  1401. * A server reconnect event, used to verify that pool reconnected.
  1402. *
  1403. * @event Pool#reconnect
  1404. * @type {Pool}
  1405. */
  1406. /**
  1407. * The server connection closed, all pool connections closed
  1408. *
  1409. * @event Pool#close
  1410. * @type {Pool}
  1411. */
  1412. /**
  1413. * The server connection caused an error, all pool connections closed
  1414. *
  1415. * @event Pool#error
  1416. * @type {Pool}
  1417. */
  1418. /**
  1419. * The server connection timed out, all pool connections closed
  1420. *
  1421. * @event Pool#timeout
  1422. * @type {Pool}
  1423. */
  1424. /**
  1425. * The driver experienced an invalid message, all pool connections closed
  1426. *
  1427. * @event Pool#parseError
  1428. * @type {Pool}
  1429. */
  1430. /**
  1431. * The driver attempted to reconnect
  1432. *
  1433. * @event Pool#attemptReconnect
  1434. * @type {Pool}
  1435. */
  1436. /**
  1437. * The driver exhausted all reconnect attempts
  1438. *
  1439. * @event Pool#reconnectFailed
  1440. * @type {Pool}
  1441. */
  1442. module.exports = Pool;