Skip to content

Commit e8d7547

Browse files
committed
refactor: introduce makeStateMachine, reuse for all fsms
This patch ensures that we use the same method for building state machines across all modern (read: unified topology) classes. NODE-1517
1 parent 9065953 commit e8d7547

File tree

4 files changed

+76
-69
lines changed

4 files changed

+76
-69
lines changed

lib/core/connection/pool.js

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,20 @@ const Buffer = require('safe-buffer').Buffer;
2020
const connect = require('./connect');
2121
const updateSessionFromResponse = require('../sessions').updateSessionFromResponse;
2222
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';
23+
const makeStateMachine = require('../utils').makeStateMachine;
24+
25+
const DISCONNECTED = 'disconnected';
26+
const CONNECTING = 'connecting';
27+
const CONNECTED = 'connected';
28+
const DESTROYING = 'destroying';
29+
const DESTROYED = 'destroyed';
30+
const stateTransition = makeStateMachine({
31+
[DISCONNECTED]: [CONNECTING, DESTROYING, DISCONNECTED],
32+
[CONNECTING]: [CONNECTING, DESTROYING, CONNECTED, DISCONNECTED],
33+
[CONNECTED]: [CONNECTED, DISCONNECTED, DESTROYING],
34+
[DESTROYING]: [DESTROYING, DESTROYED],
35+
[DESTROYED]: [DESTROYED]
36+
});
2937

