Skip to content

Commit 1615be0

Browse files
authored
fix: session support detection spec compliance [PORT: 4.0] (#2733)
1 parent d0f798d commit 1615be0

File tree

3 files changed

+133
-32
lines changed

3 files changed

+133
-32
lines changed

src/sdam/topology_description.ts

+23-12
Original file line numberDiff line numberDiff line change
@@ -95,18 +95,29 @@ export class TopologyDescription {
9595
// value among ServerDescriptions of all data-bearing server types. If any have a null
9696
// logicalSessionTimeoutMinutes, then TopologyDescription.logicalSessionTimeoutMinutes MUST be
9797
// set to null.
98-
const readableServers = Array.from(this.servers.values()).filter(
99-
(s: ServerDescription) => s.isReadable
100-
);
101-
102-
this.logicalSessionTimeoutMinutes = readableServers.reduce(
103-
(result: number | undefined, server: ServerDescription) => {
104-
if (server.logicalSessionTimeoutMinutes == null) return;
105-
if (result == null) return server.logicalSessionTimeoutMinutes;
106-
return Math.min(result, server.logicalSessionTimeoutMinutes);
107-
},
108-
undefined
109-
);
98+
this.logicalSessionTimeoutMinutes = undefined;
99+
for (const [, server] of this.servers) {
100+
if (server.isReadable) {
101+
if (server.logicalSessionTimeoutMinutes == null) {
102+
// If any of the servers have a null logicalSessionsTimeout, then the whole topology does
103+
this.logicalSessionTimeoutMinutes = undefined;
104+
break;
105+
}
106+
107+
if (this.logicalSessionTimeoutMinutes === undefined) {
108+
// First server with a non null logicalSessionsTimeout
109+
this.logicalSessionTimeoutMinutes = server.logicalSessionTimeoutMinutes;
110+
continue;
111+
}
112+
113+
// Always select the smaller of the:
114+
// current server logicalSessionsTimeout and the topologies logicalSessionsTimeout
115+
this.logicalSessionTimeoutMinutes = Math.min(
116+
this.logicalSessionTimeoutMinutes,
117+
server.logicalSessionTimeoutMinutes
118+
);
119+
}
120+
}
110121
}
111122

112123
/**

test/unit/core/common.js

+40-20
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,40 @@ class ReplSetFixture {
88
this.electionIds = [new ObjectId(), new ObjectId()];
99
}
1010

11+
uri(dbName) {
12+
return `mongodb://${this.primaryServer.uri()},${this.firstSecondaryServer.uri()},${this.secondSecondaryServer.uri()}/${
13+
dbName || 'test'
14+
}?replicaSet=rs`;
15+
}
16+
1117
setup(options) {
1218
options = options || {};
1319
const ismaster = options.ismaster ? options.ismaster : mock.DEFAULT_ISMASTER_36;
1420

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

3747
defineReplSetStates() {
@@ -55,6 +65,16 @@ class ReplSetFixture {
5565
})
5666
];
5767

68+
this.secondSecondaryStates = [
69+
Object.assign({}, this.defaultFields, {
70+
ismaster: false,
71+
secondary: true,
72+
me: this.secondSecondaryServer.uri(),
73+
primary: this.primaryServer.uri(),
74+
tags: { loc: 'la' }
75+
})
76+
];
77+
5878
this.arbiterStates = [
5979
Object.assign({}, this.defaultFields, {
6080
ismaster: false,

test/unit/sessions/client.test.js

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

33
const expect = require('chai').expect;
44
const mock = require('../../tools/mock');
5+
const { ReplSetFixture } = require('../core/common');
56

67
const test = {};
78
describe('Sessions - client/unit', function () {
@@ -37,6 +38,75 @@ describe('Sessions - client/unit', 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+
let testClient;
46+
return replicaSetMock
47+
.setup({ doNotInitHandlers: true })
48+
.then(() => {
49+
replicaSetMock.firstSecondaryServer.setMessageHandler(request => {
50+
var doc = request.document;
51+
if (doc.ismaster) {
52+
const ismaster = replicaSetMock.firstSecondaryStates[0];
53+
ismaster.logicalSessionTimeoutMinutes = 20;
54+
request.reply(ismaster);
55+
} else if (doc.endSessions) {
56+
request.reply({ ok: 1 });
57+
}
58+
});
59+
60+
replicaSetMock.secondSecondaryServer.setMessageHandler(request => {
61+
var doc = request.document;
62+
if (doc.ismaster) {
63+
const ismaster = replicaSetMock.secondSecondaryStates[0];
64+
ismaster.logicalSessionTimeoutMinutes = 10;
65+
request.reply(ismaster);
66+
} else if (doc.endSessions) {
67+
request.reply({ ok: 1 });
68+
}
69+
});
70+
71+
replicaSetMock.arbiterServer.setMessageHandler(request => {
72+
var doc = request.document;
73+
if (doc.ismaster) {
74+
const ismaster = replicaSetMock.arbiterStates[0];
75+
ismaster.logicalSessionTimeoutMinutes = 30;
76+
request.reply(ismaster);
77+
} else if (doc.endSessions) {
78+
request.reply({ ok: 1 });
79+
}
80+
});
81+
82+
replicaSetMock.primaryServer.setMessageHandler(request => {
83+
var doc = request.document;
84+
if (doc.ismaster) {
85+
const ismaster = replicaSetMock.primaryStates[0];
86+
ismaster.logicalSessionTimeoutMinutes = null;
87+
request.reply(ismaster);
88+
} else if (doc.endSessions) {
89+
request.reply({ ok: 1 });
90+
}
91+
});
92+
93+
return replicaSetMock.uri();
94+
})
95+
.then(uri => {
96+
const client = this.configuration.newClient(uri);
97+
return client.connect();
98+
})
99+
.then(client => {
100+
testClient = client;
101+
expect(client.topology.s.description.logicalSessionTimeoutMinutes).to.not.exist;
102+
expect(() => {
103+
client.startSession();
104+
}).to.throw(/Current topology does not support sessions/);
105+
})
106+
.finally(() => (testClient ? testClient.close() : null));
107+
}
108+
});
109+
40110
it('should return a client session when requested if the topology supports it', {
41111
metadata: { requires: { topology: 'single' } },
42112

0 commit comments

Comments
 (0)