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