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,1507 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const inherits = require('util').inherits;
|
|
4
|
+
const f = require('util').format;
|
|
5
|
+
const EventEmitter = require('events').EventEmitter;
|
|
6
|
+
const ReadPreference = require('./read_preference');
|
|
7
|
+
const BasicCursor = require('../cursor');
|
|
8
|
+
const retrieveBSON = require('../connection/utils').retrieveBSON;
|
|
9
|
+
const Logger = require('../connection/logger');
|
|
10
|
+
const MongoError = require('../error').MongoError;
|
|
11
|
+
const Server = require('./server');
|
|
12
|
+
const ReplSetState = require('./replset_state');
|
|
13
|
+
const clone = require('./shared').clone;
|
|
14
|
+
const Timeout = require('./shared').Timeout;
|
|
15
|
+
const Interval = require('./shared').Interval;
|
|
16
|
+
const createClientInfo = require('./shared').createClientInfo;
|
|
17
|
+
const SessionMixins = require('./shared').SessionMixins;
|
|
18
|
+
const isRetryableWritesSupported = require('./shared').isRetryableWritesSupported;
|
|
19
|
+
const relayEvents = require('../utils').relayEvents;
|
|
20
|
+
const isRetryableError = require('../error').isRetryableError;
|
|
21
|
+
const BSON = retrieveBSON();
|
|
22
|
+
|
|
23
|
+
//
|
|
24
|
+
// States
|
|
25
|
+
var DISCONNECTED = 'disconnected';
|
|
26
|
+
var CONNECTING = 'connecting';
|
|
27
|
+
var CONNECTED = 'connected';
|
|
28
|
+
var UNREFERENCED = 'unreferenced';
|
|
29
|
+
var DESTROYED = 'destroyed';
|
|
30
|
+
|
|
31
|
+
function stateTransition(self, newState) {
|
|
32
|
+
var legalTransitions = {
|
|
33
|
+
disconnected: [CONNECTING, DESTROYED, DISCONNECTED],
|
|
34
|
+
connecting: [CONNECTING, DESTROYED, CONNECTED, DISCONNECTED],
|
|
35
|
+
connected: [CONNECTED, DISCONNECTED, DESTROYED, UNREFERENCED],
|
|
36
|
+
unreferenced: [UNREFERENCED, DESTROYED],
|
|
37
|
+
destroyed: [DESTROYED]
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Get current state
|
|
41
|
+
var legalStates = legalTransitions[self.state];
|
|
42
|
+
if (legalStates && legalStates.indexOf(newState) !== -1) {
|
|
43
|
+
self.state = newState;
|
|
44
|
+
} else {
|
|
45
|
+
self.s.logger.error(
|
|
46
|
+
f(
|
|
47
|
+
'Pool with id [%s] failed attempted illegal state transition from [%s] to [%s] only following state allowed [%s]',
|
|
48
|
+
self.id,
|
|
49
|
+
self.state,
|
|
50
|
+
newState,
|
|
51
|
+
legalStates
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//
|
|
58
|
+
// ReplSet instance id
|
|
59
|
+
var id = 1;
|
|
60
|
+
var handlers = ['connect', 'close', 'error', 'timeout', 'parseError'];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a new Replset instance
|
|
64
|
+
* @class
|
|
65
|
+
* @param {array} seedlist A list of seeds for the replicaset
|
|
66
|
+
* @param {boolean} options.setName The Replicaset set name
|
|
67
|
+
* @param {boolean} [options.secondaryOnlyConnectionAllowed=false] Allow connection to a secondary only replicaset
|
|
68
|
+
* @param {number} [options.haInterval=10000] The High availability period for replicaset inquiry
|
|
69
|
+
* @param {boolean} [options.emitError=false] Server will emit errors events
|
|
70
|
+
* @param {Cursor} [options.cursorFactory=Cursor] The cursor factory class used for all query cursors
|
|
71
|
+
* @param {number} [options.size=5] Server connection pool size
|
|
72
|
+
* @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
|
|
73
|
+
* @param {number} [options.keepAliveInitialDelay=0] Initial delay before TCP keep alive enabled
|
|
74
|
+
* @param {boolean} [options.noDelay=true] TCP Connection no delay
|
|
75
|
+
* @param {number} [options.connectionTimeout=10000] TCP Connection timeout setting
|
|
76
|
+
* @param {number} [options.socketTimeout=0] TCP Socket timeout setting
|
|
77
|
+
* @param {boolean} [options.ssl=false] Use SSL for connection
|
|
78
|
+
* @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.
|
|
79
|
+
* @param {Buffer} [options.ca] SSL Certificate store binary buffer
|
|
80
|
+
* @param {Buffer} [options.crl] SSL Certificate revocation store binary buffer
|
|
81
|
+
* @param {Buffer} [options.cert] SSL Certificate binary buffer
|
|
82
|
+
* @param {Buffer} [options.key] SSL Key file binary buffer
|
|
83
|
+
* @param {string} [options.passphrase] SSL Certificate pass phrase
|
|
84
|
+
* @param {string} [options.servername=null] String containing the server name requested via TLS SNI.
|
|
85
|
+
* @param {boolean} [options.rejectUnauthorized=true] Reject unauthorized server certificates
|
|
86
|
+
* @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
|
|
87
|
+
* @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
|
|
88
|
+
* @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
|
|
89
|
+
* @param {number} [options.pingInterval=5000] Ping interval to check the response time to the different servers
|
|
90
|
+
* @param {number} [options.localThresholdMS=15] Cutoff latency point in MS for Replicaset member selection
|
|
91
|
+
* @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
|
|
92
|
+
* @param {boolean} [options.monitorCommands=false] Enable command monitoring for this topology
|
|
93
|
+
* @return {ReplSet} A cursor instance
|
|
94
|
+
* @fires ReplSet#connect
|
|
95
|
+
* @fires ReplSet#ha
|
|
96
|
+
* @fires ReplSet#joined
|
|
97
|
+
* @fires ReplSet#left
|
|
98
|
+
* @fires ReplSet#failed
|
|
99
|
+
* @fires ReplSet#fullsetup
|
|
100
|
+
* @fires ReplSet#all
|
|
101
|
+
* @fires ReplSet#error
|
|
102
|
+
* @fires ReplSet#serverHeartbeatStarted
|
|
103
|
+
* @fires ReplSet#serverHeartbeatSucceeded
|
|
104
|
+
* @fires ReplSet#serverHeartbeatFailed
|
|
105
|
+
* @fires ReplSet#topologyOpening
|
|
106
|
+
* @fires ReplSet#topologyClosed
|
|
107
|
+
* @fires ReplSet#topologyDescriptionChanged
|
|
108
|
+
* @property {string} type the topology type.
|
|
109
|
+
* @property {string} parserType the parser type used (c++ or js).
|
|
110
|
+
*/
|
|
111
|
+
var ReplSet = function(seedlist, options) {
|
|
112
|
+
var self = this;
|
|
113
|
+
options = options || {};
|
|
114
|
+
|
|
115
|
+
// Validate seedlist
|
|
116
|
+
if (!Array.isArray(seedlist)) throw new MongoError('seedlist must be an array');
|
|
117
|
+
// Validate list
|
|
118
|
+
if (seedlist.length === 0) throw new MongoError('seedlist must contain at least one entry');
|
|
119
|
+
// Validate entries
|
|
120
|
+
seedlist.forEach(function(e) {
|
|
121
|
+
if (typeof e.host !== 'string' || typeof e.port !== 'number')
|
|
122
|
+
throw new MongoError('seedlist entry must contain a host and port');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Add event listener
|
|
126
|
+
EventEmitter.call(this);
|
|
127
|
+
|
|
128
|
+
// Get replSet Id
|
|
129
|
+
this.id = id++;
|
|
130
|
+
|
|
131
|
+
// Get the localThresholdMS
|
|
132
|
+
var localThresholdMS = options.localThresholdMS || 15;
|
|
133
|
+
// Backward compatibility
|
|
134
|
+
if (options.acceptableLatency) localThresholdMS = options.acceptableLatency;
|
|
135
|
+
|
|
136
|
+
// Create a logger
|
|
137
|
+
var logger = Logger('ReplSet', options);
|
|
138
|
+
|
|
139
|
+
// Internal state
|
|
140
|
+
this.s = {
|
|
141
|
+
options: Object.assign({}, options),
|
|
142
|
+
// BSON instance
|
|
143
|
+
bson:
|
|
144
|
+
options.bson ||
|
|
145
|
+
new BSON([
|
|
146
|
+
BSON.Binary,
|
|
147
|
+
BSON.Code,
|
|
148
|
+
BSON.DBRef,
|
|
149
|
+
BSON.Decimal128,
|
|
150
|
+
BSON.Double,
|
|
151
|
+
BSON.Int32,
|
|
152
|
+
BSON.Long,
|
|
153
|
+
BSON.Map,
|
|
154
|
+
BSON.MaxKey,
|
|
155
|
+
BSON.MinKey,
|
|
156
|
+
BSON.ObjectId,
|
|
157
|
+
BSON.BSONRegExp,
|
|
158
|
+
BSON.Symbol,
|
|
159
|
+
BSON.Timestamp
|
|
160
|
+
]),
|
|
161
|
+
// Factory overrides
|
|
162
|
+
Cursor: options.cursorFactory || BasicCursor,
|
|
163
|
+
// Logger instance
|
|
164
|
+
logger: logger,
|
|
165
|
+
// Seedlist
|
|
166
|
+
seedlist: seedlist,
|
|
167
|
+
// Replicaset state
|
|
168
|
+
replicaSetState: new ReplSetState({
|
|
169
|
+
id: this.id,
|
|
170
|
+
setName: options.setName,
|
|
171
|
+
acceptableLatency: localThresholdMS,
|
|
172
|
+
heartbeatFrequencyMS: options.haInterval ? options.haInterval : 10000,
|
|
173
|
+
logger: logger
|
|
174
|
+
}),
|
|
175
|
+
// Current servers we are connecting to
|
|
176
|
+
connectingServers: [],
|
|
177
|
+
// Ha interval
|
|
178
|
+
haInterval: options.haInterval ? options.haInterval : 10000,
|
|
179
|
+
// Minimum heartbeat frequency used if we detect a server close
|
|
180
|
+
minHeartbeatFrequencyMS: 500,
|
|
181
|
+
// Disconnect handler
|
|
182
|
+
disconnectHandler: options.disconnectHandler,
|
|
183
|
+
// Server selection index
|
|
184
|
+
index: 0,
|
|
185
|
+
// Connect function options passed in
|
|
186
|
+
connectOptions: {},
|
|
187
|
+
// Are we running in debug mode
|
|
188
|
+
debug: typeof options.debug === 'boolean' ? options.debug : false,
|
|
189
|
+
// Client info
|
|
190
|
+
clientInfo: createClientInfo(options)
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Add handler for topology change
|
|
194
|
+
this.s.replicaSetState.on('topologyDescriptionChanged', function(r) {
|
|
195
|
+
self.emit('topologyDescriptionChanged', r);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Log info warning if the socketTimeout < haInterval as it will cause
|
|
199
|
+
// a lot of recycled connections to happen.
|
|
200
|
+
if (
|
|
201
|
+
this.s.logger.isWarn() &&
|
|
202
|
+
this.s.options.socketTimeout !== 0 &&
|
|
203
|
+
this.s.options.socketTimeout < this.s.haInterval
|
|
204
|
+
) {
|
|
205
|
+
this.s.logger.warn(
|
|
206
|
+
f(
|
|
207
|
+
'warning socketTimeout %s is less than haInterval %s. This might cause unnecessary server reconnections due to socket timeouts',
|
|
208
|
+
this.s.options.socketTimeout,
|
|
209
|
+
this.s.haInterval
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Add forwarding of events from state handler
|
|
215
|
+
var types = ['joined', 'left'];
|
|
216
|
+
types.forEach(function(x) {
|
|
217
|
+
self.s.replicaSetState.on(x, function(t, s) {
|
|
218
|
+
self.emit(x, t, s);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Connect stat
|
|
223
|
+
this.initialConnectState = {
|
|
224
|
+
connect: false,
|
|
225
|
+
fullsetup: false,
|
|
226
|
+
all: false
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Disconnected state
|
|
230
|
+
this.state = DISCONNECTED;
|
|
231
|
+
this.haTimeoutId = null;
|
|
232
|
+
// Last ismaster
|
|
233
|
+
this.ismaster = null;
|
|
234
|
+
// Contains the intervalId
|
|
235
|
+
this.intervalIds = [];
|
|
236
|
+
|
|
237
|
+
// Highest clusterTime seen in responses from the current deployment
|
|
238
|
+
this.clusterTime = null;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
inherits(ReplSet, EventEmitter);
|
|
242
|
+
Object.assign(ReplSet.prototype, SessionMixins);
|
|
243
|
+
|
|
244
|
+
Object.defineProperty(ReplSet.prototype, 'type', {
|
|
245
|
+
enumerable: true,
|
|
246
|
+
get: function() {
|
|
247
|
+
return 'replset';
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
Object.defineProperty(ReplSet.prototype, 'parserType', {
|
|
252
|
+
enumerable: true,
|
|
253
|
+
get: function() {
|
|
254
|
+
return BSON.native ? 'c++' : 'js';
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
Object.defineProperty(ReplSet.prototype, 'logicalSessionTimeoutMinutes', {
|
|
259
|
+
enumerable: true,
|
|
260
|
+
get: function() {
|
|
261
|
+
return this.s.replicaSetState.logicalSessionTimeoutMinutes || null;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
function rexecuteOperations(self) {
|
|
266
|
+
// If we have a primary and a disconnect handler, execute
|
|
267
|
+
// buffered operations
|
|
268
|
+
if (self.s.replicaSetState.hasPrimaryAndSecondary() && self.s.disconnectHandler) {
|
|
269
|
+
self.s.disconnectHandler.execute();
|
|
270
|
+
} else if (self.s.replicaSetState.hasPrimary() && self.s.disconnectHandler) {
|
|
271
|
+
self.s.disconnectHandler.execute({ executePrimary: true });
|
|
272
|
+
} else if (self.s.replicaSetState.hasSecondary() && self.s.disconnectHandler) {
|
|
273
|
+
self.s.disconnectHandler.execute({ executeSecondary: true });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function connectNewServers(self, servers, callback) {
|
|
278
|
+
// Count lefts
|
|
279
|
+
var count = servers.length;
|
|
280
|
+
var error = null;
|
|
281
|
+
|
|
282
|
+
// Handle events
|
|
283
|
+
var _handleEvent = function(self, event) {
|
|
284
|
+
return function(err) {
|
|
285
|
+
var _self = this;
|
|
286
|
+
count = count - 1;
|
|
287
|
+
|
|
288
|
+
// Destroyed
|
|
289
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) {
|
|
290
|
+
return this.destroy({ force: true });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (event === 'connect') {
|
|
294
|
+
// Destroyed
|
|
295
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) {
|
|
296
|
+
return _self.destroy({ force: true });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Update the state
|
|
300
|
+
var result = self.s.replicaSetState.update(_self);
|
|
301
|
+
// Update the state with the new server
|
|
302
|
+
if (result) {
|
|
303
|
+
// Primary lastIsMaster store it
|
|
304
|
+
if (_self.lastIsMaster() && _self.lastIsMaster().ismaster) {
|
|
305
|
+
self.ismaster = _self.lastIsMaster();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Remove the handlers
|
|
309
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
310
|
+
_self.removeAllListeners(handlers[i]);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Add stable state handlers
|
|
314
|
+
_self.on('error', handleEvent(self, 'error'));
|
|
315
|
+
_self.on('close', handleEvent(self, 'close'));
|
|
316
|
+
_self.on('timeout', handleEvent(self, 'timeout'));
|
|
317
|
+
_self.on('parseError', handleEvent(self, 'parseError'));
|
|
318
|
+
|
|
319
|
+
// Enalbe the monitoring of the new server
|
|
320
|
+
monitorServer(_self.lastIsMaster().me, self, {});
|
|
321
|
+
|
|
322
|
+
// Rexecute any stalled operation
|
|
323
|
+
rexecuteOperations(self);
|
|
324
|
+
} else {
|
|
325
|
+
_self.destroy({ force: true });
|
|
326
|
+
}
|
|
327
|
+
} else if (event === 'error') {
|
|
328
|
+
error = err;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Rexecute any stalled operation
|
|
332
|
+
rexecuteOperations(self);
|
|
333
|
+
|
|
334
|
+
// Are we done finish up callback
|
|
335
|
+
if (count === 0) {
|
|
336
|
+
callback(error);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// No new servers
|
|
342
|
+
if (count === 0) return callback();
|
|
343
|
+
|
|
344
|
+
// Execute method
|
|
345
|
+
function execute(_server, i) {
|
|
346
|
+
setTimeout(function() {
|
|
347
|
+
// Destroyed
|
|
348
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Create a new server instance
|
|
353
|
+
var server = new Server(
|
|
354
|
+
Object.assign({}, self.s.options, {
|
|
355
|
+
host: _server.split(':')[0],
|
|
356
|
+
port: parseInt(_server.split(':')[1], 10),
|
|
357
|
+
reconnect: false,
|
|
358
|
+
monitoring: false,
|
|
359
|
+
parent: self,
|
|
360
|
+
clientInfo: clone(self.s.clientInfo)
|
|
361
|
+
})
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
// Add temp handlers
|
|
365
|
+
server.once('connect', _handleEvent(self, 'connect'));
|
|
366
|
+
server.once('close', _handleEvent(self, 'close'));
|
|
367
|
+
server.once('timeout', _handleEvent(self, 'timeout'));
|
|
368
|
+
server.once('error', _handleEvent(self, 'error'));
|
|
369
|
+
server.once('parseError', _handleEvent(self, 'parseError'));
|
|
370
|
+
|
|
371
|
+
// SDAM Monitoring events
|
|
372
|
+
server.on('serverOpening', e => self.emit('serverOpening', e));
|
|
373
|
+
server.on('serverDescriptionChanged', e => self.emit('serverDescriptionChanged', e));
|
|
374
|
+
server.on('serverClosed', e => self.emit('serverClosed', e));
|
|
375
|
+
|
|
376
|
+
// Command Monitoring events
|
|
377
|
+
relayEvents(server, self, ['commandStarted', 'commandSucceeded', 'commandFailed']);
|
|
378
|
+
|
|
379
|
+
server.connect(self.s.connectOptions);
|
|
380
|
+
}, i);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Create new instances
|
|
384
|
+
for (var i = 0; i < servers.length; i++) {
|
|
385
|
+
execute(servers[i], i);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Ping the server
|
|
390
|
+
var pingServer = function(self, server, cb) {
|
|
391
|
+
// Measure running time
|
|
392
|
+
var start = new Date().getTime();
|
|
393
|
+
|
|
394
|
+
// Emit the server heartbeat start
|
|
395
|
+
emitSDAMEvent(self, 'serverHeartbeatStarted', { connectionId: server.name });
|
|
396
|
+
|
|
397
|
+
// Execute ismaster
|
|
398
|
+
// Set the socketTimeout for a monitoring message to a low number
|
|
399
|
+
// Ensuring ismaster calls are timed out quickly
|
|
400
|
+
server.command(
|
|
401
|
+
'admin.$cmd',
|
|
402
|
+
{
|
|
403
|
+
ismaster: true
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
monitoring: true,
|
|
407
|
+
socketTimeout: self.s.options.connectionTimeout || 2000
|
|
408
|
+
},
|
|
409
|
+
function(err, r) {
|
|
410
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) {
|
|
411
|
+
server.destroy({ force: true });
|
|
412
|
+
return cb(err, r);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Calculate latency
|
|
416
|
+
var latencyMS = new Date().getTime() - start;
|
|
417
|
+
// Set the last updatedTime
|
|
418
|
+
var hrTime = process.hrtime();
|
|
419
|
+
// Calculate the last update time
|
|
420
|
+
server.lastUpdateTime = hrTime[0] * 1000 + Math.round(hrTime[1] / 1000);
|
|
421
|
+
|
|
422
|
+
// We had an error, remove it from the state
|
|
423
|
+
if (err) {
|
|
424
|
+
// Emit the server heartbeat failure
|
|
425
|
+
emitSDAMEvent(self, 'serverHeartbeatFailed', {
|
|
426
|
+
durationMS: latencyMS,
|
|
427
|
+
failure: err,
|
|
428
|
+
connectionId: server.name
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Remove server from the state
|
|
432
|
+
self.s.replicaSetState.remove(server);
|
|
433
|
+
} else {
|
|
434
|
+
// Update the server ismaster
|
|
435
|
+
server.ismaster = r.result;
|
|
436
|
+
|
|
437
|
+
// Check if we have a lastWriteDate convert it to MS
|
|
438
|
+
// and store on the server instance for later use
|
|
439
|
+
if (server.ismaster.lastWrite && server.ismaster.lastWrite.lastWriteDate) {
|
|
440
|
+
server.lastWriteDate = server.ismaster.lastWrite.lastWriteDate.getTime();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Do we have a brand new server
|
|
444
|
+
if (server.lastIsMasterMS === -1) {
|
|
445
|
+
server.lastIsMasterMS = latencyMS;
|
|
446
|
+
} else if (server.lastIsMasterMS) {
|
|
447
|
+
// After the first measurement, average RTT MUST be computed using an
|
|
448
|
+
// exponentially-weighted moving average formula, with a weighting factor (alpha) of 0.2.
|
|
449
|
+
// If the prior average is denoted old_rtt, then the new average (new_rtt) is
|
|
450
|
+
// computed from a new RTT measurement (x) using the following formula:
|
|
451
|
+
// alpha = 0.2
|
|
452
|
+
// new_rtt = alpha * x + (1 - alpha) * old_rtt
|
|
453
|
+
server.lastIsMasterMS = 0.2 * latencyMS + (1 - 0.2) * server.lastIsMasterMS;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (self.s.replicaSetState.update(server)) {
|
|
457
|
+
// Primary lastIsMaster store it
|
|
458
|
+
if (server.lastIsMaster() && server.lastIsMaster().ismaster) {
|
|
459
|
+
self.ismaster = server.lastIsMaster();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Server heart beat event
|
|
464
|
+
emitSDAMEvent(self, 'serverHeartbeatSucceeded', {
|
|
465
|
+
durationMS: latencyMS,
|
|
466
|
+
reply: r.result,
|
|
467
|
+
connectionId: server.name
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Calculate the staleness for this server
|
|
472
|
+
self.s.replicaSetState.updateServerMaxStaleness(server, self.s.haInterval);
|
|
473
|
+
|
|
474
|
+
// Callback
|
|
475
|
+
cb(err, r);
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// Each server is monitored in parallel in their own timeout loop
|
|
481
|
+
var monitorServer = function(host, self, options) {
|
|
482
|
+
// If this is not the initial scan
|
|
483
|
+
// Is this server already being monitoried, then skip monitoring
|
|
484
|
+
if (!options.haInterval) {
|
|
485
|
+
for (var i = 0; i < self.intervalIds.length; i++) {
|
|
486
|
+
if (self.intervalIds[i].__host === host) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Get the haInterval
|
|
493
|
+
var _process = options.haInterval ? Timeout : Interval;
|
|
494
|
+
var _haInterval = options.haInterval ? options.haInterval : self.s.haInterval;
|
|
495
|
+
|
|
496
|
+
// Create the interval
|
|
497
|
+
var intervalId = new _process(function() {
|
|
498
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) {
|
|
499
|
+
// clearInterval(intervalId);
|
|
500
|
+
intervalId.stop();
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Do we already have server connection available for this host
|
|
505
|
+
var _server = self.s.replicaSetState.get(host);
|
|
506
|
+
|
|
507
|
+
// Check if we have a known server connection and reuse
|
|
508
|
+
if (_server) {
|
|
509
|
+
// Ping the server
|
|
510
|
+
return pingServer(self, _server, function(err) {
|
|
511
|
+
if (err) {
|
|
512
|
+
// NOTE: should something happen here?
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) {
|
|
517
|
+
intervalId.stop();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Filter out all called intervaliIds
|
|
522
|
+
self.intervalIds = self.intervalIds.filter(function(intervalId) {
|
|
523
|
+
return intervalId.isRunning();
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Initial sweep
|
|
527
|
+
if (_process === Timeout) {
|
|
528
|
+
if (
|
|
529
|
+
self.state === CONNECTING &&
|
|
530
|
+
((self.s.replicaSetState.hasSecondary() &&
|
|
531
|
+
self.s.options.secondaryOnlyConnectionAllowed) ||
|
|
532
|
+
self.s.replicaSetState.hasPrimary())
|
|
533
|
+
) {
|
|
534
|
+
self.state = CONNECTED;
|
|
535
|
+
|
|
536
|
+
// Emit connected sign
|
|
537
|
+
process.nextTick(function() {
|
|
538
|
+
self.emit('connect', self);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// Start topology interval check
|
|
542
|
+
topologyMonitor(self, {});
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
if (
|
|
546
|
+
self.state === DISCONNECTED &&
|
|
547
|
+
((self.s.replicaSetState.hasSecondary() &&
|
|
548
|
+
self.s.options.secondaryOnlyConnectionAllowed) ||
|
|
549
|
+
self.s.replicaSetState.hasPrimary())
|
|
550
|
+
) {
|
|
551
|
+
self.state = CONNECTED;
|
|
552
|
+
|
|
553
|
+
// Rexecute any stalled operation
|
|
554
|
+
rexecuteOperations(self);
|
|
555
|
+
|
|
556
|
+
// Emit connected sign
|
|
557
|
+
process.nextTick(function() {
|
|
558
|
+
self.emit('reconnect', self);
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (
|
|
564
|
+
self.initialConnectState.connect &&
|
|
565
|
+
!self.initialConnectState.fullsetup &&
|
|
566
|
+
self.s.replicaSetState.hasPrimaryAndSecondary()
|
|
567
|
+
) {
|
|
568
|
+
// Set initial connect state
|
|
569
|
+
self.initialConnectState.fullsetup = true;
|
|
570
|
+
self.initialConnectState.all = true;
|
|
571
|
+
|
|
572
|
+
process.nextTick(function() {
|
|
573
|
+
self.emit('fullsetup', self);
|
|
574
|
+
self.emit('all', self);
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}, _haInterval);
|
|
580
|
+
|
|
581
|
+
// Start the interval
|
|
582
|
+
intervalId.start();
|
|
583
|
+
// Add the intervalId host name
|
|
584
|
+
intervalId.__host = host;
|
|
585
|
+
// Add the intervalId to our list of intervalIds
|
|
586
|
+
self.intervalIds.push(intervalId);
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
function topologyMonitor(self, options) {
|
|
590
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) return;
|
|
591
|
+
options = options || {};
|
|
592
|
+
|
|
593
|
+
// Get the servers
|
|
594
|
+
var servers = Object.keys(self.s.replicaSetState.set);
|
|
595
|
+
|
|
596
|
+
// Get the haInterval
|
|
597
|
+
var _process = options.haInterval ? Timeout : Interval;
|
|
598
|
+
var _haInterval = options.haInterval ? options.haInterval : self.s.haInterval;
|
|
599
|
+
|
|
600
|
+
if (_process === Timeout) {
|
|
601
|
+
return connectNewServers(self, self.s.replicaSetState.unknownServers, function(err) {
|
|
602
|
+
// Don't emit errors if the connection was already
|
|
603
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (!self.s.replicaSetState.hasPrimary() && !self.s.options.secondaryOnlyConnectionAllowed) {
|
|
608
|
+
if (err) {
|
|
609
|
+
return self.emit('error', err);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
self.emit(
|
|
613
|
+
'error',
|
|
614
|
+
new MongoError('no primary found in replicaset or invalid replica set name')
|
|
615
|
+
);
|
|
616
|
+
return self.destroy({ force: true });
|
|
617
|
+
} else if (
|
|
618
|
+
!self.s.replicaSetState.hasSecondary() &&
|
|
619
|
+
self.s.options.secondaryOnlyConnectionAllowed
|
|
620
|
+
) {
|
|
621
|
+
if (err) {
|
|
622
|
+
return self.emit('error', err);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
self.emit(
|
|
626
|
+
'error',
|
|
627
|
+
new MongoError('no secondary found in replicaset or invalid replica set name')
|
|
628
|
+
);
|
|
629
|
+
return self.destroy({ force: true });
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
for (var i = 0; i < servers.length; i++) {
|
|
633
|
+
monitorServer(servers[i], self, options);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
} else {
|
|
637
|
+
for (var i = 0; i < servers.length; i++) {
|
|
638
|
+
monitorServer(servers[i], self, options);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Run the reconnect process
|
|
643
|
+
function executeReconnect(self) {
|
|
644
|
+
return function() {
|
|
645
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
connectNewServers(self, self.s.replicaSetState.unknownServers, function() {
|
|
650
|
+
var monitoringFrequencey = self.s.replicaSetState.hasPrimary()
|
|
651
|
+
? _haInterval
|
|
652
|
+
: self.s.minHeartbeatFrequencyMS;
|
|
653
|
+
|
|
654
|
+
// Create a timeout
|
|
655
|
+
self.intervalIds.push(new Timeout(executeReconnect(self), monitoringFrequencey).start());
|
|
656
|
+
});
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Decide what kind of interval to use
|
|
661
|
+
var intervalTime = !self.s.replicaSetState.hasPrimary()
|
|
662
|
+
? self.s.minHeartbeatFrequencyMS
|
|
663
|
+
: _haInterval;
|
|
664
|
+
|
|
665
|
+
self.intervalIds.push(new Timeout(executeReconnect(self), intervalTime).start());
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function addServerToList(list, server) {
|
|
669
|
+
for (var i = 0; i < list.length; i++) {
|
|
670
|
+
if (list[i].name.toLowerCase() === server.name.toLowerCase()) return true;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
list.push(server);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function handleEvent(self, event) {
|
|
677
|
+
return function() {
|
|
678
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) return;
|
|
679
|
+
// Debug log
|
|
680
|
+
if (self.s.logger.isDebug()) {
|
|
681
|
+
self.s.logger.debug(
|
|
682
|
+
f('handleEvent %s from server %s in replset with id %s', event, this.name, self.id)
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Remove from the replicaset state
|
|
687
|
+
self.s.replicaSetState.remove(this);
|
|
688
|
+
|
|
689
|
+
// Are we in a destroyed state return
|
|
690
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) return;
|
|
691
|
+
|
|
692
|
+
// If no primary and secondary available
|
|
693
|
+
if (
|
|
694
|
+
!self.s.replicaSetState.hasPrimary() &&
|
|
695
|
+
!self.s.replicaSetState.hasSecondary() &&
|
|
696
|
+
self.s.options.secondaryOnlyConnectionAllowed
|
|
697
|
+
) {
|
|
698
|
+
stateTransition(self, DISCONNECTED);
|
|
699
|
+
} else if (!self.s.replicaSetState.hasPrimary()) {
|
|
700
|
+
stateTransition(self, DISCONNECTED);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
addServerToList(self.s.connectingServers, this);
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function shouldTriggerConnect(self) {
|
|
708
|
+
const isConnecting = self.state === CONNECTING;
|
|
709
|
+
const hasPrimary = self.s.replicaSetState.hasPrimary();
|
|
710
|
+
const hasSecondary = self.s.replicaSetState.hasSecondary();
|
|
711
|
+
const secondaryOnlyConnectionAllowed = self.s.options.secondaryOnlyConnectionAllowed;
|
|
712
|
+
const readPreferenceSecondary =
|
|
713
|
+
self.s.connectOptions.readPreference &&
|
|
714
|
+
self.s.connectOptions.readPreference.equals(ReadPreference.secondary);
|
|
715
|
+
|
|
716
|
+
return (
|
|
717
|
+
(isConnecting &&
|
|
718
|
+
((readPreferenceSecondary && hasSecondary) || (!readPreferenceSecondary && hasPrimary))) ||
|
|
719
|
+
(hasSecondary && secondaryOnlyConnectionAllowed)
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function handleInitialConnectEvent(self, event) {
|
|
724
|
+
return function() {
|
|
725
|
+
var _this = this;
|
|
726
|
+
// Debug log
|
|
727
|
+
if (self.s.logger.isDebug()) {
|
|
728
|
+
self.s.logger.debug(
|
|
729
|
+
f(
|
|
730
|
+
'handleInitialConnectEvent %s from server %s in replset with id %s',
|
|
731
|
+
event,
|
|
732
|
+
this.name,
|
|
733
|
+
self.id
|
|
734
|
+
)
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Destroy the instance
|
|
739
|
+
if (self.state === DESTROYED || self.state === UNREFERENCED) {
|
|
740
|
+
return this.destroy({ force: true });
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Check the type of server
|
|
744
|
+
if (event === 'connect') {
|
|
745
|
+
// Update the state
|
|
746
|
+
var result = self.s.replicaSetState.update(_this);
|
|
747
|
+
if (result === true) {
|
|
748
|
+
// Primary lastIsMaster store it
|
|
749
|
+
if (_this.lastIsMaster() && _this.lastIsMaster().ismaster) {
|
|
750
|
+
self.ismaster = _this.lastIsMaster();
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Debug log
|
|
754
|
+
if (self.s.logger.isDebug()) {
|
|
755
|
+
self.s.logger.debug(
|
|
756
|
+
f(
|
|
757
|
+
'handleInitialConnectEvent %s from server %s in replset with id %s has state [%s]',
|
|
758
|
+
event,
|
|
759
|
+
_this.name,
|
|
760
|
+
self.id,
|
|
761
|
+
JSON.stringify(self.s.replicaSetState.set)
|
|
762
|
+
)
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Remove the handlers
|
|
767
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
768
|
+
_this.removeAllListeners(handlers[i]);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Add stable state handlers
|
|
772
|
+
_this.on('error', handleEvent(self, 'error'));
|
|
773
|
+
_this.on('close', handleEvent(self, 'close'));
|
|
774
|
+
_this.on('timeout', handleEvent(self, 'timeout'));
|
|
775
|
+
_this.on('parseError', handleEvent(self, 'parseError'));
|
|
776
|
+
|
|
777
|
+
// Do we have a primary or primaryAndSecondary
|
|
778
|
+
if (shouldTriggerConnect(self)) {
|
|
779
|
+
// We are connected
|
|
780
|
+
self.state = CONNECTED;
|
|
781
|
+
|
|
782
|
+
// Set initial connect state
|
|
783
|
+
self.initialConnectState.connect = true;
|
|
784
|
+
// Emit connect event
|
|
785
|
+
process.nextTick(function() {
|
|
786
|
+
self.emit('connect', self);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
topologyMonitor(self, {});
|
|
790
|
+
}
|
|
791
|
+
} else if (result instanceof MongoError) {
|
|
792
|
+
_this.destroy({ force: true });
|
|
793
|
+
self.destroy({ force: true });
|
|
794
|
+
return self.emit('error', result);
|
|
795
|
+
} else {
|
|
796
|
+
_this.destroy({ force: true });
|
|
797
|
+
}
|
|
798
|
+
} else {
|
|
799
|
+
// Emit failure to connect
|
|
800
|
+
self.emit('failed', this);
|
|
801
|
+
|
|
802
|
+
addServerToList(self.s.connectingServers, this);
|
|
803
|
+
// Remove from the state
|
|
804
|
+
self.s.replicaSetState.remove(this);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (
|
|
808
|
+
self.initialConnectState.connect &&
|
|
809
|
+
!self.initialConnectState.fullsetup &&
|
|
810
|
+
self.s.replicaSetState.hasPrimaryAndSecondary()
|
|
811
|
+
) {
|
|
812
|
+
// Set initial connect state
|
|
813
|
+
self.initialConnectState.fullsetup = true;
|
|
814
|
+
self.initialConnectState.all = true;
|
|
815
|
+
|
|
816
|
+
process.nextTick(function() {
|
|
817
|
+
self.emit('fullsetup', self);
|
|
818
|
+
self.emit('all', self);
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Remove from the list from connectingServers
|
|
823
|
+
for (var i = 0; i < self.s.connectingServers.length; i++) {
|
|
824
|
+
if (self.s.connectingServers[i].equals(this)) {
|
|
825
|
+
self.s.connectingServers.splice(i, 1);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Trigger topologyMonitor
|
|
830
|
+
if (self.s.connectingServers.length === 0 && self.state === CONNECTING) {
|
|
831
|
+
topologyMonitor(self, { haInterval: 1 });
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function connectServers(self, servers) {
|
|
837
|
+
// Update connectingServers
|
|
838
|
+
self.s.connectingServers = self.s.connectingServers.concat(servers);
|
|
839
|
+
|
|
840
|
+
// Index used to interleaf the server connects, avoiding
|
|
841
|
+
// runtime issues on io constrained vm's
|
|
842
|
+
var timeoutInterval = 0;
|
|
843
|
+
|
|
844
|
+
function connect(server, timeoutInterval) {
|
|
845
|
+
setTimeout(function() {
|
|
846
|
+
// Add the server to the state
|
|
847
|
+
if (self.s.replicaSetState.update(server)) {
|
|
848
|
+
// Primary lastIsMaster store it
|
|
849
|
+
if (server.lastIsMaster() && server.lastIsMaster().ismaster) {
|
|
850
|
+
self.ismaster = server.lastIsMaster();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Add event handlers
|
|
855
|
+
server.once('close', handleInitialConnectEvent(self, 'close'));
|
|
856
|
+
server.once('timeout', handleInitialConnectEvent(self, 'timeout'));
|
|
857
|
+
server.once('parseError', handleInitialConnectEvent(self, 'parseError'));
|
|
858
|
+
server.once('error', handleInitialConnectEvent(self, 'error'));
|
|
859
|
+
server.once('connect', handleInitialConnectEvent(self, 'connect'));
|
|
860
|
+
|
|
861
|
+
// SDAM Monitoring events
|
|
862
|
+
server.on('serverOpening', e => self.emit('serverOpening', e));
|
|
863
|
+
server.on('serverDescriptionChanged', e => self.emit('serverDescriptionChanged', e));
|
|
864
|
+
server.on('serverClosed', e => self.emit('serverClosed', e));
|
|
865
|
+
|
|
866
|
+
// Command Monitoring events
|
|
867
|
+
relayEvents(server, self, ['commandStarted', 'commandSucceeded', 'commandFailed']);
|
|
868
|
+
|
|
869
|
+
// Start connection
|
|
870
|
+
server.connect(self.s.connectOptions);
|
|
871
|
+
}, timeoutInterval);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Start all the servers
|
|
875
|
+
while (servers.length > 0) {
|
|
876
|
+
connect(servers.shift(), timeoutInterval++);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Emit event if it exists
|
|
882
|
+
* @method
|
|
883
|
+
*/
|
|
884
|
+
function emitSDAMEvent(self, event, description) {
|
|
885
|
+
if (self.listeners(event).length > 0) {
|
|
886
|
+
self.emit(event, description);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Initiate server connect
|
|
892
|
+
*/
|
|
893
|
+
ReplSet.prototype.connect = function(options) {
|
|
894
|
+
var self = this;
|
|
895
|
+
// Add any connect level options to the internal state
|
|
896
|
+
this.s.connectOptions = options || {};
|
|
897
|
+
|
|
898
|
+
// Set connecting state
|
|
899
|
+
stateTransition(this, CONNECTING);
|
|
900
|
+
|
|
901
|
+
// Create server instances
|
|
902
|
+
var servers = this.s.seedlist.map(function(x) {
|
|
903
|
+
return new Server(
|
|
904
|
+
Object.assign({}, self.s.options, x, options, {
|
|
905
|
+
reconnect: false,
|
|
906
|
+
monitoring: false,
|
|
907
|
+
parent: self,
|
|
908
|
+
clientInfo: clone(self.s.clientInfo)
|
|
909
|
+
})
|
|
910
|
+
);
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
// Error out as high availbility interval must be < than socketTimeout
|
|
914
|
+
if (
|
|
915
|
+
this.s.options.socketTimeout > 0 &&
|
|
916
|
+
this.s.options.socketTimeout <= this.s.options.haInterval
|
|
917
|
+
) {
|
|
918
|
+
return self.emit(
|
|
919
|
+
'error',
|
|
920
|
+
new MongoError(
|
|
921
|
+
f(
|
|
922
|
+
'haInterval [%s] MS must be set to less than socketTimeout [%s] MS',
|
|
923
|
+
this.s.options.haInterval,
|
|
924
|
+
this.s.options.socketTimeout
|
|
925
|
+
)
|
|
926
|
+
)
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// Emit the topology opening event
|
|
931
|
+
emitSDAMEvent(this, 'topologyOpening', { topologyId: this.id });
|
|
932
|
+
// Start all server connections
|
|
933
|
+
connectServers(self, servers);
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Authenticate the topology.
|
|
938
|
+
* @method
|
|
939
|
+
* @param {MongoCredentials} credentials The credentials for authentication we are using
|
|
940
|
+
* @param {authResultCallback} callback A callback function
|
|
941
|
+
*/
|
|
942
|
+
ReplSet.prototype.auth = function(credentials, callback) {
|
|
943
|
+
if (typeof callback === 'function') callback(null, null);
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Destroy the server connection
|
|
948
|
+
* @param {boolean} [options.force=false] Force destroy the pool
|
|
949
|
+
* @method
|
|
950
|
+
*/
|
|
951
|
+
ReplSet.prototype.destroy = function(options, callback) {
|
|
952
|
+
options = options || {};
|
|
953
|
+
|
|
954
|
+
let destroyCount = this.s.connectingServers.length + 1; // +1 for the callback from `replicaSetState.destroy`
|
|
955
|
+
const serverDestroyed = () => {
|
|
956
|
+
destroyCount--;
|
|
957
|
+
if (destroyCount > 0) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Emit toplogy closing event
|
|
962
|
+
emitSDAMEvent(this, 'topologyClosed', { topologyId: this.id });
|
|
963
|
+
|
|
964
|
+
// Transition state
|
|
965
|
+
stateTransition(this, DESTROYED);
|
|
966
|
+
|
|
967
|
+
if (typeof callback === 'function') {
|
|
968
|
+
callback(null, null);
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
// Clear out any monitoring process
|
|
973
|
+
if (this.haTimeoutId) clearTimeout(this.haTimeoutId);
|
|
974
|
+
|
|
975
|
+
// Clear out all monitoring
|
|
976
|
+
for (var i = 0; i < this.intervalIds.length; i++) {
|
|
977
|
+
this.intervalIds[i].stop();
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// Reset list of intervalIds
|
|
981
|
+
this.intervalIds = [];
|
|
982
|
+
|
|
983
|
+
if (destroyCount === 0) {
|
|
984
|
+
serverDestroyed();
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Destroy the replicaset
|
|
989
|
+
this.s.replicaSetState.destroy(options, serverDestroyed);
|
|
990
|
+
|
|
991
|
+
// Destroy all connecting servers
|
|
992
|
+
this.s.connectingServers.forEach(function(x) {
|
|
993
|
+
x.destroy(options, serverDestroyed);
|
|
994
|
+
});
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Unref all connections belong to this server
|
|
999
|
+
* @method
|
|
1000
|
+
*/
|
|
1001
|
+
ReplSet.prototype.unref = function() {
|
|
1002
|
+
// Transition state
|
|
1003
|
+
stateTransition(this, UNREFERENCED);
|
|
1004
|
+
|
|
1005
|
+
this.s.replicaSetState.allServers().forEach(function(x) {
|
|
1006
|
+
x.unref();
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
clearTimeout(this.haTimeoutId);
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Returns the last known ismaster document for this server
|
|
1014
|
+
* @method
|
|
1015
|
+
* @return {object}
|
|
1016
|
+
*/
|
|
1017
|
+
ReplSet.prototype.lastIsMaster = function() {
|
|
1018
|
+
// If secondaryOnlyConnectionAllowed and no primary but secondary
|
|
1019
|
+
// return the secondaries ismaster result.
|
|
1020
|
+
if (
|
|
1021
|
+
this.s.options.secondaryOnlyConnectionAllowed &&
|
|
1022
|
+
!this.s.replicaSetState.hasPrimary() &&
|
|
1023
|
+
this.s.replicaSetState.hasSecondary()
|
|
1024
|
+
) {
|
|
1025
|
+
return this.s.replicaSetState.secondaries[0].lastIsMaster();
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
return this.s.replicaSetState.primary
|
|
1029
|
+
? this.s.replicaSetState.primary.lastIsMaster()
|
|
1030
|
+
: this.ismaster;
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* All raw connections
|
|
1035
|
+
* @method
|
|
1036
|
+
* @return {Connection[]}
|
|
1037
|
+
*/
|
|
1038
|
+
ReplSet.prototype.connections = function() {
|
|
1039
|
+
var servers = this.s.replicaSetState.allServers();
|
|
1040
|
+
var connections = [];
|
|
1041
|
+
for (var i = 0; i < servers.length; i++) {
|
|
1042
|
+
connections = connections.concat(servers[i].connections());
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
return connections;
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Figure out if the server is connected
|
|
1050
|
+
* @method
|
|
1051
|
+
* @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
|
|
1052
|
+
* @return {boolean}
|
|
1053
|
+
*/
|
|
1054
|
+
ReplSet.prototype.isConnected = function(options) {
|
|
1055
|
+
options = options || {};
|
|
1056
|
+
|
|
1057
|
+
// If we specified a read preference check if we are connected to something
|
|
1058
|
+
// than can satisfy this
|
|
1059
|
+
if (options.readPreference && options.readPreference.equals(ReadPreference.secondary)) {
|
|
1060
|
+
return this.s.replicaSetState.hasSecondary();
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (options.readPreference && options.readPreference.equals(ReadPreference.primary)) {
|
|
1064
|
+
return this.s.replicaSetState.hasPrimary();
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
if (options.readPreference && options.readPreference.equals(ReadPreference.primaryPreferred)) {
|
|
1068
|
+
return this.s.replicaSetState.hasSecondary() || this.s.replicaSetState.hasPrimary();
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (options.readPreference && options.readPreference.equals(ReadPreference.secondaryPreferred)) {
|
|
1072
|
+
return this.s.replicaSetState.hasSecondary() || this.s.replicaSetState.hasPrimary();
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if (this.s.options.secondaryOnlyConnectionAllowed && this.s.replicaSetState.hasSecondary()) {
|
|
1076
|
+
return true;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
return this.s.replicaSetState.hasPrimary();
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* Figure out if the replicaset instance was destroyed by calling destroy
|
|
1084
|
+
* @method
|
|
1085
|
+
* @return {boolean}
|
|
1086
|
+
*/
|
|
1087
|
+
ReplSet.prototype.isDestroyed = function() {
|
|
1088
|
+
return this.state === DESTROYED;
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Selects a server
|
|
1093
|
+
*
|
|
1094
|
+
* @method
|
|
1095
|
+
* @param {function} selector Unused
|
|
1096
|
+
* @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
|
|
1097
|
+
* @param {ClientSession} [options.session] Unused
|
|
1098
|
+
* @param {function} callback
|
|
1099
|
+
*/
|
|
1100
|
+
ReplSet.prototype.selectServer = function(selector, options, callback) {
|
|
1101
|
+
if (typeof selector === 'function' && typeof callback === 'undefined')
|
|
1102
|
+
(callback = selector), (selector = undefined), (options = {});
|
|
1103
|
+
if (typeof options === 'function')
|
|
1104
|
+
(callback = options), (options = selector), (selector = undefined);
|
|
1105
|
+
options = options || {};
|
|
1106
|
+
|
|
1107
|
+
const server = this.s.replicaSetState.pickServer(options.readPreference);
|
|
1108
|
+
if (this.s.debug) this.emit('pickedServer', options.readPreference, server);
|
|
1109
|
+
callback(null, server);
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Get all connected servers
|
|
1114
|
+
* @method
|
|
1115
|
+
* @return {Server[]}
|
|
1116
|
+
*/
|
|
1117
|
+
ReplSet.prototype.getServers = function() {
|
|
1118
|
+
return this.s.replicaSetState.allServers();
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
//
|
|
1122
|
+
// Execute write operation
|
|
1123
|
+
function executeWriteOperation(args, options, callback) {
|
|
1124
|
+
if (typeof options === 'function') (callback = options), (options = {});
|
|
1125
|
+
options = options || {};
|
|
1126
|
+
|
|
1127
|
+
// TODO: once we drop Node 4, use destructuring either here or in arguments.
|
|
1128
|
+
const self = args.self;
|
|
1129
|
+
const op = args.op;
|
|
1130
|
+
const ns = args.ns;
|
|
1131
|
+
const ops = args.ops;
|
|
1132
|
+
|
|
1133
|
+
if (self.state === DESTROYED) {
|
|
1134
|
+
return callback(new MongoError(f('topology was destroyed')));
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
const willRetryWrite =
|
|
1138
|
+
!args.retrying &&
|
|
1139
|
+
!!options.retryWrites &&
|
|
1140
|
+
options.session &&
|
|
1141
|
+
isRetryableWritesSupported(self) &&
|
|
1142
|
+
!options.session.inTransaction();
|
|
1143
|
+
|
|
1144
|
+
if (!self.s.replicaSetState.hasPrimary()) {
|
|
1145
|
+
if (self.s.disconnectHandler) {
|
|
1146
|
+
// Not connected but we have a disconnecthandler
|
|
1147
|
+
return self.s.disconnectHandler.add(op, ns, ops, options, callback);
|
|
1148
|
+
} else if (!willRetryWrite) {
|
|
1149
|
+
// No server returned we had an error
|
|
1150
|
+
return callback(new MongoError('no primary server found'));
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const handler = (err, result) => {
|
|
1155
|
+
if (!err) return callback(null, result);
|
|
1156
|
+
if (!isRetryableError(err)) {
|
|
1157
|
+
return callback(err);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if (willRetryWrite) {
|
|
1161
|
+
const newArgs = Object.assign({}, args, { retrying: true });
|
|
1162
|
+
return executeWriteOperation(newArgs, options, callback);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// Per SDAM, remove primary from replicaset
|
|
1166
|
+
if (self.s.replicaSetState.primary) {
|
|
1167
|
+
self.s.replicaSetState.remove(self.s.replicaSetState.primary, { force: true });
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
return callback(err);
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
if (callback.operationId) {
|
|
1174
|
+
handler.operationId = callback.operationId;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// increment and assign txnNumber
|
|
1178
|
+
if (willRetryWrite) {
|
|
1179
|
+
options.session.incrementTransactionNumber();
|
|
1180
|
+
options.willRetryWrite = willRetryWrite;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
self.s.replicaSetState.primary[op](ns, ops, options, handler);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Insert one or more documents
|
|
1188
|
+
* @method
|
|
1189
|
+
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
1190
|
+
* @param {array} ops An array of documents to insert
|
|
1191
|
+
* @param {boolean} [options.ordered=true] Execute in order or out of order
|
|
1192
|
+
* @param {object} [options.writeConcern={}] Write concern for the operation
|
|
1193
|
+
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
1194
|
+
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
1195
|
+
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
1196
|
+
* @param {boolean} [options.retryWrites] Enable retryable writes for this operation
|
|
1197
|
+
* @param {opResultCallback} callback A callback function
|
|
1198
|
+
*/
|
|
1199
|
+
ReplSet.prototype.insert = function(ns, ops, options, callback) {
|
|
1200
|
+
// Execute write operation
|
|
1201
|
+
executeWriteOperation({ self: this, op: 'insert', ns, ops }, options, callback);
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Perform one or more update operations
|
|
1206
|
+
* @method
|
|
1207
|
+
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
1208
|
+
* @param {array} ops An array of updates
|
|
1209
|
+
* @param {boolean} [options.ordered=true] Execute in order or out of order
|
|
1210
|
+
* @param {object} [options.writeConcern={}] Write concern for the operation
|
|
1211
|
+
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
1212
|
+
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
1213
|
+
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
1214
|
+
* @param {boolean} [options.retryWrites] Enable retryable writes for this operation
|
|
1215
|
+
* @param {opResultCallback} callback A callback function
|
|
1216
|
+
*/
|
|
1217
|
+
ReplSet.prototype.update = function(ns, ops, options, callback) {
|
|
1218
|
+
// Execute write operation
|
|
1219
|
+
executeWriteOperation({ self: this, op: 'update', ns, ops }, options, callback);
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* Perform one or more remove operations
|
|
1224
|
+
* @method
|
|
1225
|
+
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
1226
|
+
* @param {array} ops An array of removes
|
|
1227
|
+
* @param {boolean} [options.ordered=true] Execute in order or out of order
|
|
1228
|
+
* @param {object} [options.writeConcern={}] Write concern for the operation
|
|
1229
|
+
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
1230
|
+
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
1231
|
+
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
1232
|
+
* @param {boolean} [options.retryWrites] Enable retryable writes for this operation
|
|
1233
|
+
* @param {opResultCallback} callback A callback function
|
|
1234
|
+
*/
|
|
1235
|
+
ReplSet.prototype.remove = function(ns, ops, options, callback) {
|
|
1236
|
+
// Execute write operation
|
|
1237
|
+
executeWriteOperation({ self: this, op: 'remove', ns, ops }, options, callback);
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
const RETRYABLE_WRITE_OPERATIONS = ['findAndModify', 'insert', 'update', 'delete'];
|
|
1241
|
+
|
|
1242
|
+
function isWriteCommand(command) {
|
|
1243
|
+
return RETRYABLE_WRITE_OPERATIONS.some(op => command[op]);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/**
|
|
1247
|
+
* Execute a command
|
|
1248
|
+
* @method
|
|
1249
|
+
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
1250
|
+
* @param {object} cmd The command hash
|
|
1251
|
+
* @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
|
|
1252
|
+
* @param {Connection} [options.connection] Specify connection object to execute command against
|
|
1253
|
+
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
1254
|
+
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
1255
|
+
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
1256
|
+
* @param {opResultCallback} callback A callback function
|
|
1257
|
+
*/
|
|
1258
|
+
ReplSet.prototype.command = function(ns, cmd, options, callback) {
|
|
1259
|
+
if (typeof options === 'function') {
|
|
1260
|
+
(callback = options), (options = {}), (options = options || {});
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
if (this.state === DESTROYED) return callback(new MongoError(f('topology was destroyed')));
|
|
1264
|
+
var self = this;
|
|
1265
|
+
|
|
1266
|
+
// Establish readPreference
|
|
1267
|
+
var readPreference = options.readPreference ? options.readPreference : ReadPreference.primary;
|
|
1268
|
+
|
|
1269
|
+
// If the readPreference is primary and we have no primary, store it
|
|
1270
|
+
if (
|
|
1271
|
+
readPreference.preference === 'primary' &&
|
|
1272
|
+
!this.s.replicaSetState.hasPrimary() &&
|
|
1273
|
+
this.s.disconnectHandler != null
|
|
1274
|
+
) {
|
|
1275
|
+
return this.s.disconnectHandler.add('command', ns, cmd, options, callback);
|
|
1276
|
+
} else if (
|
|
1277
|
+
readPreference.preference === 'secondary' &&
|
|
1278
|
+
!this.s.replicaSetState.hasSecondary() &&
|
|
1279
|
+
this.s.disconnectHandler != null
|
|
1280
|
+
) {
|
|
1281
|
+
return this.s.disconnectHandler.add('command', ns, cmd, options, callback);
|
|
1282
|
+
} else if (
|
|
1283
|
+
readPreference.preference !== 'primary' &&
|
|
1284
|
+
!this.s.replicaSetState.hasSecondary() &&
|
|
1285
|
+
!this.s.replicaSetState.hasPrimary() &&
|
|
1286
|
+
this.s.disconnectHandler != null
|
|
1287
|
+
) {
|
|
1288
|
+
return this.s.disconnectHandler.add('command', ns, cmd, options, callback);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// Pick a server
|
|
1292
|
+
var server = this.s.replicaSetState.pickServer(readPreference);
|
|
1293
|
+
// We received an error, return it
|
|
1294
|
+
if (!(server instanceof Server)) return callback(server);
|
|
1295
|
+
// Emit debug event
|
|
1296
|
+
if (self.s.debug) self.emit('pickedServer', ReadPreference.primary, server);
|
|
1297
|
+
|
|
1298
|
+
// No server returned we had an error
|
|
1299
|
+
if (server == null) {
|
|
1300
|
+
return callback(
|
|
1301
|
+
new MongoError(
|
|
1302
|
+
f('no server found that matches the provided readPreference %s', readPreference)
|
|
1303
|
+
)
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
const willRetryWrite =
|
|
1308
|
+
!options.retrying &&
|
|
1309
|
+
!!options.retryWrites &&
|
|
1310
|
+
options.session &&
|
|
1311
|
+
isRetryableWritesSupported(self) &&
|
|
1312
|
+
!options.session.inTransaction() &&
|
|
1313
|
+
isWriteCommand(cmd);
|
|
1314
|
+
|
|
1315
|
+
const cb = (err, result) => {
|
|
1316
|
+
if (!err) return callback(null, result);
|
|
1317
|
+
if (!isRetryableError(err)) {
|
|
1318
|
+
return callback(err);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
if (willRetryWrite) {
|
|
1322
|
+
const newOptions = Object.assign({}, options, { retrying: true });
|
|
1323
|
+
return this.command(ns, cmd, newOptions, callback);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// Per SDAM, remove primary from replicaset
|
|
1327
|
+
if (this.s.replicaSetState.primary) {
|
|
1328
|
+
this.s.replicaSetState.remove(this.s.replicaSetState.primary, { force: true });
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
return callback(err);
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
// increment and assign txnNumber
|
|
1335
|
+
if (willRetryWrite) {
|
|
1336
|
+
options.session.incrementTransactionNumber();
|
|
1337
|
+
options.willRetryWrite = willRetryWrite;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// Execute the command
|
|
1341
|
+
server.command(ns, cmd, options, cb);
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
/**
|
|
1345
|
+
* Get a new cursor
|
|
1346
|
+
* @method
|
|
1347
|
+
* @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
|
|
1348
|
+
* @param {object|Long} cmd Can be either a command returning a cursor or a cursorId
|
|
1349
|
+
* @param {object} [options] Options for the cursor
|
|
1350
|
+
* @param {object} [options.batchSize=0] Batchsize for the operation
|
|
1351
|
+
* @param {array} [options.documents=[]] Initial documents list for cursor
|
|
1352
|
+
* @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
|
|
1353
|
+
* @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
|
|
1354
|
+
* @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
|
|
1355
|
+
* @param {ClientSession} [options.session=null] Session to use for the operation
|
|
1356
|
+
* @param {object} [options.topology] The internal topology of the created cursor
|
|
1357
|
+
* @returns {Cursor}
|
|
1358
|
+
*/
|
|
1359
|
+
ReplSet.prototype.cursor = function(ns, cmd, options) {
|
|
1360
|
+
options = options || {};
|
|
1361
|
+
const topology = options.topology || this;
|
|
1362
|
+
|
|
1363
|
+
// Set up final cursor type
|
|
1364
|
+
var FinalCursor = options.cursorFactory || this.s.Cursor;
|
|
1365
|
+
|
|
1366
|
+
// Return the cursor
|
|
1367
|
+
return new FinalCursor(topology, ns, cmd, options);
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
/**
|
|
1371
|
+
* A replset connect event, used to verify that the connection is up and running
|
|
1372
|
+
*
|
|
1373
|
+
* @event ReplSet#connect
|
|
1374
|
+
* @type {ReplSet}
|
|
1375
|
+
*/
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* A replset reconnect event, used to verify that the topology reconnected
|
|
1379
|
+
*
|
|
1380
|
+
* @event ReplSet#reconnect
|
|
1381
|
+
* @type {ReplSet}
|
|
1382
|
+
*/
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
* A replset fullsetup event, used to signal that all topology members have been contacted.
|
|
1386
|
+
*
|
|
1387
|
+
* @event ReplSet#fullsetup
|
|
1388
|
+
* @type {ReplSet}
|
|
1389
|
+
*/
|
|
1390
|
+
|
|
1391
|
+
/**
|
|
1392
|
+
* A replset all event, used to signal that all topology members have been contacted.
|
|
1393
|
+
*
|
|
1394
|
+
* @event ReplSet#all
|
|
1395
|
+
* @type {ReplSet}
|
|
1396
|
+
*/
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
* A replset failed event, used to signal that initial replset connection failed.
|
|
1400
|
+
*
|
|
1401
|
+
* @event ReplSet#failed
|
|
1402
|
+
* @type {ReplSet}
|
|
1403
|
+
*/
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* A server member left the replicaset
|
|
1407
|
+
*
|
|
1408
|
+
* @event ReplSet#left
|
|
1409
|
+
* @type {function}
|
|
1410
|
+
* @param {string} type The type of member that left (primary|secondary|arbiter)
|
|
1411
|
+
* @param {Server} server The server object that left
|
|
1412
|
+
*/
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* A server member joined the replicaset
|
|
1416
|
+
*
|
|
1417
|
+
* @event ReplSet#joined
|
|
1418
|
+
* @type {function}
|
|
1419
|
+
* @param {string} type The type of member that joined (primary|secondary|arbiter)
|
|
1420
|
+
* @param {Server} server The server object that joined
|
|
1421
|
+
*/
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* A server opening SDAM monitoring event
|
|
1425
|
+
*
|
|
1426
|
+
* @event ReplSet#serverOpening
|
|
1427
|
+
* @type {object}
|
|
1428
|
+
*/
|
|
1429
|
+
|
|
1430
|
+
/**
|
|
1431
|
+
* A server closed SDAM monitoring event
|
|
1432
|
+
*
|
|
1433
|
+
* @event ReplSet#serverClosed
|
|
1434
|
+
* @type {object}
|
|
1435
|
+
*/
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* A server description SDAM change monitoring event
|
|
1439
|
+
*
|
|
1440
|
+
* @event ReplSet#serverDescriptionChanged
|
|
1441
|
+
* @type {object}
|
|
1442
|
+
*/
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* A topology open SDAM event
|
|
1446
|
+
*
|
|
1447
|
+
* @event ReplSet#topologyOpening
|
|
1448
|
+
* @type {object}
|
|
1449
|
+
*/
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* A topology closed SDAM event
|
|
1453
|
+
*
|
|
1454
|
+
* @event ReplSet#topologyClosed
|
|
1455
|
+
* @type {object}
|
|
1456
|
+
*/
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* A topology structure SDAM change event
|
|
1460
|
+
*
|
|
1461
|
+
* @event ReplSet#topologyDescriptionChanged
|
|
1462
|
+
* @type {object}
|
|
1463
|
+
*/
|
|
1464
|
+
|
|
1465
|
+
/**
|
|
1466
|
+
* A topology serverHeartbeatStarted SDAM event
|
|
1467
|
+
*
|
|
1468
|
+
* @event ReplSet#serverHeartbeatStarted
|
|
1469
|
+
* @type {object}
|
|
1470
|
+
*/
|
|
1471
|
+
|
|
1472
|
+
/**
|
|
1473
|
+
* A topology serverHeartbeatFailed SDAM event
|
|
1474
|
+
*
|
|
1475
|
+
* @event ReplSet#serverHeartbeatFailed
|
|
1476
|
+
* @type {object}
|
|
1477
|
+
*/
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* A topology serverHeartbeatSucceeded SDAM change event
|
|
1481
|
+
*
|
|
1482
|
+
* @event ReplSet#serverHeartbeatSucceeded
|
|
1483
|
+
* @type {object}
|
|
1484
|
+
*/
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* An event emitted indicating a command was started, if command monitoring is enabled
|
|
1488
|
+
*
|
|
1489
|
+
* @event ReplSet#commandStarted
|
|
1490
|
+
* @type {object}
|
|
1491
|
+
*/
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* An event emitted indicating a command succeeded, if command monitoring is enabled
|
|
1495
|
+
*
|
|
1496
|
+
* @event ReplSet#commandSucceeded
|
|
1497
|
+
* @type {object}
|
|
1498
|
+
*/
|
|
1499
|
+
|
|
1500
|
+
/**
|
|
1501
|
+
* An event emitted indicating a command failed, if command monitoring is enabled
|
|
1502
|
+
*
|
|
1503
|
+
* @event ReplSet#commandFailed
|
|
1504
|
+
* @type {object}
|
|
1505
|
+
*/
|
|
1506
|
+
|
|
1507
|
+
module.exports = ReplSet;
|