mongodb 3.2.5 → 3.3.0-beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HISTORY.md +0 -10
- package/index.js +4 -4
- package/lib/admin.js +56 -56
- package/lib/aggregation_cursor.js +7 -3
- package/lib/bulk/common.js +18 -13
- package/lib/change_stream.js +196 -89
- package/lib/collection.js +217 -169
- package/lib/command_cursor.js +17 -7
- package/lib/core/auth/auth_provider.js +158 -0
- package/lib/core/auth/defaultAuthProviders.js +29 -0
- package/lib/core/auth/gssapi.js +241 -0
- package/lib/core/auth/mongo_credentials.js +81 -0
- package/lib/core/auth/mongocr.js +51 -0
- package/lib/core/auth/plain.js +35 -0
- package/lib/core/auth/scram.js +293 -0
- package/lib/core/auth/sspi.js +131 -0
- package/lib/core/auth/x509.js +26 -0
- package/lib/core/connection/apm.js +236 -0
- package/lib/core/connection/command_result.js +36 -0
- package/lib/core/connection/commands.js +507 -0
- package/lib/core/connection/connect.js +370 -0
- package/lib/core/connection/connection.js +624 -0
- package/lib/core/connection/logger.js +246 -0
- package/lib/core/connection/msg.js +219 -0
- package/lib/core/connection/pool.js +1285 -0
- package/lib/core/connection/utils.js +57 -0
- package/lib/core/cursor.js +752 -0
- package/lib/core/error.js +186 -0
- package/lib/core/index.js +50 -0
- package/lib/core/sdam/monitoring.js +228 -0
- package/lib/core/sdam/server.js +467 -0
- package/lib/core/sdam/server_description.js +163 -0
- package/lib/core/sdam/server_selectors.js +244 -0
- package/lib/core/sdam/srv_polling.js +135 -0
- package/lib/core/sdam/topology.js +1151 -0
- package/lib/core/sdam/topology_description.js +408 -0
- package/lib/core/sessions.js +711 -0
- package/lib/core/tools/smoke_plugin.js +61 -0
- package/lib/core/topologies/mongos.js +1337 -0
- package/lib/core/topologies/read_preference.js +202 -0
- package/lib/core/topologies/replset.js +1507 -0
- package/lib/core/topologies/replset_state.js +1121 -0
- package/lib/core/topologies/server.js +984 -0
- package/lib/core/topologies/shared.js +453 -0
- package/lib/core/transactions.js +167 -0
- package/lib/core/uri_parser.js +631 -0
- package/lib/core/utils.js +165 -0
- package/lib/core/wireprotocol/command.js +170 -0
- package/lib/core/wireprotocol/compression.js +73 -0
- package/lib/core/wireprotocol/constants.js +13 -0
- package/lib/core/wireprotocol/get_more.js +86 -0
- package/lib/core/wireprotocol/index.js +18 -0
- package/lib/core/wireprotocol/kill_cursors.js +70 -0
- package/lib/core/wireprotocol/query.js +224 -0
- package/lib/core/wireprotocol/shared.js +115 -0
- package/lib/core/wireprotocol/write_command.js +50 -0
- package/lib/cursor.js +40 -46
- package/lib/db.js +141 -95
- package/lib/dynamic_loaders.js +32 -0
- package/lib/error.js +12 -10
- package/lib/gridfs/chunk.js +2 -2
- package/lib/gridfs/grid_store.js +31 -25
- package/lib/gridfs-stream/index.js +4 -4
- package/lib/gridfs-stream/upload.js +1 -1
- package/lib/mongo_client.js +37 -15
- package/lib/operations/add_user.js +96 -0
- package/lib/operations/aggregate.js +24 -13
- package/lib/operations/aggregate_operation.js +127 -0
- package/lib/operations/bulk_write.js +104 -0
- package/lib/operations/close.js +47 -0
- package/lib/operations/collection_ops.js +28 -287
- package/lib/operations/collections.js +55 -0
- package/lib/operations/command.js +120 -0
- package/lib/operations/command_v2.js +43 -0
- package/lib/operations/common_functions.js +372 -0
- package/lib/operations/{mongo_client_ops.js → connect.js} +185 -157
- package/lib/operations/count.js +72 -0
- package/lib/operations/count_documents.js +46 -0
- package/lib/operations/create_collection.js +118 -0
- package/lib/operations/create_index.js +92 -0
- package/lib/operations/create_indexes.js +61 -0
- package/lib/operations/cursor_ops.js +3 -4
- package/lib/operations/db_ops.js +15 -12
- package/lib/operations/delete_many.js +25 -0
- package/lib/operations/delete_one.js +25 -0
- package/lib/operations/distinct.js +85 -0
- package/lib/operations/drop.js +53 -0
- package/lib/operations/drop_index.js +42 -0
- package/lib/operations/drop_indexes.js +23 -0
- package/lib/operations/estimated_document_count.js +33 -0
- package/lib/operations/execute_db_admin_command.js +34 -0
- package/lib/operations/execute_operation.js +165 -0
- package/lib/operations/explain.js +23 -0
- package/lib/operations/find_and_modify.js +98 -0
- package/lib/operations/find_one.js +33 -0
- package/lib/operations/find_one_and_delete.js +16 -0
- package/lib/operations/find_one_and_replace.js +18 -0
- package/lib/operations/find_one_and_update.js +19 -0
- package/lib/operations/geo_haystack_search.js +79 -0
- package/lib/operations/has_next.js +40 -0
- package/lib/operations/index_exists.js +39 -0
- package/lib/operations/index_information.js +23 -0
- package/lib/operations/indexes.js +22 -0
- package/lib/operations/insert_many.js +63 -0
- package/lib/operations/insert_one.js +75 -0
- package/lib/operations/is_capped.js +19 -0
- package/lib/operations/list_indexes.js +66 -0
- package/lib/operations/map_reduce.js +189 -0
- package/lib/operations/next.js +32 -0
- package/lib/operations/operation.js +63 -0
- package/lib/operations/options_operation.js +32 -0
- package/lib/operations/profiling_level.js +31 -0
- package/lib/operations/re_index.js +28 -0
- package/lib/operations/remove_user.js +52 -0
- package/lib/operations/rename.js +61 -0
- package/lib/operations/replace_one.js +47 -0
- package/lib/operations/set_profiling_level.js +48 -0
- package/lib/operations/stats.js +45 -0
- package/lib/operations/to_array.js +68 -0
- package/lib/operations/update_many.js +29 -0
- package/lib/operations/update_one.js +44 -0
- package/lib/operations/validate_collection.js +40 -0
- package/lib/read_concern.js +55 -0
- package/lib/topologies/mongos.js +3 -3
- package/lib/topologies/native_topology.js +22 -2
- package/lib/topologies/replset.js +3 -3
- package/lib/topologies/server.js +4 -4
- package/lib/topologies/topology_base.js +6 -6
- package/lib/url_parser.js +4 -3
- package/lib/utils.js +46 -59
- package/lib/write_concern.js +66 -0
- package/package.json +15 -6
- package/lib/.DS_Store +0 -0
|
@@ -0,0 +1,1151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const EventEmitter = require('events');
|
|
3
|
+
const ServerDescription = require('./server_description').ServerDescription;
|
|
4
|
+
const ServerType = require('./server_description').ServerType;
|
|
5
|
+
const TopologyDescription = require('./topology_description').TopologyDescription;
|
|
6
|
+
const TopologyType = require('./topology_description').TopologyType;
|
|
7
|
+
const monitoring = require('./monitoring');
|
|
8
|
+
const calculateDurationInMs = require('../utils').calculateDurationInMs;
|
|
9
|
+
const MongoTimeoutError = require('../error').MongoTimeoutError;
|
|
10
|
+
const Server = require('./server');
|
|
11
|
+
const relayEvents = require('../utils').relayEvents;
|
|
12
|
+
const ReadPreference = require('../topologies/read_preference');
|
|
13
|
+
const readPreferenceServerSelector = require('./server_selectors').readPreferenceServerSelector;
|
|
14
|
+
const writableServerSelector = require('./server_selectors').writableServerSelector;
|
|
15
|
+
const isRetryableWritesSupported = require('../topologies/shared').isRetryableWritesSupported;
|
|
16
|
+
const Cursor = require('../cursor');
|
|
17
|
+
const deprecate = require('util').deprecate;
|
|
18
|
+
const BSON = require('../connection/utils').retrieveBSON();
|
|
19
|
+
const createCompressionInfo = require('../topologies/shared').createCompressionInfo;
|
|
20
|
+
const isRetryableError = require('../error').isRetryableError;
|
|
21
|
+
const MongoParseError = require('../error').MongoParseError;
|
|
22
|
+
const isSDAMUnrecoverableError = require('../error').isSDAMUnrecoverableError;
|
|
23
|
+
const ClientSession = require('../sessions').ClientSession;
|
|
24
|
+
const createClientInfo = require('../topologies/shared').createClientInfo;
|
|
25
|
+
const MongoError = require('../error').MongoError;
|
|
26
|
+
const resolveClusterTime = require('../topologies/shared').resolveClusterTime;
|
|
27
|
+
const SrvPoller = require('./srv_polling').SrvPoller;
|
|
28
|
+
|
|
29
|
+
// Global state
|
|
30
|
+
let globalTopologyCounter = 0;
|
|
31
|
+
|
|
32
|
+
// Constants
|
|
33
|
+
const TOPOLOGY_DEFAULTS = {
|
|
34
|
+
localThresholdMS: 15,
|
|
35
|
+
serverSelectionTimeoutMS: 10000,
|
|
36
|
+
heartbeatFrequencyMS: 30000,
|
|
37
|
+
minHeartbeatFrequencyMS: 500
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// events that we relay to the `Topology`
|
|
41
|
+
const SERVER_RELAY_EVENTS = [
|
|
42
|
+
'serverHeartbeatStarted',
|
|
43
|
+
'serverHeartbeatSucceeded',
|
|
44
|
+
'serverHeartbeatFailed',
|
|
45
|
+
'commandStarted',
|
|
46
|
+
'commandSucceeded',
|
|
47
|
+
'commandFailed',
|
|
48
|
+
|
|
49
|
+
// NOTE: Legacy events
|
|
50
|
+
'monitoring'
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// all events we listen to from `Server` instances
|
|
54
|
+
const LOCAL_SERVER_EVENTS = SERVER_RELAY_EVENTS.concat([
|
|
55
|
+
'error',
|
|
56
|
+
'connect',
|
|
57
|
+
'descriptionReceived',
|
|
58
|
+
'close',
|
|
59
|
+
'ended'
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A container of server instances representing a connection to a MongoDB topology.
|
|
64
|
+
*
|
|
65
|
+
* @fires Topology#serverOpening
|
|
66
|
+
* @fires Topology#serverClosed
|
|
67
|
+
* @fires Topology#serverDescriptionChanged
|
|
68
|
+
* @fires Topology#topologyOpening
|
|
69
|
+
* @fires Topology#topologyClosed
|
|
70
|
+
* @fires Topology#topologyDescriptionChanged
|
|
71
|
+
* @fires Topology#serverHeartbeatStarted
|
|
72
|
+
* @fires Topology#serverHeartbeatSucceeded
|
|
73
|
+
* @fires Topology#serverHeartbeatFailed
|
|
74
|
+
*/
|
|
75
|
+
class Topology extends EventEmitter {
|
|
76
|
+
/**
|
|
77
|
+
* Create a topology
|
|
78
|
+
*
|
|
79
|
+
* @param {Array|String} [seedlist] a string list, or array of Server instances to connect to
|
|
80
|
+
* @param {Object} [options] Optional settings
|
|
81
|
+
* @param {Number} [options.localThresholdMS=15] The size of the latency window for selecting among multiple suitable servers
|
|
82
|
+
* @param {Number} [options.serverSelectionTimeoutMS=30000] How long to block for server selection before throwing an error
|
|
83
|
+
* @param {Number} [options.heartbeatFrequencyMS=10000] The frequency with which topology updates are scheduled
|
|
84
|
+
*/
|
|
85
|
+
constructor(seedlist, options) {
|
|
86
|
+
super();
|
|
87
|
+
if (typeof options === 'undefined' && typeof seedlist !== 'string') {
|
|
88
|
+
options = seedlist;
|
|
89
|
+
seedlist = [];
|
|
90
|
+
|
|
91
|
+
// this is for legacy single server constructor support
|
|
92
|
+
if (options.host) {
|
|
93
|
+
seedlist.push({ host: options.host, port: options.port });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
seedlist = seedlist || [];
|
|
98
|
+
if (typeof seedlist === 'string') {
|
|
99
|
+
seedlist = parseStringSeedlist(seedlist);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
options = Object.assign({}, TOPOLOGY_DEFAULTS, options);
|
|
103
|
+
|
|
104
|
+
const topologyType = topologyTypeFromSeedlist(seedlist, options);
|
|
105
|
+
const topologyId = globalTopologyCounter++;
|
|
106
|
+
const serverDescriptions = seedlist.reduce((result, seed) => {
|
|
107
|
+
if (seed.domain_socket) seed.host = seed.domain_socket;
|
|
108
|
+
const address = seed.port ? `${seed.host}:${seed.port}` : `${seed.host}:27017`;
|
|
109
|
+
result.set(address, new ServerDescription(address));
|
|
110
|
+
return result;
|
|
111
|
+
}, new Map());
|
|
112
|
+
|
|
113
|
+
this.s = {
|
|
114
|
+
// the id of this topology
|
|
115
|
+
id: topologyId,
|
|
116
|
+
// passed in options
|
|
117
|
+
options,
|
|
118
|
+
// initial seedlist of servers to connect to
|
|
119
|
+
seedlist: seedlist,
|
|
120
|
+
// the topology description
|
|
121
|
+
description: new TopologyDescription(
|
|
122
|
+
topologyType,
|
|
123
|
+
serverDescriptions,
|
|
124
|
+
options.replicaSet,
|
|
125
|
+
null,
|
|
126
|
+
null,
|
|
127
|
+
null,
|
|
128
|
+
options
|
|
129
|
+
),
|
|
130
|
+
serverSelectionTimeoutMS: options.serverSelectionTimeoutMS,
|
|
131
|
+
heartbeatFrequencyMS: options.heartbeatFrequencyMS,
|
|
132
|
+
minHeartbeatIntervalMS: options.minHeartbeatIntervalMS,
|
|
133
|
+
// allow users to override the cursor factory
|
|
134
|
+
Cursor: options.cursorFactory || Cursor,
|
|
135
|
+
// the bson parser
|
|
136
|
+
bson: options.bson || new BSON(),
|
|
137
|
+
// a map of server instances to normalized addresses
|
|
138
|
+
servers: new Map(),
|
|
139
|
+
// Server Session Pool
|
|
140
|
+
sessionPool: null,
|
|
141
|
+
// Active client sessions
|
|
142
|
+
sessions: [],
|
|
143
|
+
// Promise library
|
|
144
|
+
promiseLibrary: options.promiseLibrary || Promise,
|
|
145
|
+
credentials: options.credentials,
|
|
146
|
+
clusterTime: null,
|
|
147
|
+
|
|
148
|
+
// timer management
|
|
149
|
+
monitorTimers: [],
|
|
150
|
+
iterationTimers: []
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// amend options for server instance creation
|
|
154
|
+
this.s.options.compression = { compressors: createCompressionInfo(options) };
|
|
155
|
+
|
|
156
|
+
// add client info
|
|
157
|
+
this.s.clientInfo = createClientInfo(options);
|
|
158
|
+
|
|
159
|
+
if (options.srvHost) {
|
|
160
|
+
this.s.srvPoller =
|
|
161
|
+
options.srvPoller ||
|
|
162
|
+
new SrvPoller({
|
|
163
|
+
heartbeatFrequencyMS: this.s.heartbeatFrequencyMS,
|
|
164
|
+
srvHost: options.srvHost, // TODO: GET THIS
|
|
165
|
+
logger: options.logger,
|
|
166
|
+
loggerLevel: options.loggerLevel
|
|
167
|
+
});
|
|
168
|
+
this.s.detectTopologyDescriptionChange = ev => {
|
|
169
|
+
const previousType = ev.previousDescription.type;
|
|
170
|
+
const newType = ev.newDescription.type;
|
|
171
|
+
|
|
172
|
+
if (previousType !== TopologyType.Sharded && newType === TopologyType.Sharded) {
|
|
173
|
+
this.s.handleSrvPolling = srvPollingHandler(this);
|
|
174
|
+
this.s.srvPoller.on('srvRecordDiscovery', this.s.handleSrvPolling);
|
|
175
|
+
this.s.srvPoller.start();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
this.on('topologyDescriptionChanged', this.s.detectTopologyDescriptionChange);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @return A `TopologyDescription` for this topology
|
|
185
|
+
*/
|
|
186
|
+
get description() {
|
|
187
|
+
return this.s.description;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
get parserType() {
|
|
191
|
+
return BSON.native ? 'c++' : 'js';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* All raw connections
|
|
196
|
+
* @method
|
|
197
|
+
* @return {Connection[]}
|
|
198
|
+
*/
|
|
199
|
+
connections() {
|
|
200
|
+
return Array.from(this.s.servers.values()).reduce((result, server) => {
|
|
201
|
+
return result.concat(server.s.pool.allConnections());
|
|
202
|
+
}, []);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Initiate server connect
|
|
207
|
+
*
|
|
208
|
+
* @param {Object} [options] Optional settings
|
|
209
|
+
* @param {Array} [options.auth=null] Array of auth options to apply on connect
|
|
210
|
+
* @param {function} [callback] An optional callback called once on the first connected server
|
|
211
|
+
*/
|
|
212
|
+
connect(options, callback) {
|
|
213
|
+
if (typeof options === 'function') (callback = options), (options = {});
|
|
214
|
+
options = options || {};
|
|
215
|
+
|
|
216
|
+
// emit SDAM monitoring events
|
|
217
|
+
this.emit('topologyOpening', new monitoring.TopologyOpeningEvent(this.s.id));
|
|
218
|
+
|
|
219
|
+
// emit an event for the topology change
|
|
220
|
+
this.emit(
|
|
221
|
+
'topologyDescriptionChanged',
|
|
222
|
+
new monitoring.TopologyDescriptionChangedEvent(
|
|
223
|
+
this.s.id,
|
|
224
|
+
new TopologyDescription(TopologyType.Unknown), // initial is always Unknown
|
|
225
|
+
this.s.description
|
|
226
|
+
)
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
connectServers(this, Array.from(this.s.description.servers.values()));
|
|
230
|
+
this.s.connected = true;
|
|
231
|
+
|
|
232
|
+
// otherwise, wait for a server to properly connect based on user provided read preference,
|
|
233
|
+
// or primary.
|
|
234
|
+
|
|
235
|
+
translateReadPreference(options);
|
|
236
|
+
const readPreference = options.readPreference || ReadPreference.primary;
|
|
237
|
+
|
|
238
|
+
this.selectServer(readPreferenceServerSelector(readPreference), options, (err, server) => {
|
|
239
|
+
if (err) {
|
|
240
|
+
if (typeof callback === 'function') {
|
|
241
|
+
callback(err, null);
|
|
242
|
+
} else {
|
|
243
|
+
this.emit('error', err);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const errorHandler = err => {
|
|
250
|
+
server.removeListener('connect', connectHandler);
|
|
251
|
+
if (typeof callback === 'function') callback(err, null);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const connectHandler = (_, err) => {
|
|
255
|
+
server.removeListener('error', errorHandler);
|
|
256
|
+
this.emit('open', err, this);
|
|
257
|
+
this.emit('connect', this);
|
|
258
|
+
|
|
259
|
+
if (typeof callback === 'function') callback(err, this);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const STATE_CONNECTING = 1;
|
|
263
|
+
if (server.s.state === STATE_CONNECTING) {
|
|
264
|
+
server.once('error', errorHandler);
|
|
265
|
+
server.once('connect', connectHandler);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
connectHandler();
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Close this topology
|
|
275
|
+
*/
|
|
276
|
+
close(options, callback) {
|
|
277
|
+
if (typeof options === 'function') (callback = options), (options = {});
|
|
278
|
+
options = options || {};
|
|
279
|
+
|
|
280
|
+
// clear all existing monitor timers
|
|
281
|
+
this.s.monitorTimers.map(timer => clearTimeout(timer));
|
|
282
|
+
this.s.monitorTimers = [];
|
|
283
|
+
|
|
284
|
+
this.s.iterationTimers.map(timer => clearTimeout(timer));
|
|
285
|
+
this.s.iterationTimers = [];
|
|
286
|
+
|
|
287
|
+
if (this.s.sessionPool) {
|
|
288
|
+
this.s.sessions.forEach(session => session.endSession());
|
|
289
|
+
this.s.sessionPool.endAllPooledSessions();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (this.s.srvPoller) {
|
|
293
|
+
this.s.srvPoller.stop();
|
|
294
|
+
if (this.s.handleSrvPolling) {
|
|
295
|
+
this.s.srvPoller.removeListener('srvRecordDiscovery', this.s.handleSrvPolling);
|
|
296
|
+
delete this.s.handleSrvPolling;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (this.s.detectTopologyDescriptionChange) {
|
|
301
|
+
this.removeListener('topologyDescriptionChanged', this.s.detectTopologyDescriptionChange);
|
|
302
|
+
delete this.s.detectTopologyDescriptionChange;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const servers = this.s.servers;
|
|
306
|
+
if (servers.size === 0) {
|
|
307
|
+
this.s.connected = false;
|
|
308
|
+
if (typeof callback === 'function') {
|
|
309
|
+
callback(null, null);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// destroy all child servers
|
|
316
|
+
let destroyed = 0;
|
|
317
|
+
servers.forEach(server =>
|
|
318
|
+
destroyServer(server, this, () => {
|
|
319
|
+
destroyed++;
|
|
320
|
+
if (destroyed === servers.size) {
|
|
321
|
+
// emit an event for close
|
|
322
|
+
this.emit('topologyClosed', new monitoring.TopologyClosedEvent(this.s.id));
|
|
323
|
+
|
|
324
|
+
this.s.connected = false;
|
|
325
|
+
if (typeof callback === 'function') {
|
|
326
|
+
callback(null, null);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Selects a server according to the selection predicate provided
|
|
335
|
+
*
|
|
336
|
+
* @param {function} [selector] An optional selector to select servers by, defaults to a random selection within a latency window
|
|
337
|
+
* @param {object} [options] Optional settings related to server selection
|
|
338
|
+
* @param {number} [options.serverSelectionTimeoutMS] How long to block for server selection before throwing an error
|
|
339
|
+
* @param {function} callback The callback used to indicate success or failure
|
|
340
|
+
* @return {Server} An instance of a `Server` meeting the criteria of the predicate provided
|
|
341
|
+
*/
|
|
342
|
+
selectServer(selector, options, callback) {
|
|
343
|
+
if (typeof options === 'function') {
|
|
344
|
+
callback = options;
|
|
345
|
+
if (typeof selector !== 'function') {
|
|
346
|
+
options = selector;
|
|
347
|
+
|
|
348
|
+
translateReadPreference(options);
|
|
349
|
+
const readPreference = options.readPreference || ReadPreference.primary;
|
|
350
|
+
selector = readPreferenceServerSelector(readPreference);
|
|
351
|
+
} else {
|
|
352
|
+
options = {};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
options = Object.assign(
|
|
357
|
+
{},
|
|
358
|
+
{ serverSelectionTimeoutMS: this.s.serverSelectionTimeoutMS },
|
|
359
|
+
options
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const isSharded = this.description.type === TopologyType.Sharded;
|
|
363
|
+
const session = options.session;
|
|
364
|
+
const transaction = session && session.transaction;
|
|
365
|
+
|
|
366
|
+
if (isSharded && transaction && transaction.server) {
|
|
367
|
+
callback(null, transaction.server);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// clear out any existing iteration timers
|
|
372
|
+
this.s.iterationTimers.map(timer => clearTimeout(timer));
|
|
373
|
+
this.s.iterationTimers = [];
|
|
374
|
+
|
|
375
|
+
selectServers(
|
|
376
|
+
this,
|
|
377
|
+
selector,
|
|
378
|
+
options.serverSelectionTimeoutMS,
|
|
379
|
+
process.hrtime(),
|
|
380
|
+
(err, servers) => {
|
|
381
|
+
if (err) return callback(err, null);
|
|
382
|
+
|
|
383
|
+
const selectedServer = randomSelection(servers);
|
|
384
|
+
if (isSharded && transaction && transaction.isActive) {
|
|
385
|
+
transaction.pinServer(selectedServer);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
callback(null, selectedServer);
|
|
389
|
+
}
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Sessions related methods
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* @return Whether the topology should initiate selection to determine session support
|
|
397
|
+
*/
|
|
398
|
+
shouldCheckForSessionSupport() {
|
|
399
|
+
return (
|
|
400
|
+
(this.description.type === TopologyType.Single && !this.description.hasKnownServers) ||
|
|
401
|
+
!this.description.hasDataBearingServers
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* @return Whether sessions are supported on the current topology
|
|
407
|
+
*/
|
|
408
|
+
hasSessionSupport() {
|
|
409
|
+
return this.description.logicalSessionTimeoutMinutes != null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Start a logical session
|
|
414
|
+
*/
|
|
415
|
+
startSession(options, clientOptions) {
|
|
416
|
+
const session = new ClientSession(this, this.s.sessionPool, options, clientOptions);
|
|
417
|
+
session.once('ended', () => {
|
|
418
|
+
this.s.sessions = this.s.sessions.filter(s => !s.equals(session));
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
this.s.sessions.push(session);
|
|
422
|
+
return session;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Send endSessions command(s) with the given session ids
|
|
427
|
+
*
|
|
428
|
+
* @param {Array} sessions The sessions to end
|
|
429
|
+
* @param {function} [callback]
|
|
430
|
+
*/
|
|
431
|
+
endSessions(sessions, callback) {
|
|
432
|
+
if (!Array.isArray(sessions)) {
|
|
433
|
+
sessions = [sessions];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
this.command(
|
|
437
|
+
'admin.$cmd',
|
|
438
|
+
{ endSessions: sessions },
|
|
439
|
+
{ readPreference: ReadPreference.primaryPreferred, noResponse: true },
|
|
440
|
+
() => {
|
|
441
|
+
// intentionally ignored, per spec
|
|
442
|
+
if (typeof callback === 'function') callback();
|
|
443
|
+
}
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Update the internal TopologyDescription with a ServerDescription
|
|
449
|
+
*
|
|
450
|
+
* @param {object} serverDescription The server to update in the internal list of server descriptions
|
|
451
|
+
*/
|
|
452
|
+
serverUpdateHandler(serverDescription) {
|
|
453
|
+
if (!this.s.description.hasServer(serverDescription.address)) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// these will be used for monitoring events later
|
|
458
|
+
const previousTopologyDescription = this.s.description;
|
|
459
|
+
const previousServerDescription = this.s.description.servers.get(serverDescription.address);
|
|
460
|
+
|
|
461
|
+
// first update the TopologyDescription
|
|
462
|
+
this.s.description = this.s.description.update(serverDescription);
|
|
463
|
+
if (this.s.description.compatibilityError) {
|
|
464
|
+
this.emit('error', new MongoError(this.s.description.compatibilityError));
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// emit monitoring events for this change
|
|
469
|
+
this.emit(
|
|
470
|
+
'serverDescriptionChanged',
|
|
471
|
+
new monitoring.ServerDescriptionChangedEvent(
|
|
472
|
+
this.s.id,
|
|
473
|
+
serverDescription.address,
|
|
474
|
+
previousServerDescription,
|
|
475
|
+
this.s.description.servers.get(serverDescription.address)
|
|
476
|
+
)
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
// update server list from updated descriptions
|
|
480
|
+
updateServers(this, serverDescription);
|
|
481
|
+
|
|
482
|
+
// Driver Sessions Spec: "Whenever a driver receives a cluster time from
|
|
483
|
+
// a server it MUST compare it to the current highest seen cluster time
|
|
484
|
+
// for the deployment. If the new cluster time is higher than the
|
|
485
|
+
// highest seen cluster time it MUST become the new highest seen cluster
|
|
486
|
+
// time. Two cluster times are compared using only the BsonTimestamp
|
|
487
|
+
// value of the clusterTime embedded field."
|
|
488
|
+
const clusterTime = serverDescription.$clusterTime;
|
|
489
|
+
if (clusterTime) {
|
|
490
|
+
resolveClusterTime(this, clusterTime);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
this.emit(
|
|
494
|
+
'topologyDescriptionChanged',
|
|
495
|
+
new monitoring.TopologyDescriptionChangedEvent(
|
|
496
|
+
this.s.id,
|
|
497
|
+
previousTopologyDescription,
|
|
498
|
+
this.s.description
|
|
499
|
+
)
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
auth(credentials, callback) {
|
|
504
|
+
if (typeof credentials === 'function') (callback = credentials), (credentials = null);
|
|
505
|
+
if (typeof callback === 'function') callback(null, true);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
logout(callback) {
|
|
509
|
+
if (typeof callback === 'function') callback(null, true);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Basic operation support. Eventually this should be moved into command construction
|
|
513
|
+
// during the command refactor.
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Insert one or more documents
|
|
517
|
+
*
|
|
518
|
+
* @param {String} ns The full qualified namespace for this operation
|
|
519
|
+
* @param {Array} ops An array of documents to insert
|
|
520
|
+
* @param {Boolean} [options.ordered=true] Execute in order or out of order
|
|
521
|
+
* @param {Object} [options.writeConcern] Write concern for the operation
|
|
522
|
+
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized
|
|
523
|
+
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields
|
|
524
|
+
* @param {ClientSession} [options.session] Session to use for the operation
|
|
525
|
+
* @param {boolean} [options.retryWrites] Enable retryable writes for this operation
|
|
526
|
+
* @param {opResultCallback} callback A callback function
|
|
527
|
+
*/
|
|
528
|
+
insert(ns, ops, options, callback) {
|
|
529
|
+
executeWriteOperation({ topology: this, op: 'insert', ns, ops }, options, callback);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Perform one or more update operations
|
|
534
|
+
*
|
|
535
|
+
* @param {string} ns The fully qualified namespace for this operation
|
|
536
|
+
* @param {array} ops An array of updates
|
|
537
|
+
* @param {boolean} [options.ordered=true] Execute in order or out of order
|
|
538
|
+
* @param {object} [options.writeConcern] Write concern for the operation
|
|
539
|
+
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized
|
|
540
|
+
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields
|
|
541
|
+
* @param {ClientSession} [options.session] Session to use for the operation
|
|
542
|
+
* @param {boolean} [options.retryWrites] Enable retryable writes for this operation
|
|
543
|
+
* @param {opResultCallback} callback A callback function
|
|
544
|
+
*/
|
|
545
|
+
update(ns, ops, options, callback) {
|
|
546
|
+
executeWriteOperation({ topology: this, op: 'update', ns, ops }, options, callback);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Perform one or more remove operations
|
|
551
|
+
*
|
|
552
|
+
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
553
|
+
* @param {array} ops An array of removes
|
|
554
|
+
* @param {boolean} [options.ordered=true] Execute in order or out of order
|
|
555
|
+
* @param {object} [options.writeConcern={}] Write concern for the operation
|
|
556
|
+
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
557
|
+
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
558
|
+
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
559
|
+
* @param {boolean} [options.retryWrites] Enable retryable writes for this operation
|
|
560
|
+
* @param {opResultCallback} callback A callback function
|
|
561
|
+
*/
|
|
562
|
+
remove(ns, ops, options, callback) {
|
|
563
|
+
executeWriteOperation({ topology: this, op: 'remove', ns, ops }, options, callback);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Execute a command
|
|
568
|
+
*
|
|
569
|
+
* @method
|
|
570
|
+
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
571
|
+
* @param {object} cmd The command hash
|
|
572
|
+
* @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
|
|
573
|
+
* @param {Connection} [options.connection] Specify connection object to execute command against
|
|
574
|
+
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
575
|
+
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
576
|
+
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
577
|
+
* @param {opResultCallback} callback A callback function
|
|
578
|
+
*/
|
|
579
|
+
command(ns, cmd, options, callback) {
|
|
580
|
+
if (typeof options === 'function') {
|
|
581
|
+
(callback = options), (options = {}), (options = options || {});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
translateReadPreference(options);
|
|
585
|
+
const readPreference = options.readPreference || ReadPreference.primary;
|
|
586
|
+
|
|
587
|
+
this.selectServer(readPreferenceServerSelector(readPreference), options, (err, server) => {
|
|
588
|
+
if (err) {
|
|
589
|
+
callback(err, null);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const willRetryWrite =
|
|
594
|
+
!options.retrying &&
|
|
595
|
+
!!options.retryWrites &&
|
|
596
|
+
options.session &&
|
|
597
|
+
isRetryableWritesSupported(this) &&
|
|
598
|
+
!options.session.inTransaction() &&
|
|
599
|
+
isWriteCommand(cmd);
|
|
600
|
+
|
|
601
|
+
const cb = (err, result) => {
|
|
602
|
+
if (!err) return callback(null, result);
|
|
603
|
+
if (!isRetryableError(err)) {
|
|
604
|
+
return callback(err);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (willRetryWrite) {
|
|
608
|
+
const newOptions = Object.assign({}, options, { retrying: true });
|
|
609
|
+
return this.command(ns, cmd, newOptions, callback);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return callback(err);
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// increment and assign txnNumber
|
|
616
|
+
if (willRetryWrite) {
|
|
617
|
+
options.session.incrementTransactionNumber();
|
|
618
|
+
options.willRetryWrite = willRetryWrite;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
server.command(ns, cmd, options, cb);
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Create a new cursor
|
|
627
|
+
*
|
|
628
|
+
* @method
|
|
629
|
+
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
630
|
+
* @param {object|Long} cmd Can be either a command returning a cursor or a cursorId
|
|
631
|
+
* @param {object} [options] Options for the cursor
|
|
632
|
+
* @param {object} [options.batchSize=0] Batchsize for the operation
|
|
633
|
+
* @param {array} [options.documents=[]] Initial documents list for cursor
|
|
634
|
+
* @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
|
|
635
|
+
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
636
|
+
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
637
|
+
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
638
|
+
* @param {object} [options.topology] The internal topology of the created cursor
|
|
639
|
+
* @returns {Cursor}
|
|
640
|
+
*/
|
|
641
|
+
cursor(ns, cmd, options) {
|
|
642
|
+
options = options || {};
|
|
643
|
+
const topology = options.topology || this;
|
|
644
|
+
const CursorClass = options.cursorFactory || this.s.Cursor;
|
|
645
|
+
translateReadPreference(options);
|
|
646
|
+
|
|
647
|
+
return new CursorClass(topology, ns, cmd, options);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
get clientInfo() {
|
|
651
|
+
return this.s.clientInfo;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Legacy methods for compat with old topology types
|
|
655
|
+
isConnected() {
|
|
656
|
+
// console.log('not implemented: `isConnected`');
|
|
657
|
+
return true;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
isDestroyed() {
|
|
661
|
+
// console.log('not implemented: `isDestroyed`');
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
unref() {
|
|
666
|
+
console.log('not implemented: `unref`');
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// NOTE: There are many places in code where we explicitly check the last isMaster
|
|
670
|
+
// to do feature support detection. This should be done any other way, but for
|
|
671
|
+
// now we will just return the first isMaster seen, which should suffice.
|
|
672
|
+
lastIsMaster() {
|
|
673
|
+
const serverDescriptions = Array.from(this.description.servers.values());
|
|
674
|
+
if (serverDescriptions.length === 0) return {};
|
|
675
|
+
|
|
676
|
+
const sd = serverDescriptions.filter(sd => sd.type !== ServerType.Unknown)[0];
|
|
677
|
+
const result = sd || { maxWireVersion: this.description.commonWireVersion };
|
|
678
|
+
return result;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
get logicalSessionTimeoutMinutes() {
|
|
682
|
+
return this.description.logicalSessionTimeoutMinutes;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
get bson() {
|
|
686
|
+
return this.s.bson;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
Object.defineProperty(Topology.prototype, 'clusterTime', {
|
|
691
|
+
enumerable: true,
|
|
692
|
+
get: function() {
|
|
693
|
+
return this.s.clusterTime;
|
|
694
|
+
},
|
|
695
|
+
set: function(clusterTime) {
|
|
696
|
+
this.s.clusterTime = clusterTime;
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// legacy aliases
|
|
701
|
+
Topology.prototype.destroy = deprecate(
|
|
702
|
+
Topology.prototype.close,
|
|
703
|
+
'destroy() is deprecated, please use close() instead'
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
const RETRYABLE_WRITE_OPERATIONS = ['findAndModify', 'insert', 'update', 'delete'];
|
|
707
|
+
function isWriteCommand(command) {
|
|
708
|
+
return RETRYABLE_WRITE_OPERATIONS.some(op => command[op]);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Destroys a server, and removes all event listeners from the instance
|
|
713
|
+
*
|
|
714
|
+
* @param {Server} server
|
|
715
|
+
*/
|
|
716
|
+
function destroyServer(server, topology, callback) {
|
|
717
|
+
LOCAL_SERVER_EVENTS.forEach(event => server.removeAllListeners(event));
|
|
718
|
+
|
|
719
|
+
server.destroy(() => {
|
|
720
|
+
topology.emit(
|
|
721
|
+
'serverClosed',
|
|
722
|
+
new monitoring.ServerClosedEvent(topology.s.id, server.description.address)
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
if (typeof callback === 'function') callback(null, null);
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Parses a basic seedlist in string form
|
|
731
|
+
*
|
|
732
|
+
* @param {string} seedlist The seedlist to parse
|
|
733
|
+
*/
|
|
734
|
+
function parseStringSeedlist(seedlist) {
|
|
735
|
+
return seedlist.split(',').map(seed => ({
|
|
736
|
+
host: seed.split(':')[0],
|
|
737
|
+
port: seed.split(':')[1] || 27017
|
|
738
|
+
}));
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function topologyTypeFromSeedlist(seedlist, options) {
|
|
742
|
+
const replicaSet = options.replicaSet || options.setName || options.rs_name;
|
|
743
|
+
if (seedlist.length === 1 && !replicaSet) return TopologyType.Single;
|
|
744
|
+
if (replicaSet) return TopologyType.ReplicaSetNoPrimary;
|
|
745
|
+
return TopologyType.Unknown;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function randomSelection(array) {
|
|
749
|
+
return array[Math.floor(Math.random() * array.length)];
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Selects servers using the provided selector
|
|
754
|
+
*
|
|
755
|
+
* @private
|
|
756
|
+
* @param {Topology} topology The topology to select servers from
|
|
757
|
+
* @param {function} selector The actual predicate used for selecting servers
|
|
758
|
+
* @param {Number} timeout The max time we are willing wait for selection
|
|
759
|
+
* @param {Number} start A high precision timestamp for the start of the selection process
|
|
760
|
+
* @param {function} callback The callback used to convey errors or the resultant servers
|
|
761
|
+
*/
|
|
762
|
+
function selectServers(topology, selector, timeout, start, callback) {
|
|
763
|
+
const duration = calculateDurationInMs(start);
|
|
764
|
+
if (duration >= timeout) {
|
|
765
|
+
return callback(new MongoTimeoutError(`Server selection timed out after ${timeout} ms`));
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// ensure we are connected
|
|
769
|
+
if (!topology.s.connected) {
|
|
770
|
+
topology.connect();
|
|
771
|
+
|
|
772
|
+
// we want to make sure we're still within the requested timeout window
|
|
773
|
+
const failToConnectTimer = setTimeout(() => {
|
|
774
|
+
topology.removeListener('connect', connectHandler);
|
|
775
|
+
callback(new MongoTimeoutError('Server selection timed out waiting to connect'));
|
|
776
|
+
}, timeout - duration);
|
|
777
|
+
|
|
778
|
+
const connectHandler = () => {
|
|
779
|
+
clearTimeout(failToConnectTimer);
|
|
780
|
+
selectServers(topology, selector, timeout, process.hrtime(), callback);
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
topology.once('connect', connectHandler);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// otherwise, attempt server selection
|
|
788
|
+
const serverDescriptions = Array.from(topology.description.servers.values());
|
|
789
|
+
let descriptions;
|
|
790
|
+
|
|
791
|
+
// support server selection by options with readPreference
|
|
792
|
+
if (typeof selector === 'object') {
|
|
793
|
+
const readPreference = selector.readPreference
|
|
794
|
+
? selector.readPreference
|
|
795
|
+
: ReadPreference.primary;
|
|
796
|
+
|
|
797
|
+
selector = readPreferenceServerSelector(readPreference);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
try {
|
|
801
|
+
descriptions = selector
|
|
802
|
+
? selector(topology.description, serverDescriptions)
|
|
803
|
+
: serverDescriptions;
|
|
804
|
+
} catch (e) {
|
|
805
|
+
return callback(e, null);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (descriptions.length) {
|
|
809
|
+
const servers = descriptions.map(description => topology.s.servers.get(description.address));
|
|
810
|
+
return callback(null, servers);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const retrySelection = () => {
|
|
814
|
+
// clear all existing monitor timers
|
|
815
|
+
topology.s.monitorTimers.map(timer => clearTimeout(timer));
|
|
816
|
+
topology.s.monitorTimers = [];
|
|
817
|
+
|
|
818
|
+
// ensure all server monitors attempt monitoring soon
|
|
819
|
+
topology.s.servers.forEach(server => {
|
|
820
|
+
const timer = setTimeout(
|
|
821
|
+
() => server.monitor({ heartbeatFrequencyMS: topology.description.heartbeatFrequencyMS }),
|
|
822
|
+
TOPOLOGY_DEFAULTS.minHeartbeatFrequencyMS
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
topology.s.monitorTimers.push(timer);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
const descriptionChangedHandler = () => {
|
|
829
|
+
// successful iteration, clear the check timer
|
|
830
|
+
clearTimeout(iterationTimer);
|
|
831
|
+
topology.s.iterationTimers.splice(timerIndex, 1);
|
|
832
|
+
|
|
833
|
+
if (topology.description.error) {
|
|
834
|
+
callback(topology.description.error, null);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// topology description has changed due to monitoring, reattempt server selection
|
|
839
|
+
selectServers(topology, selector, timeout, start, callback);
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
const iterationTimer = setTimeout(() => {
|
|
843
|
+
topology.removeListener('topologyDescriptionChanged', descriptionChangedHandler);
|
|
844
|
+
callback(new MongoTimeoutError(`Server selection timed out after ${timeout} ms`));
|
|
845
|
+
}, timeout - duration);
|
|
846
|
+
|
|
847
|
+
// track this timer in case we need to clean it up outside this loop
|
|
848
|
+
const timerIndex = topology.s.iterationTimers.push(iterationTimer);
|
|
849
|
+
|
|
850
|
+
topology.once('topologyDescriptionChanged', descriptionChangedHandler);
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
retrySelection();
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function createAndConnectServer(topology, serverDescription) {
|
|
857
|
+
topology.emit(
|
|
858
|
+
'serverOpening',
|
|
859
|
+
new monitoring.ServerOpeningEvent(topology.s.id, serverDescription.address)
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
const server = new Server(serverDescription, topology.s.options, topology);
|
|
863
|
+
relayEvents(server, topology, SERVER_RELAY_EVENTS);
|
|
864
|
+
|
|
865
|
+
server.once('connect', serverConnectEventHandler(server, topology));
|
|
866
|
+
server.on('descriptionReceived', topology.serverUpdateHandler.bind(topology));
|
|
867
|
+
server.on('error', serverErrorEventHandler(server, topology));
|
|
868
|
+
server.on('close', () => topology.emit('close', server));
|
|
869
|
+
server.connect();
|
|
870
|
+
return server;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Create `Server` instances for all initially known servers, connect them, and assign
|
|
875
|
+
* them to the passed in `Topology`.
|
|
876
|
+
*
|
|
877
|
+
* @param {Topology} topology The topology responsible for the servers
|
|
878
|
+
* @param {ServerDescription[]} serverDescriptions A list of server descriptions to connect
|
|
879
|
+
*/
|
|
880
|
+
function connectServers(topology, serverDescriptions) {
|
|
881
|
+
topology.s.servers = serverDescriptions.reduce((servers, serverDescription) => {
|
|
882
|
+
const server = createAndConnectServer(topology, serverDescription);
|
|
883
|
+
servers.set(serverDescription.address, server);
|
|
884
|
+
return servers;
|
|
885
|
+
}, new Map());
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function updateServers(topology, incomingServerDescription) {
|
|
889
|
+
// update the internal server's description
|
|
890
|
+
if (incomingServerDescription && topology.s.servers.has(incomingServerDescription.address)) {
|
|
891
|
+
const server = topology.s.servers.get(incomingServerDescription.address);
|
|
892
|
+
server.s.description = incomingServerDescription;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// add new servers for all descriptions we currently don't know about locally
|
|
896
|
+
for (const serverDescription of topology.description.servers.values()) {
|
|
897
|
+
if (!topology.s.servers.has(serverDescription.address)) {
|
|
898
|
+
const server = createAndConnectServer(topology, serverDescription);
|
|
899
|
+
topology.s.servers.set(serverDescription.address, server);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// for all servers no longer known, remove their descriptions and destroy their instances
|
|
904
|
+
for (const entry of topology.s.servers) {
|
|
905
|
+
const serverAddress = entry[0];
|
|
906
|
+
if (topology.description.hasServer(serverAddress)) {
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
const server = topology.s.servers.get(serverAddress);
|
|
911
|
+
topology.s.servers.delete(serverAddress);
|
|
912
|
+
|
|
913
|
+
// prepare server for garbage collection
|
|
914
|
+
destroyServer(server, topology);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function serverConnectEventHandler(server, topology) {
|
|
919
|
+
return function(/* isMaster, err */) {
|
|
920
|
+
server.monitor({
|
|
921
|
+
initial: true,
|
|
922
|
+
heartbeatFrequencyMS: topology.description.heartbeatFrequencyMS
|
|
923
|
+
});
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function serverErrorEventHandler(server, topology) {
|
|
928
|
+
return function(err) {
|
|
929
|
+
topology.emit(
|
|
930
|
+
'serverClosed',
|
|
931
|
+
new monitoring.ServerClosedEvent(topology.s.id, server.description.address)
|
|
932
|
+
);
|
|
933
|
+
|
|
934
|
+
if (err instanceof MongoParseError || isSDAMUnrecoverableError(err)) {
|
|
935
|
+
resetServerState(server, err, { clearPool: true });
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
resetServerState(server, err);
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function executeWriteOperation(args, options, callback) {
|
|
944
|
+
if (typeof options === 'function') (callback = options), (options = {});
|
|
945
|
+
options = options || {};
|
|
946
|
+
|
|
947
|
+
// TODO: once we drop Node 4, use destructuring either here or in arguments.
|
|
948
|
+
const topology = args.topology;
|
|
949
|
+
const op = args.op;
|
|
950
|
+
const ns = args.ns;
|
|
951
|
+
const ops = args.ops;
|
|
952
|
+
|
|
953
|
+
const willRetryWrite =
|
|
954
|
+
!args.retrying &&
|
|
955
|
+
!!options.retryWrites &&
|
|
956
|
+
options.session &&
|
|
957
|
+
isRetryableWritesSupported(topology) &&
|
|
958
|
+
!options.session.inTransaction();
|
|
959
|
+
|
|
960
|
+
topology.selectServer(writableServerSelector(), options, (err, server) => {
|
|
961
|
+
if (err) {
|
|
962
|
+
callback(err, null);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const handler = (err, result) => {
|
|
967
|
+
if (!err) return callback(null, result);
|
|
968
|
+
if (!isRetryableError(err)) {
|
|
969
|
+
return callback(err);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
if (willRetryWrite) {
|
|
973
|
+
const newArgs = Object.assign({}, args, { retrying: true });
|
|
974
|
+
return executeWriteOperation(newArgs, options, callback);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
return callback(err);
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
if (callback.operationId) {
|
|
981
|
+
handler.operationId = callback.operationId;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// increment and assign txnNumber
|
|
985
|
+
if (willRetryWrite) {
|
|
986
|
+
options.session.incrementTransactionNumber();
|
|
987
|
+
options.willRetryWrite = willRetryWrite;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// execute the write operation
|
|
991
|
+
server[op](ns, ops, options, handler);
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Resets the internal state of this server to `Unknown` by simulating an empty ismaster
|
|
997
|
+
*
|
|
998
|
+
* @private
|
|
999
|
+
* @param {Server} server
|
|
1000
|
+
* @param {MongoError} error The error that caused the state reset
|
|
1001
|
+
* @param {object} [options] Optional settings
|
|
1002
|
+
* @param {boolean} [options.clearPool=false] Pool should be cleared out on state reset
|
|
1003
|
+
*/
|
|
1004
|
+
function resetServerState(server, error, options) {
|
|
1005
|
+
options = Object.assign({}, { clearPool: false }, options);
|
|
1006
|
+
|
|
1007
|
+
function resetState() {
|
|
1008
|
+
server.emit(
|
|
1009
|
+
'descriptionReceived',
|
|
1010
|
+
new ServerDescription(server.description.address, null, { error })
|
|
1011
|
+
);
|
|
1012
|
+
server.monitor();
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
if (options.clearPool && server.s.pool) {
|
|
1016
|
+
server.s.pool.reset(() => resetState());
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
resetState();
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function translateReadPreference(options) {
|
|
1024
|
+
if (options.readPreference == null) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
let r = options.readPreference;
|
|
1029
|
+
if (typeof r === 'string') {
|
|
1030
|
+
options.readPreference = new ReadPreference(r);
|
|
1031
|
+
} else if (r && !(r instanceof ReadPreference) && typeof r === 'object') {
|
|
1032
|
+
const mode = r.mode || r.preference;
|
|
1033
|
+
if (mode && typeof mode === 'string') {
|
|
1034
|
+
options.readPreference = new ReadPreference(mode, r.tags, {
|
|
1035
|
+
maxStalenessSeconds: r.maxStalenessSeconds
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
} else if (!(r instanceof ReadPreference)) {
|
|
1039
|
+
throw new TypeError('Invalid read preference: ' + r);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
return options;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function srvPollingHandler(topology) {
|
|
1046
|
+
return function handleSrvPolling(ev) {
|
|
1047
|
+
const previousTopologyDescription = topology.s.description;
|
|
1048
|
+
topology.s.description = topology.s.description.updateFromSrvPollingEvent(ev);
|
|
1049
|
+
if (topology.s.description === previousTopologyDescription) {
|
|
1050
|
+
// Nothing changed, so return
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
updateServers(topology);
|
|
1055
|
+
|
|
1056
|
+
topology.emit(
|
|
1057
|
+
'topologyDescriptionChanged',
|
|
1058
|
+
new monitoring.TopologyDescriptionChangedEvent(
|
|
1059
|
+
topology.s.id,
|
|
1060
|
+
previousTopologyDescription,
|
|
1061
|
+
topology.s.description
|
|
1062
|
+
)
|
|
1063
|
+
);
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* A server opening SDAM monitoring event
|
|
1069
|
+
*
|
|
1070
|
+
* @event Topology#serverOpening
|
|
1071
|
+
* @type {ServerOpeningEvent}
|
|
1072
|
+
*/
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* A server closed SDAM monitoring event
|
|
1076
|
+
*
|
|
1077
|
+
* @event Topology#serverClosed
|
|
1078
|
+
* @type {ServerClosedEvent}
|
|
1079
|
+
*/
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* A server description SDAM change monitoring event
|
|
1083
|
+
*
|
|
1084
|
+
* @event Topology#serverDescriptionChanged
|
|
1085
|
+
* @type {ServerDescriptionChangedEvent}
|
|
1086
|
+
*/
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* A topology open SDAM event
|
|
1090
|
+
*
|
|
1091
|
+
* @event Topology#topologyOpening
|
|
1092
|
+
* @type {TopologyOpeningEvent}
|
|
1093
|
+
*/
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* A topology closed SDAM event
|
|
1097
|
+
*
|
|
1098
|
+
* @event Topology#topologyClosed
|
|
1099
|
+
* @type {TopologyClosedEvent}
|
|
1100
|
+
*/
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* A topology structure SDAM change event
|
|
1104
|
+
*
|
|
1105
|
+
* @event Topology#topologyDescriptionChanged
|
|
1106
|
+
* @type {TopologyDescriptionChangedEvent}
|
|
1107
|
+
*/
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* A topology serverHeartbeatStarted SDAM event
|
|
1111
|
+
*
|
|
1112
|
+
* @event Topology#serverHeartbeatStarted
|
|
1113
|
+
* @type {ServerHeartbeatStartedEvent}
|
|
1114
|
+
*/
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* A topology serverHeartbeatFailed SDAM event
|
|
1118
|
+
*
|
|
1119
|
+
* @event Topology#serverHeartbeatFailed
|
|
1120
|
+
* @type {ServerHearbeatFailedEvent}
|
|
1121
|
+
*/
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* A topology serverHeartbeatSucceeded SDAM change event
|
|
1125
|
+
*
|
|
1126
|
+
* @event Topology#serverHeartbeatSucceeded
|
|
1127
|
+
* @type {ServerHeartbeatSucceededEvent}
|
|
1128
|
+
*/
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* An event emitted indicating a command was started, if command monitoring is enabled
|
|
1132
|
+
*
|
|
1133
|
+
* @event Topology#commandStarted
|
|
1134
|
+
* @type {object}
|
|
1135
|
+
*/
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* An event emitted indicating a command succeeded, if command monitoring is enabled
|
|
1139
|
+
*
|
|
1140
|
+
* @event Topology#commandSucceeded
|
|
1141
|
+
* @type {object}
|
|
1142
|
+
*/
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* An event emitted indicating a command failed, if command monitoring is enabled
|
|
1146
|
+
*
|
|
1147
|
+
* @event Topology#commandFailed
|
|
1148
|
+
* @type {object}
|
|
1149
|
+
*/
|
|
1150
|
+
|
|
1151
|
+
module.exports = Topology;
|