3038
const CONNECTION_EVENTS = new Set([
3139
'error',
@@ -80,6 +88,10 @@ var Pool = function(topology, options) {
8088
// Store topology for later use
8189
this.topology = topology;
8290

91+
this.s = {
92+
state: DISCONNECTED
93+
};
94+
8395
// Add the options
8496
this.options = Object.assign(
8597
{
@@ -138,8 +150,6 @@ var Pool = function(topology, options) {
138150

139151
// Logger instance
140152
this.logger = Logger('Pool', options);
141-
// Pool state
142-
this.state = DISCONNECTED;
143153
// Connections
144154
this.availableConnections = [];
145155
this.inUseConnections = [];
@@ -208,6 +218,13 @@ Object.defineProperty(Pool.prototype, 'socketTimeout', {
208218
}
209219
});
210220

221+
Object.defineProperty(Pool.prototype, 'state', {
222+
enumerable: true,
223+
get: function() {
224+
return this.s.state;
225+
}
226+
});
227+
211228
// clears all pool state
212229
function resetPoolState(pool) {
213230
pool.inUseConnections = [];
@@ -220,33 +237,6 @@ function resetPoolState(pool) {
220237
pool.reconnectId = null;
221238
}
222239

223-
function stateTransition(self, newState) {
224-
var legalTransitions = {
225-
disconnected: [CONNECTING, DESTROYING, DISCONNECTED],
226-
connecting: [CONNECTING, DESTROYING, CONNECTED, DISCONNECTED],
227-
connected: [CONNECTED, DISCONNECTED, DESTROYING],
228-
destroying: [DESTROYING, DESTROYED],
229-
destroyed: [DESTROYED]
230-
};
231-
232-
// Get current state
233-
var legalStates = legalTransitions[self.state];
234-
if (legalStates && legalStates.indexOf(newState) !== -1) {
235-
self.emit('stateChanged', self.state, newState);
236-
self.state = newState;
237-
} else {
238-
self.logger.error(
239-
f(
240-
'Pool with id [%s] failed attempted illegal state transition from [%s] to [%s] only following state allowed [%s]',
241-
self.id,
242-
self.state,
243-
newState,
244-
legalStates
245-
)
246-
);
247-
}
248-
}
249-
250240
function connectionFailureHandler(pool, event, err, conn) {
251241
if (conn) {
252242
if (conn._connectionFailHandled) return;

lib/core/sdam/server.js

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const MongoNetworkError = require('../error').MongoNetworkError;
1515
const collationNotSupported = require('../utils').collationNotSupported;
1616
const debugOptions = require('../connection/utils').debugOptions;
1717
const isSDAMUnrecoverableError = require('../error').isSDAMUnrecoverableError;
18+
const makeStateMachine = require('../utils').makeStateMachine;
1819

1920
// Used for filtering out fields for logging
2021
const DEBUG_FIELDS = [
@@ -44,10 +45,16 @@ const DEBUG_FIELDS = [
4445
'servername'
4546
];
4647

47-
const STATE_DISCONNECTING = 'disconnecting';
48-
const STATE_DISCONNECTED = 'disconnected';
48+
const STATE_CLOSING = 'closing';
49+
const STATE_CLOSED = 'closed';
4950
const STATE_CONNECTING = 'connecting';
5051
const STATE_CONNECTED = 'connected';
52+
const stateTransition = makeStateMachine({
53+
[STATE_CLOSED]: [STATE_CLOSED, STATE_CONNECTING],
54+
[STATE_CONNECTING]: [STATE_CONNECTING, STATE_CLOSING, STATE_CONNECTED, STATE_CLOSED],
55+
[STATE_CONNECTED]: [STATE_CONNECTED, STATE_CLOSING, STATE_CLOSED],
56+
[STATE_CLOSING]: [STATE_CLOSING, STATE_CLOSED]
57+
});
5158

5259
/**
5360
*
@@ -100,7 +107,7 @@ class Server extends EventEmitter {
100107
// the connection pool
101108
pool: null,
102109
// the server state
103-
state: STATE_DISCONNECTED,
110+
state: STATE_CLOSED,
104111
credentials: options.credentials,
105112
topology
106113
};
@@ -161,7 +168,7 @@ class Server extends EventEmitter {
161168
// relay all command monitoring events
162169
relayEvents(this.s.pool, this, ['commandStarted', 'commandSucceeded', 'commandFailed']);
163170

164-
this.s.state = STATE_CONNECTING;
171+
stateTransition(this, STATE_CONNECTING);
165172

166173
// If auth settings have been provided, use them
167174
if (options.auth) {
@@ -180,11 +187,19 @@ class Server extends EventEmitter {
180187
destroy(options, callback) {
181188
if (typeof options === 'function') (callback = options), (options = {});
182189
options = Object.assign({}, { force: false }, options);
190+
if (this.s.state === STATE_CLOSED) {
191+
if (typeof callback === 'function') {
192+
callback();
193+
}
194+
195+
return;
196+
}
197+
198+
stateTransition(this, STATE_CLOSING);
183199

184-
this.s.state = STATE_DISCONNECTING;
185200
const done = err => {
201+
stateTransition(this, STATE_CLOSED);
186202
this.emit('closed');
187-
this.s.state = STATE_DISCONNECTED;
188203
if (typeof callback === 'function') {
189204
callback(err, null);
190205
}
@@ -482,12 +497,12 @@ function connectEventHandler(server) {
482497
);
483498
}
484499

485-
// emit an event indicating that our description has changed
486-
server.emit('descriptionReceived', new ServerDescription(server.description.address, ismaster));
487-
488500
// we are connected and handshaked (guaranteed by the pool)
489-
server.s.state = STATE_CONNECTED;
501+
stateTransition(server, STATE_CONNECTED);
490502
server.emit('connect', server);
503+
504+
// emit an event indicating that our description has changed
505+
server.emit('descriptionReceived', new ServerDescription(server.description.address, ismaster));
491506
};
492507
}
493508

@@ -503,7 +518,7 @@ function errorEventHandler(server) {
503518

504519
function parseErrorEventHandler(server) {
505520
return function(err) {
506-
server.s.state = STATE_DISCONNECTED;
521+
stateTransition(this, STATE_CLOSED);
507522
server.emit('error', new MongoParseError(err));
508523
};
509524
}

lib/core/sdam/topology.js

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const MongoError = require('../error').MongoError;
2525
const resolveClusterTime = require('../topologies/shared').resolveClusterTime;
2626
const SrvPoller = require('./srv_polling').SrvPoller;
2727
const getMMAPError = require('../topologies/shared').getMMAPError;
28+
const makeStateMachine = require('../utils').makeStateMachine;
2829

2930
// Global state
3031
let globalTopologyCounter = 0;
@@ -63,28 +64,12 @@ const STATE_CLOSING = 'closing';
6364
const STATE_CLOSED = 'closed';
6465
const STATE_CONNECTING = 'connecting';
6566
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-
}
67+
const stateTransition = makeStateMachine({
68+
[STATE_CLOSED]: [STATE_CLOSED, STATE_CONNECTING],
69+
[STATE_CONNECTING]: [STATE_CONNECTING, STATE_CLOSING, STATE_CONNECTED, STATE_CLOSED],
70+
[STATE_CONNECTED]: [STATE_CONNECTED, STATE_CLOSING, STATE_CLOSED],
71+
[STATE_CLOSING]: [STATE_CLOSING, STATE_CLOSED]
72+
});
8873

8974
/**
9075
* A container of server instances representing a connection to a MongoDB topology.

lib/core/utils.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,22 @@ function tagsStrictEqual(tags, tags2) {
177177
return tagsKeys.length === tags2Keys.length && tagsKeys.every(key => tags2[key] === tags[key]);
178178
}
179179

180+
function makeStateMachine(stateTable) {
181+
return function stateTransition(target, newState) {
182+
const legalStates = stateTable[target.s.state];
183+
if (legalStates && legalStates.indexOf(newState) < 0) {
184+
throw new TypeError(
185+
`illegal state transition from [${
186+
target.s.state
187+
}] => [${newState}], allowed: [${legalStates}]`
188+
);
189+
}
190+
191+
target.emit('stateChanged', target.s.state, newState);
192+
target.s.state = newState;
193+
};
194+
}
195+
180196
module.exports = {
181197
uuidV4,
182198
calculateDurationInMs,
@@ -189,5 +205,6 @@ module.exports = {
189205
eachAsync,
190206
isUnifiedTopology,
191207
arrayStrictEqual,
192-
tagsStrictEqual
208+
tagsStrictEqual,
209+
makeStateMachine
193210
};

0 commit comments

Comments
 (0)