Skip to content

Commit 9baec71

Browse files
authored
fix: session support detection spec compliance (#2732)
1 parent e8ac558 commit 9baec71

File tree

3 files changed

+130
-26
lines changed

3 files changed

+130
-26
lines changed

lib/core/sdam/topology_description.js

+24-6
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,30 @@ class TopologyDescription {
7272
// value among ServerDescriptions of all data-bearing server types. If any have a null
7373
// logicalSessionTimeoutMinutes, then TopologyDescription.logicalSessionTimeoutMinutes MUST be
7474
// set to null.
75-
const readableServers = Array.from(this.servers.values()).filter(s => s.isReadable);
76-
this.logicalSessionTimeoutMinutes = readableServers.reduce((result, server) => {
77-
if (server.logicalSessionTimeoutMinutes == null) return null;
78-
if (result == null) return server.logicalSessionTimeoutMinutes;
79-
return Math.min(result, server.logicalSessionTimeoutMinutes);
80-
}, null);
75+
this.logicalSessionTimeoutMinutes = null;
76+
for (const addressServerTuple of this.servers) {
77+
const server = addressServerTuple[1];
78+
if (server.isReadable) {
79+
if (server.logicalSessionTimeoutMinutes == null) {
80+
// If any of the servers have a null logicalSessionsTimeout, then the whole topology does
81+
this.logicalSessionTimeoutMinutes = null;
82+
break;
83+
}
84+
85+
if (this.logicalSessionTimeoutMinutes == null) {
86+
// First server with a non null logicalSessionsTimeout
87+
this.logicalSessionTimeoutMinutes = server.logicalSessionTimeoutMinutes;
88+
continue;
89+
}
90+
91+
// Always select the smaller of the:
92+
// current server logicalSessionsTimeout and the topologies logicalSessionsTimeout
93+
this.logicalSessionTimeoutMinutes = Math.min(
94+
this.logicalSessionTimeoutMinutes,
95+
server.logicalSessionTimeoutMinutes
96+
);
97+
}
98+
}
8199
}
82100

83101
/**

test/unit/core/common.js

+39-20
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,39 @@ class ReplSetFixture {
1111
this.electionIds = [new ObjectId(), new ObjectId()];
1212
}
1313

14+
uri(dbName) {
15+
return `mongodb://${this.primaryServer.uri()},${this.firstSecondaryServer.uri()},${this.secondSecondaryServer.uri()}/${dbName ||
16+
'test'}?replicaSet=rs`;
17+
}
18+
1419
setup(options) {
1520
options = options || {};
1621
const ismaster = options.ismaster ? options.ismaster : mock.DEFAULT_ISMASTER_36;
1722

18-
return Promise.all([mock.createServer(), mock.createServer(), mock.createServer()]).then(
19-
servers => {
20-
this.servers = servers;
21-
this.primaryServer = servers[0];
22-
this.firstSecondaryServer = servers[1];
23-
this.arbiterServer = servers[2];
24-
25-
this.defaultFields = Object.assign({}, ismaster, {
26-
__nodejs_mock_server__: true,
27-
setName: 'rs',
28-
setVersion: 1,
29-
electionId: this.electionIds[0],
30-
hosts: this.servers.map(server => server.uri()),
31-
arbiters: [this.arbiterServer.uri()]
32-
});
33-
34-
this.defineReplSetStates();
35-
this.configureMessageHandlers();
36-
}
37-
);
23+
return Promise.all([
24+
mock.createServer(),
25+
mock.createServer(),
26+
mock.createServer(),
27+
mock.createServer()
28+
]).then(servers => {
29+
this.servers = servers;
30+
this.primaryServer = servers[0];
31+
this.firstSecondaryServer = servers[1];
32+
this.secondSecondaryServer = servers[2];
33+
this.arbiterServer = servers[3];
34+
35+
this.defaultFields = Object.assign({}, ismaster, {
36+
__nodejs_mock_server__: true,
37+
setName: 'rs',
38+
setVersion: 1,
39+
electionId: this.electionIds[0],
40+
hosts: this.servers.map(server => server.uri()),
41+
arbiters: [this.arbiterServer.uri()]
42+
});
43+
44+
if (!options.doNotInitStates) this.defineReplSetStates();
45+
if (!options.doNotInitHandlers) this.configureMessageHandlers();
46+
});
3847
}
3948

4049
defineReplSetStates() {
@@ -58,6 +67,16 @@ class ReplSetFixture {
5867
})
5968
];
6069

70+
this.secondSecondaryStates = [
71+
Object.assign({}, this.defaultFields, {
72+
ismaster: false,
73+
secondary: true,
74+
me: this.secondSecondaryServer.uri(),
75+
primary: this.primaryServer.uri(),
76+
tags: { loc: 'la' }
77+
})
78+
];
79+
6180
this.arbiterStates = [
6281
Object.assign({}, this.defaultFields, {
6382
ismaster: false,

test/unit/sessions/client.test.js

+67
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const expect = require('chai').expect;
44
const mock = require('mongodb-mock-server');
5+
const ReplSetFixture = require('../core/common').ReplSetFixture;
56

67
const test = {};
78
describe('Sessions', function() {
@@ -37,6 +38,72 @@ describe('Sessions', function() {
3738
}
3839
});
3940

41+
it('should throw an exception if sessions are not supported on some servers', {
42+
metadata: { requires: { topology: 'single' } },
43+
test() {
44+
const replicaSetMock = new ReplSetFixture();
45+
return replicaSetMock
46+
.setup({ doNotInitHandlers: true })
47+
.then(() => {
48+
replicaSetMock.firstSecondaryServer.setMessageHandler(request => {
49+
var doc = request.document;
50+
if (doc.ismaster) {
51+
const ismaster = replicaSetMock.firstSecondaryStates[0];
52+
ismaster.logicalSessionTimeoutMinutes = 20;
53+
request.reply(ismaster);
54+
} else if (doc.endSessions) {
55+
request.reply({ ok: 1 });
56+
}
57+
});
58+
59+
replicaSetMock.secondSecondaryServer.setMessageHandler(request => {
60+
var doc = request.document;
61+
if (doc.ismaster) {
62+
const ismaster = replicaSetMock.secondSecondaryStates[0];
63+
ismaster.logicalSessionTimeoutMinutes = 10;
64+
request.reply(ismaster);
65+
} else if (doc.endSessions) {
66+
request.reply({ ok: 1 });
67+
}
68+
});
69+
70+
replicaSetMock.arbiterServer.setMessageHandler(request => {
71+
var doc = request.document;
72+
if (doc.ismaster) {
73+
const ismaster = replicaSetMock.arbiterStates[0];
74+
ismaster.logicalSessionTimeoutMinutes = 30;
75+
request.reply(ismaster);
76+
} else if (doc.endSessions) {
77+
request.reply({ ok: 1 });
78+
}
79+
});
80+
81+
replicaSetMock.primaryServer.setMessageHandler(request => {
82+
var doc = request.document;
83+
if (doc.ismaster) {
84+
const ismaster = replicaSetMock.primaryStates[0];
85+
ismaster.logicalSessionTimeoutMinutes = null;
86+
request.reply(ismaster);
87+
} else if (doc.endSessions) {
88+
request.reply({ ok: 1 });
89+
}
90+
});
91+
92+
return replicaSetMock.uri();
93+
})
94+
.then(uri => {
95+
const client = this.configuration.newClient(uri);
96+
return client.connect();
97+
})
98+
.then(client => {
99+
expect(client.topology.s.description.logicalSessionTimeoutMinutes).to.not.exist;
100+
expect(() => {
101+
client.startSession();
102+
}).to.throw(/Current topology does not support sessions/);
103+
return client.close();
104+
});
105+
}
106+
});
40107
it('should return a client session when requested if the topology supports it', {
41108
metadata: { requires: { topology: 'single' } },
42109

0 commit comments

Comments
 (0)