topology.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.ServerCapabilities = exports.Topology = void 0;
  4. const Denque = require("denque");
  5. const timers_1 = require("timers");
  6. const bson_1 = require("../bson");
  7. const connection_string_1 = require("../connection_string");
  8. const constants_1 = require("../constants");
  9. const error_1 = require("../error");
  10. const mongo_types_1 = require("../mongo_types");
  11. const read_preference_1 = require("../read_preference");
  12. const sessions_1 = require("../sessions");
  13. const utils_1 = require("../utils");
  14. const common_1 = require("./common");
  15. const events_1 = require("./events");
  16. const server_1 = require("./server");
  17. const server_description_1 = require("./server_description");
  18. const server_selection_1 = require("./server_selection");
  19. const srv_polling_1 = require("./srv_polling");
  20. const topology_description_1 = require("./topology_description");
  21. // Global state
  22. let globalTopologyCounter = 0;
  23. const stateTransition = (0, utils_1.makeStateMachine)({
  24. [common_1.STATE_CLOSED]: [common_1.STATE_CLOSED, common_1.STATE_CONNECTING],
  25. [common_1.STATE_CONNECTING]: [common_1.STATE_CONNECTING, common_1.STATE_CLOSING, common_1.STATE_CONNECTED, common_1.STATE_CLOSED],
  26. [common_1.STATE_CONNECTED]: [common_1.STATE_CONNECTED, common_1.STATE_CLOSING, common_1.STATE_CLOSED],
  27. [common_1.STATE_CLOSING]: [common_1.STATE_CLOSING, common_1.STATE_CLOSED]
  28. });
  29. /** @internal */
  30. const kCancelled = Symbol('cancelled');
  31. /** @internal */
  32. const kWaitQueue = Symbol('waitQueue');
  33. /**
  34. * A container of server instances representing a connection to a MongoDB topology.
  35. * @internal
  36. */
  37. class Topology extends mongo_types_1.TypedEventEmitter {
  38. /**
  39. * @param seedlist - a list of HostAddress instances to connect to
  40. */
  41. constructor(seeds, options) {
  42. var _a;
  43. super();
  44. // Legacy CSFLE support
  45. this.bson = Object.create(null);
  46. this.bson.serialize = bson_1.serialize;
  47. this.bson.deserialize = bson_1.deserialize;
  48. // Options should only be undefined in tests, MongoClient will always have defined options
  49. options = options !== null && options !== void 0 ? options : {
  50. hosts: [utils_1.HostAddress.fromString('localhost:27017')],
  51. ...Object.fromEntries(connection_string_1.DEFAULT_OPTIONS.entries()),
  52. ...Object.fromEntries(connection_string_1.FEATURE_FLAGS.entries())
  53. };
  54. if (typeof seeds === 'string') {
  55. seeds = [utils_1.HostAddress.fromString(seeds)];
  56. }
  57. else if (!Array.isArray(seeds)) {
  58. seeds = [seeds];
  59. }
  60. const seedlist = [];
  61. for (const seed of seeds) {
  62. if (typeof seed === 'string') {
  63. seedlist.push(utils_1.HostAddress.fromString(seed));
  64. }
  65. else if (seed instanceof utils_1.HostAddress) {
  66. seedlist.push(seed);
  67. }
  68. else {
  69. // FIXME(NODE-3483): May need to be a MongoParseError
  70. throw new error_1.MongoRuntimeError(`Topology cannot be constructed from ${JSON.stringify(seed)}`);
  71. }
  72. }
  73. const topologyType = topologyTypeFromOptions(options);
  74. const topologyId = globalTopologyCounter++;
  75. const selectedHosts = options.srvMaxHosts == null ||
  76. options.srvMaxHosts === 0 ||
  77. options.srvMaxHosts >= seedlist.length
  78. ? seedlist
  79. : (0, utils_1.shuffle)(seedlist, options.srvMaxHosts);
  80. const serverDescriptions = new Map();
  81. for (const hostAddress of selectedHosts) {
  82. serverDescriptions.set(hostAddress.toString(), new server_description_1.ServerDescription(hostAddress));
  83. }
  84. this[kWaitQueue] = new Denque();
  85. this.s = {
  86. // the id of this topology
  87. id: topologyId,
  88. // passed in options
  89. options,
  90. // initial seedlist of servers to connect to
  91. seedlist,
  92. // initial state
  93. state: common_1.STATE_CLOSED,
  94. // the topology description
  95. description: new topology_description_1.TopologyDescription(topologyType, serverDescriptions, options.replicaSet, undefined, undefined, undefined, options),
  96. serverSelectionTimeoutMS: options.serverSelectionTimeoutMS,
  97. heartbeatFrequencyMS: options.heartbeatFrequencyMS,
  98. minHeartbeatFrequencyMS: options.minHeartbeatFrequencyMS,
  99. // a map of server instances to normalized addresses
  100. servers: new Map(),
  101. // Server Session Pool
  102. sessionPool: new sessions_1.ServerSessionPool(this),
  103. // Active client sessions
  104. sessions: new Set(),
  105. credentials: options === null || options === void 0 ? void 0 : options.credentials,
  106. clusterTime: undefined,
  107. // timer management
  108. connectionTimers: new Set(),
  109. detectShardedTopology: ev => this.detectShardedTopology(ev),
  110. detectSrvRecords: ev => this.detectSrvRecords(ev)
  111. };
  112. if (options.srvHost && !options.loadBalanced) {
  113. this.s.srvPoller =
  114. (_a = options.srvPoller) !== null && _a !== void 0 ? _a : new srv_polling_1.SrvPoller({
  115. heartbeatFrequencyMS: this.s.heartbeatFrequencyMS,
  116. srvHost: options.srvHost,
  117. srvMaxHosts: options.srvMaxHosts,
  118. srvServiceName: options.srvServiceName
  119. });
  120. this.on(Topology.TOPOLOGY_DESCRIPTION_CHANGED, this.s.detectShardedTopology);
  121. }
  122. }
  123. detectShardedTopology(event) {
  124. var _a, _b, _c;
  125. const previousType = event.previousDescription.type;
  126. const newType = event.newDescription.type;
  127. const transitionToSharded = previousType !== common_1.TopologyType.Sharded && newType === common_1.TopologyType.Sharded;
  128. const srvListeners = (_a = this.s.srvPoller) === null || _a === void 0 ? void 0 : _a.listeners(srv_polling_1.SrvPoller.SRV_RECORD_DISCOVERY);
  129. const listeningToSrvPolling = !!(srvListeners === null || srvListeners === void 0 ? void 0 : srvListeners.includes(this.s.detectSrvRecords));
  130. if (transitionToSharded && !listeningToSrvPolling) {
  131. (_b = this.s.srvPoller) === null || _b === void 0 ? void 0 : _b.on(srv_polling_1.SrvPoller.SRV_RECORD_DISCOVERY, this.s.detectSrvRecords);
  132. (_c = this.s.srvPoller) === null || _c === void 0 ? void 0 : _c.start();
  133. }
  134. }
  135. detectSrvRecords(ev) {
  136. const previousTopologyDescription = this.s.description;
  137. this.s.description = this.s.description.updateFromSrvPollingEvent(ev, this.s.options.srvMaxHosts);
  138. if (this.s.description === previousTopologyDescription) {
  139. // Nothing changed, so return
  140. return;
  141. }
  142. updateServers(this);
  143. this.emit(Topology.TOPOLOGY_DESCRIPTION_CHANGED, new events_1.TopologyDescriptionChangedEvent(this.s.id, previousTopologyDescription, this.s.description));
  144. }
  145. /**
  146. * @returns A `TopologyDescription` for this topology
  147. */
  148. get description() {
  149. return this.s.description;
  150. }
  151. get loadBalanced() {
  152. return this.s.options.loadBalanced;
  153. }
  154. get capabilities() {
  155. return new ServerCapabilities(this.lastHello());
  156. }
  157. /** Initiate server connect */
  158. connect(options, callback) {
  159. var _a;
  160. if (typeof options === 'function')
  161. (callback = options), (options = {});
  162. options = options !== null && options !== void 0 ? options : {};
  163. if (this.s.state === common_1.STATE_CONNECTED) {
  164. if (typeof callback === 'function') {
  165. callback();
  166. }
  167. return;
  168. }
  169. stateTransition(this, common_1.STATE_CONNECTING);
  170. // emit SDAM monitoring events
  171. this.emit(Topology.TOPOLOGY_OPENING, new events_1.TopologyOpeningEvent(this.s.id));
  172. // emit an event for the topology change
  173. this.emit(Topology.TOPOLOGY_DESCRIPTION_CHANGED, new events_1.TopologyDescriptionChangedEvent(this.s.id, new topology_description_1.TopologyDescription(common_1.TopologyType.Unknown), // initial is always Unknown
  174. this.s.description));
  175. // connect all known servers, then attempt server selection to connect
  176. const serverDescriptions = Array.from(this.s.description.servers.values());
  177. this.s.servers = new Map(serverDescriptions.map(serverDescription => [
  178. serverDescription.address,
  179. createAndConnectServer(this, serverDescription)
  180. ]));
  181. // In load balancer mode we need to fake a server description getting
  182. // emitted from the monitor, since the monitor doesn't exist.
  183. if (this.s.options.loadBalanced) {
  184. for (const description of serverDescriptions) {
  185. const newDescription = new server_description_1.ServerDescription(description.hostAddress, undefined, {
  186. loadBalanced: this.s.options.loadBalanced
  187. });
  188. this.serverUpdateHandler(newDescription);
  189. }
  190. }
  191. const readPreference = (_a = options.readPreference) !== null && _a !== void 0 ? _a : read_preference_1.ReadPreference.primary;
  192. this.selectServer((0, server_selection_1.readPreferenceServerSelector)(readPreference), options, (err, server) => {
  193. if (err) {
  194. this.close();
  195. typeof callback === 'function' ? callback(err) : this.emit(Topology.ERROR, err);
  196. return;
  197. }
  198. // TODO: NODE-2471
  199. const skipPingOnConnect = this.s.options[Symbol.for('@@mdb.skipPingOnConnect')] === true;
  200. if (!skipPingOnConnect && server && this.s.credentials) {
  201. server.command((0, utils_1.ns)('admin.$cmd'), { ping: 1 }, {}, err => {
  202. if (err) {
  203. typeof callback === 'function' ? callback(err) : this.emit(Topology.ERROR, err);
  204. return;
  205. }
  206. stateTransition(this, common_1.STATE_CONNECTED);
  207. this.emit(Topology.OPEN, this);
  208. this.emit(Topology.CONNECT, this);
  209. if (typeof callback === 'function')
  210. callback(undefined, this);
  211. });
  212. return;
  213. }
  214. stateTransition(this, common_1.STATE_CONNECTED);
  215. this.emit(Topology.OPEN, this);
  216. this.emit(Topology.CONNECT, this);
  217. if (typeof callback === 'function')
  218. callback(undefined, this);
  219. });
  220. }
  221. /** Close this topology */
  222. close(options, callback) {
  223. if (typeof options === 'function') {
  224. callback = options;
  225. options = {};
  226. }
  227. if (typeof options === 'boolean') {
  228. options = { force: options };
  229. }
  230. options = options !== null && options !== void 0 ? options : {};
  231. if (this.s.state === common_1.STATE_CLOSED || this.s.state === common_1.STATE_CLOSING) {
  232. if (typeof callback === 'function') {
  233. callback();
  234. }
  235. return;
  236. }
  237. stateTransition(this, common_1.STATE_CLOSING);
  238. drainWaitQueue(this[kWaitQueue], new error_1.MongoTopologyClosedError());
  239. (0, common_1.drainTimerQueue)(this.s.connectionTimers);
  240. if (this.s.srvPoller) {
  241. this.s.srvPoller.stop();
  242. this.s.srvPoller.removeListener(srv_polling_1.SrvPoller.SRV_RECORD_DISCOVERY, this.s.detectSrvRecords);
  243. }
  244. this.removeListener(Topology.TOPOLOGY_DESCRIPTION_CHANGED, this.s.detectShardedTopology);
  245. (0, utils_1.eachAsync)(Array.from(this.s.sessions.values()), (session, cb) => session.endSession(cb), () => {
  246. this.s.sessionPool.endAllPooledSessions(() => {
  247. (0, utils_1.eachAsync)(Array.from(this.s.servers.values()), (server, cb) => destroyServer(server, this, options, cb), err => {
  248. this.s.servers.clear();
  249. // emit an event for close
  250. this.emit(Topology.TOPOLOGY_CLOSED, new events_1.TopologyClosedEvent(this.s.id));
  251. stateTransition(this, common_1.STATE_CLOSED);
  252. if (typeof callback === 'function') {
  253. callback(err);
  254. }
  255. });
  256. });
  257. });
  258. }
  259. /**
  260. * Selects a server according to the selection predicate provided
  261. *
  262. * @param selector - An optional selector to select servers by, defaults to a random selection within a latency window
  263. * @param options - Optional settings related to server selection
  264. * @param callback - The callback used to indicate success or failure
  265. * @returns An instance of a `Server` meeting the criteria of the predicate provided
  266. */
  267. selectServer(selector, options, callback) {
  268. let serverSelector;
  269. if (typeof selector !== 'function') {
  270. if (typeof selector === 'string') {
  271. serverSelector = (0, server_selection_1.readPreferenceServerSelector)(read_preference_1.ReadPreference.fromString(selector));
  272. }
  273. else {
  274. let readPreference;
  275. if (selector instanceof read_preference_1.ReadPreference) {
  276. readPreference = selector;
  277. }
  278. else {
  279. read_preference_1.ReadPreference.translate(options);
  280. readPreference = options.readPreference || read_preference_1.ReadPreference.primary;
  281. }
  282. serverSelector = (0, server_selection_1.readPreferenceServerSelector)(readPreference);
  283. }
  284. }
  285. else {
  286. serverSelector = selector;
  287. }
  288. options = Object.assign({}, { serverSelectionTimeoutMS: this.s.serverSelectionTimeoutMS }, options);
  289. const isSharded = this.description.type === common_1.TopologyType.Sharded;
  290. const session = options.session;
  291. const transaction = session && session.transaction;
  292. if (isSharded && transaction && transaction.server) {
  293. callback(undefined, transaction.server);
  294. return;
  295. }
  296. const waitQueueMember = {
  297. serverSelector,
  298. transaction,
  299. callback
  300. };
  301. const serverSelectionTimeoutMS = options.serverSelectionTimeoutMS;
  302. if (serverSelectionTimeoutMS) {
  303. waitQueueMember.timer = (0, timers_1.setTimeout)(() => {
  304. waitQueueMember[kCancelled] = true;
  305. waitQueueMember.timer = undefined;
  306. const timeoutError = new error_1.MongoServerSelectionError(`Server selection timed out after ${serverSelectionTimeoutMS} ms`, this.description);
  307. waitQueueMember.callback(timeoutError);
  308. }, serverSelectionTimeoutMS);
  309. }
  310. this[kWaitQueue].push(waitQueueMember);
  311. processWaitQueue(this);
  312. }
  313. // Sessions related methods
  314. /**
  315. * @returns Whether the topology should initiate selection to determine session support
  316. */
  317. shouldCheckForSessionSupport() {
  318. if (this.description.type === common_1.TopologyType.Single) {
  319. return !this.description.hasKnownServers;
  320. }
  321. return !this.description.hasDataBearingServers;
  322. }
  323. /**
  324. * @returns Whether sessions are supported on the current topology
  325. */
  326. hasSessionSupport() {
  327. return this.loadBalanced || this.description.logicalSessionTimeoutMinutes != null;
  328. }
  329. /** Start a logical session */
  330. startSession(options, clientOptions) {
  331. const session = new sessions_1.ClientSession(this.client, this.s.sessionPool, options, clientOptions);
  332. session.once('ended', () => {
  333. this.s.sessions.delete(session);
  334. });
  335. this.s.sessions.add(session);
  336. return session;
  337. }
  338. /** Send endSessions command(s) with the given session ids */
  339. endSessions(sessions, callback) {
  340. if (!Array.isArray(sessions)) {
  341. sessions = [sessions];
  342. }
  343. this.selectServer((0, server_selection_1.readPreferenceServerSelector)(read_preference_1.ReadPreference.primaryPreferred), {}, (err, server) => {
  344. if (err || !server) {
  345. if (typeof callback === 'function')
  346. callback(err);
  347. return;
  348. }
  349. server.command((0, utils_1.ns)('admin.$cmd'), { endSessions: sessions }, { noResponse: true }, (err, result) => {
  350. if (typeof callback === 'function')
  351. callback(err, result);
  352. });
  353. });
  354. }
  355. /**
  356. * Update the internal TopologyDescription with a ServerDescription
  357. *
  358. * @param serverDescription - The server to update in the internal list of server descriptions
  359. */
  360. serverUpdateHandler(serverDescription) {
  361. if (!this.s.description.hasServer(serverDescription.address)) {
  362. return;
  363. }
  364. // ignore this server update if its from an outdated topologyVersion
  365. if (isStaleServerDescription(this.s.description, serverDescription)) {
  366. return;
  367. }
  368. // these will be used for monitoring events later
  369. const previousTopologyDescription = this.s.description;
  370. const previousServerDescription = this.s.description.servers.get(serverDescription.address);
  371. if (!previousServerDescription) {
  372. return;
  373. }
  374. // Driver Sessions Spec: "Whenever a driver receives a cluster time from
  375. // a server it MUST compare it to the current highest seen cluster time
  376. // for the deployment. If the new cluster time is higher than the
  377. // highest seen cluster time it MUST become the new highest seen cluster
  378. // time. Two cluster times are compared using only the BsonTimestamp
  379. // value of the clusterTime embedded field."
  380. const clusterTime = serverDescription.$clusterTime;
  381. if (clusterTime) {
  382. (0, common_1._advanceClusterTime)(this, clusterTime);
  383. }
  384. // If we already know all the information contained in this updated description, then
  385. // we don't need to emit SDAM events, but still need to update the description, in order
  386. // to keep client-tracked attributes like last update time and round trip time up to date
  387. const equalDescriptions = previousServerDescription && previousServerDescription.equals(serverDescription);
  388. // first update the TopologyDescription
  389. this.s.description = this.s.description.update(serverDescription);
  390. if (this.s.description.compatibilityError) {
  391. this.emit(Topology.ERROR, new error_1.MongoCompatibilityError(this.s.description.compatibilityError));
  392. return;
  393. }
  394. // emit monitoring events for this change
  395. if (!equalDescriptions) {
  396. const newDescription = this.s.description.servers.get(serverDescription.address);
  397. if (newDescription) {
  398. this.emit(Topology.SERVER_DESCRIPTION_CHANGED, new events_1.ServerDescriptionChangedEvent(this.s.id, serverDescription.address, previousServerDescription, newDescription));
  399. }
  400. }
  401. // update server list from updated descriptions
  402. updateServers(this, serverDescription);
  403. // attempt to resolve any outstanding server selection attempts
  404. if (this[kWaitQueue].length > 0) {
  405. processWaitQueue(this);
  406. }
  407. if (!equalDescriptions) {
  408. this.emit(Topology.TOPOLOGY_DESCRIPTION_CHANGED, new events_1.TopologyDescriptionChangedEvent(this.s.id, previousTopologyDescription, this.s.description));
  409. }
  410. }
  411. auth(credentials, callback) {
  412. if (typeof credentials === 'function')
  413. (callback = credentials), (credentials = undefined);
  414. if (typeof callback === 'function')
  415. callback(undefined, true);
  416. }
  417. get clientMetadata() {
  418. return this.s.options.metadata;
  419. }
  420. isConnected() {
  421. return this.s.state === common_1.STATE_CONNECTED;
  422. }
  423. isDestroyed() {
  424. return this.s.state === common_1.STATE_CLOSED;
  425. }
  426. /**
  427. * @deprecated This function is deprecated and will be removed in the next major version.
  428. */
  429. unref() {
  430. (0, utils_1.emitWarning)('`unref` is a noop and will be removed in the next major version');
  431. }
  432. // NOTE: There are many places in code where we explicitly check the last hello
  433. // to do feature support detection. This should be done any other way, but for
  434. // now we will just return the first hello seen, which should suffice.
  435. lastHello() {
  436. const serverDescriptions = Array.from(this.description.servers.values());
  437. if (serverDescriptions.length === 0)
  438. return {};
  439. const sd = serverDescriptions.filter((sd) => sd.type !== common_1.ServerType.Unknown)[0];
  440. const result = sd || { maxWireVersion: this.description.commonWireVersion };
  441. return result;
  442. }
  443. get commonWireVersion() {
  444. return this.description.commonWireVersion;
  445. }
  446. get logicalSessionTimeoutMinutes() {
  447. return this.description.logicalSessionTimeoutMinutes;
  448. }
  449. get clusterTime() {
  450. return this.s.clusterTime;
  451. }
  452. set clusterTime(clusterTime) {
  453. this.s.clusterTime = clusterTime;
  454. }
  455. }
  456. exports.Topology = Topology;
  457. /** @event */
  458. Topology.SERVER_OPENING = constants_1.SERVER_OPENING;
  459. /** @event */
  460. Topology.SERVER_CLOSED = constants_1.SERVER_CLOSED;
  461. /** @event */
  462. Topology.SERVER_DESCRIPTION_CHANGED = constants_1.SERVER_DESCRIPTION_CHANGED;
  463. /** @event */
  464. Topology.TOPOLOGY_OPENING = constants_1.TOPOLOGY_OPENING;
  465. /** @event */
  466. Topology.TOPOLOGY_CLOSED = constants_1.TOPOLOGY_CLOSED;
  467. /** @event */
  468. Topology.TOPOLOGY_DESCRIPTION_CHANGED = constants_1.TOPOLOGY_DESCRIPTION_CHANGED;
  469. /** @event */
  470. Topology.ERROR = constants_1.ERROR;
  471. /** @event */
  472. Topology.OPEN = constants_1.OPEN;
  473. /** @event */
  474. Topology.CONNECT = constants_1.CONNECT;
  475. /** @event */
  476. Topology.CLOSE = constants_1.CLOSE;
  477. /** @event */
  478. Topology.TIMEOUT = constants_1.TIMEOUT;
  479. /** Destroys a server, and removes all event listeners from the instance */
  480. function destroyServer(server, topology, options, callback) {
  481. options = options !== null && options !== void 0 ? options : {};
  482. for (const event of constants_1.LOCAL_SERVER_EVENTS) {
  483. server.removeAllListeners(event);
  484. }
  485. server.destroy(options, () => {
  486. topology.emit(Topology.SERVER_CLOSED, new events_1.ServerClosedEvent(topology.s.id, server.description.address));
  487. for (const event of constants_1.SERVER_RELAY_EVENTS) {
  488. server.removeAllListeners(event);
  489. }
  490. if (typeof callback === 'function') {
  491. callback();
  492. }
  493. });
  494. }
  495. /** Predicts the TopologyType from options */
  496. function topologyTypeFromOptions(options) {
  497. if (options === null || options === void 0 ? void 0 : options.directConnection) {
  498. return common_1.TopologyType.Single;
  499. }
  500. if (options === null || options === void 0 ? void 0 : options.replicaSet) {
  501. return common_1.TopologyType.ReplicaSetNoPrimary;
  502. }
  503. if (options === null || options === void 0 ? void 0 : options.loadBalanced) {
  504. return common_1.TopologyType.LoadBalanced;
  505. }
  506. return common_1.TopologyType.Unknown;
  507. }
  508. /**
  509. * Creates new server instances and attempts to connect them
  510. *
  511. * @param topology - The topology that this server belongs to
  512. * @param serverDescription - The description for the server to initialize and connect to
  513. */
  514. function createAndConnectServer(topology, serverDescription) {
  515. topology.emit(Topology.SERVER_OPENING, new events_1.ServerOpeningEvent(topology.s.id, serverDescription.address));
  516. const server = new server_1.Server(topology, serverDescription, topology.s.options);
  517. for (const event of constants_1.SERVER_RELAY_EVENTS) {
  518. server.on(event, (e) => topology.emit(event, e));
  519. }
  520. server.on(server_1.Server.DESCRIPTION_RECEIVED, description => topology.serverUpdateHandler(description));
  521. server.connect();
  522. return server;
  523. }
  524. /**
  525. * @param topology - Topology to update.
  526. * @param incomingServerDescription - New server description.
  527. */
  528. function updateServers(topology, incomingServerDescription) {
  529. // update the internal server's description
  530. if (incomingServerDescription && topology.s.servers.has(incomingServerDescription.address)) {
  531. const server = topology.s.servers.get(incomingServerDescription.address);
  532. if (server) {
  533. server.s.description = incomingServerDescription;
  534. }
  535. }
  536. // add new servers for all descriptions we currently don't know about locally
  537. for (const serverDescription of topology.description.servers.values()) {
  538. if (!topology.s.servers.has(serverDescription.address)) {
  539. const server = createAndConnectServer(topology, serverDescription);
  540. topology.s.servers.set(serverDescription.address, server);
  541. }
  542. }
  543. // for all servers no longer known, remove their descriptions and destroy their instances
  544. for (const entry of topology.s.servers) {
  545. const serverAddress = entry[0];
  546. if (topology.description.hasServer(serverAddress)) {
  547. continue;
  548. }
  549. if (!topology.s.servers.has(serverAddress)) {
  550. continue;
  551. }
  552. const server = topology.s.servers.get(serverAddress);
  553. topology.s.servers.delete(serverAddress);
  554. // prepare server for garbage collection
  555. if (server) {
  556. destroyServer(server, topology);
  557. }
  558. }
  559. }
  560. function drainWaitQueue(queue, err) {
  561. while (queue.length) {
  562. const waitQueueMember = queue.shift();
  563. if (!waitQueueMember) {
  564. continue;
  565. }
  566. if (waitQueueMember.timer) {
  567. clearTimeout(waitQueueMember.timer);
  568. }
  569. if (!waitQueueMember[kCancelled]) {
  570. waitQueueMember.callback(err);
  571. }
  572. }
  573. }
  574. function processWaitQueue(topology) {
  575. if (topology.s.state === common_1.STATE_CLOSED) {
  576. drainWaitQueue(topology[kWaitQueue], new error_1.MongoTopologyClosedError());
  577. return;
  578. }
  579. const isSharded = topology.description.type === common_1.TopologyType.Sharded;
  580. const serverDescriptions = Array.from(topology.description.servers.values());
  581. const membersToProcess = topology[kWaitQueue].length;
  582. for (let i = 0; i < membersToProcess; ++i) {
  583. const waitQueueMember = topology[kWaitQueue].shift();
  584. if (!waitQueueMember) {
  585. continue;
  586. }
  587. if (waitQueueMember[kCancelled]) {
  588. continue;
  589. }
  590. let selectedDescriptions;
  591. try {
  592. const serverSelector = waitQueueMember.serverSelector;
  593. selectedDescriptions = serverSelector
  594. ? serverSelector(topology.description, serverDescriptions)
  595. : serverDescriptions;
  596. }
  597. catch (e) {
  598. if (waitQueueMember.timer) {
  599. clearTimeout(waitQueueMember.timer);
  600. }
  601. waitQueueMember.callback(e);
  602. continue;
  603. }
  604. let selectedServer;
  605. if (selectedDescriptions.length === 0) {
  606. topology[kWaitQueue].push(waitQueueMember);
  607. continue;
  608. }
  609. else if (selectedDescriptions.length === 1) {
  610. selectedServer = topology.s.servers.get(selectedDescriptions[0].address);
  611. }
  612. else {
  613. // don't shuffle the array if there are only two elements
  614. const descriptions = selectedDescriptions.length === 2 ? selectedDescriptions : (0, utils_1.shuffle)(selectedDescriptions, 2);
  615. const server1 = topology.s.servers.get(descriptions[0].address);
  616. const server2 = topology.s.servers.get(descriptions[1].address);
  617. selectedServer =
  618. server1 && server2 && server1.s.operationCount < server2.s.operationCount
  619. ? server1
  620. : server2;
  621. }
  622. if (!selectedServer) {
  623. waitQueueMember.callback(new error_1.MongoServerSelectionError('server selection returned a server description but the server was not found in the topology', topology.description));
  624. return;
  625. }
  626. const transaction = waitQueueMember.transaction;
  627. if (isSharded && transaction && transaction.isActive && selectedServer) {
  628. transaction.pinServer(selectedServer);
  629. }
  630. if (waitQueueMember.timer) {
  631. clearTimeout(waitQueueMember.timer);
  632. }
  633. waitQueueMember.callback(undefined, selectedServer);
  634. }
  635. if (topology[kWaitQueue].length > 0) {
  636. // ensure all server monitors attempt monitoring soon
  637. for (const [, server] of topology.s.servers) {
  638. process.nextTick(function scheduleServerCheck() {
  639. return server.requestCheck();
  640. });
  641. }
  642. }
  643. }
  644. function isStaleServerDescription(topologyDescription, incomingServerDescription) {
  645. const currentServerDescription = topologyDescription.servers.get(incomingServerDescription.address);
  646. const currentTopologyVersion = currentServerDescription === null || currentServerDescription === void 0 ? void 0 : currentServerDescription.topologyVersion;
  647. return ((0, server_description_1.compareTopologyVersion)(currentTopologyVersion, incomingServerDescription.topologyVersion) > 0);
  648. }
  649. /** @public */
  650. class ServerCapabilities {
  651. constructor(hello) {
  652. this.minWireVersion = hello.minWireVersion || 0;
  653. this.maxWireVersion = hello.maxWireVersion || 0;
  654. }
  655. get hasAggregationCursor() {
  656. return this.maxWireVersion >= 1;
  657. }
  658. get hasWriteCommands() {
  659. return this.maxWireVersion >= 2;
  660. }
  661. get hasTextSearch() {
  662. return this.minWireVersion >= 0;
  663. }
  664. get hasAuthCommands() {
  665. return this.maxWireVersion >= 1;
  666. }
  667. get hasListCollectionsCommand() {
  668. return this.maxWireVersion >= 3;
  669. }
  670. get hasListIndexesCommand() {
  671. return this.maxWireVersion >= 3;
  672. }
  673. get supportsSnapshotReads() {
  674. return this.maxWireVersion >= 13;
  675. }
  676. get commandsTakeWriteConcern() {
  677. return this.maxWireVersion >= 5;
  678. }
  679. get commandsTakeCollation() {
  680. return this.maxWireVersion >= 5;
  681. }
  682. }
  683. exports.ServerCapabilities = ServerCapabilities;
  684. //# sourceMappingURL=topology.js.map