@@ -59,6 +59,33 @@ const LOCAL_SERVER_EVENTS = SERVER_RELAY_EVENTS.concat([
59
59
'ended'
60
60
] ) ;
61
61
62
+ const STATE_CLOSING = 'closing' ;
63
+ const STATE_CLOSED = 'closed' ;
64
+ const STATE_CONNECTING = 'connecting' ;
65
+ const STATE_CONNECTED = 'connected' ;
66
+
67
+ function stateTransition ( topology , newState ) {
68
+ const legalTransitions = {
69
+ [ STATE_CLOSED ] : [ STATE_CLOSED , STATE_CONNECTING ] ,
70
+ [ STATE_CONNECTING ] : [ STATE_CONNECTING , STATE_CLOSING , STATE_CONNECTED , STATE_CLOSED ] ,
71
+ [ STATE_CONNECTED ] : [ STATE_CONNECTED , STATE_CLOSING , STATE_CLOSED ] ,
72
+ [ STATE_CLOSING ] : [ STATE_CLOSING , STATE_CLOSED ]
73
+ } ;
74
+
75
+ const legalStates = legalTransitions [ topology . s . state ] ;
76
+ if ( legalStates && legalStates . indexOf ( newState ) < 0 ) {
77
+ console . log ( 'throwing an error' ) ;
78
+ throw new MongoError (
79
+ `illegal state transition from [${
80
+ topology . s . state
81
+ } ] => [${ newState } ], allowed: [${ legalStates } ]`
82
+ ) ;
83
+ }
84
+
85
+ topology . emit ( 'stateChanged' , topology . s . state , newState ) ;
86
+ topology . s . state = newState ;
87
+ }
88
+
62
89
/**
63
90
* A container of server instances representing a connection to a MongoDB topology.
64
91
*
@@ -117,6 +144,8 @@ class Topology extends EventEmitter {
117
144
options,
118
145
// initial seedlist of servers to connect to
119
146
seedlist : seedlist ,
147
+ // initial state
148
+ state : STATE_CLOSED ,
120
149
// the topology description
121
150
description : new TopologyDescription (
122
151
topologyType ,
@@ -229,6 +258,15 @@ class Topology extends EventEmitter {
229
258
connect ( options , callback ) {
230
259
if ( typeof options === 'function' ) ( callback = options ) , ( options = { } ) ;
231
260
options = options || { } ;
261
+ if ( this . s . state === STATE_CONNECTED ) {
262
+ if ( typeof callback === 'function' ) {
263
+ callback ( ) ;
264
+ }
265
+
266
+ return ;
267
+ }
268
+
269
+ stateTransition ( this , STATE_CONNECTING ) ;
232
270
233
271
// emit SDAM monitoring events
234
272
this . emit ( 'topologyOpening' , new monitoring . TopologyOpeningEvent ( this . s . id ) ) ;
@@ -243,17 +281,15 @@ class Topology extends EventEmitter {
243
281
)
244
282
) ;
245
283
284
+ // connect all known servers, then attempt server selection to connect
246
285
connectServers ( this , Array . from ( this . s . description . servers . values ( ) ) ) ;
247
- this . s . connected = true ;
248
-
249
- // otherwise, wait for a server to properly connect based on user provided read preference,
250
- // or primary.
251
286
252
287
translateReadPreference ( options ) ;
253
288
const readPreference = options . readPreference || ReadPreference . primary ;
254
289
255
290
this . selectServer ( readPreferenceServerSelector ( readPreference ) , options , ( err , server ) => {
256
291
if ( err ) {
292
+ stateTransition ( this , STATE_CLOSED ) ;
257
293
if ( typeof callback === 'function' ) {
258
294
callback ( err , null ) ;
259
295
} else {
@@ -264,11 +300,13 @@ class Topology extends EventEmitter {
264
300
}
265
301
266
302
const errorHandler = err => {
303
+ stateTransition ( this , STATE_CLOSED ) ;
267
304
server . removeListener ( 'connect' , connectHandler ) ;
268
305
if ( typeof callback === 'function' ) callback ( err , null ) ;
269
306
} ;
270
307
271
308
const connectHandler = ( _ , err ) => {
309
+ stateTransition ( this , STATE_CONNECTED ) ;
272
310
server . removeListener ( 'error' , errorHandler ) ;
273
311
this . emit ( 'open' , err , this ) ;
274
312
this . emit ( 'connect' , this ) ;
@@ -301,6 +339,13 @@ class Topology extends EventEmitter {
301
339
}
302
340
303
341
options = options || { } ;
342
+ if ( this . s . state === STATE_CLOSED ) {
343
+ if ( typeof callback === 'function' ) {
344
+ callback ( ) ;
345
+ }
346
+
347
+ return ;
348
+ }
304
349
305
350
// clear all existing monitor timers
306
351
this . s . monitorTimers . map ( timer => clearTimeout ( timer ) ) ;
@@ -327,9 +372,12 @@ class Topology extends EventEmitter {
327
372
delete this . s . detectTopologyDescriptionChange ;
328
373
}
329
374
375
+ // defer state transition because we may need to send an `endSessions` command above
376
+ stateTransition ( this , STATE_CLOSING ) ;
377
+
330
378
const servers = this . s . servers ;
331
379
if ( servers . size === 0 ) {
332
- this . s . connected = false ;
380
+ stateTransition ( this , STATE_CLOSED ) ;
333
381
if ( typeof callback === 'function' ) {
334
382
callback ( null , null ) ;
335
383
}
@@ -346,7 +394,7 @@ class Topology extends EventEmitter {
346
394
// emit an event for close
347
395
this . emit ( 'topologyClosed' , new monitoring . TopologyClosedEvent ( this . s . id ) ) ;
348
396
349
- this . s . connected = false ;
397
+ stateTransition ( this , STATE_CLOSED ) ;
350
398
if ( typeof callback === 'function' ) {
351
399
callback ( null , null ) ;
352
400
}
@@ -807,7 +855,7 @@ function selectServers(topology, selector, timeout, start, callback) {
807
855
}
808
856
809
857
// ensure we are connected
810
- if ( ! topology . s . connected ) {
858
+ if ( topology . s . state !== STATE_CONNECTED && topology . s . state !== STATE_CONNECTING ) {
811
859
topology . connect ( ) ;
812
860
813
861
// we want to make sure we're still within the requested timeout window
0 commit comments