Skip to content

Commit aaa453d

Browse files
authored
fix(NODE-3521): update session support checks (#3151)
1 parent ade5ec2 commit aaa453d

File tree

5 files changed

+261
-4
lines changed

5 files changed

+261
-4
lines changed

src/cursor/abstract_cursor.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,15 @@ function next<T>(cursor: AbstractCursor, blocking: boolean, callback: Callback<T
653653

654654
if (cursorId == null) {
655655
// All cursors must operate within a session, one must be made implicitly if not explicitly provided
656-
if (cursor[kSession] == null && cursor[kTopology].hasSessionSupport()) {
657-
cursor[kSession] = cursor[kTopology].startSession({ owner: cursor, explicit: false });
656+
if (cursor[kSession] == null) {
657+
if (cursor[kTopology].shouldCheckForSessionSupport()) {
658+
return cursor[kTopology].selectServer(ReadPreference.primaryPreferred, err => {
659+
if (err) return callback(err);
660+
return next(cursor, blocking, callback);
661+
});
662+
} else if (cursor[kTopology].hasSessionSupport()) {
663+
cursor[kSession] = cursor[kTopology].startSession({ owner: cursor, explicit: false });
664+
}
658665
}
659666

660667
cursor._initialize(cursor[kSession], (err, state) => {

src/cursor/aggregation_cursor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export class AggregationCursor<TSchema = Document> extends AbstractCursor<TSchem
6161
}
6262

6363
/** @internal */
64-
_initialize(session: ClientSession | undefined, callback: Callback<ExecutionResult>): void {
64+
_initialize(session: ClientSession, callback: Callback<ExecutionResult>): void {
6565
const aggregateOperation = new AggregateOperation(this.namespace, this[kPipeline], {
6666
...this[kOptions],
6767
...this.cursorOptions,

src/cursor/find_cursor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export class FindCursor<TSchema = Document> extends AbstractCursor<TSchema> {
6868
}
6969

7070
/** @internal */
71-
_initialize(session: ClientSession | undefined, callback: Callback<ExecutionResult>): void {
71+
_initialize(session: ClientSession, callback: Callback<ExecutionResult>): void {
7272
const findOperation = new FindOperation(undefined, this.namespace, this[kFilter], {
7373
...this[kBuiltOptions], // NOTE: order matters here, we may need to refine this
7474
...this.cursorOptions,
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
'use strict';
2+
3+
const { expect } = require('chai');
4+
const mock = require('../../tools/mongodb-mock/index');
5+
const { Topology } = require('../../../src/sdam/topology');
6+
const { Long } = require('bson');
7+
const { MongoDBNamespace, isHello } = require('../../../src/utils');
8+
const { AggregationCursor } = require('../../../src/cursor/aggregation_cursor');
9+
10+
const test = {};
11+
describe('Aggregation Cursor', function () {
12+
describe('#next', function () {
13+
afterEach(function () {
14+
mock.cleanup();
15+
});
16+
beforeEach(async function () {
17+
test.server = await mock.createServer();
18+
});
19+
20+
context('when there is a data bearing server', function () {
21+
beforeEach(function () {
22+
test.server.setMessageHandler(request => {
23+
const doc = request.document;
24+
if (isHello(doc)) {
25+
request.reply(mock.HELLO);
26+
} else if (doc.aggregate) {
27+
request.reply({
28+
cursor: {
29+
id: Long.fromNumber(1),
30+
ns: 'test.test',
31+
firstBatch: [{ _id: 1, name: 'test' }]
32+
},
33+
ok: 1
34+
});
35+
}
36+
});
37+
});
38+
39+
it('sets the session on the cursor', function (done) {
40+
const topology = new Topology(test.server.hostAddress());
41+
const cursor = new AggregationCursor(
42+
topology,
43+
MongoDBNamespace.fromString('test.test'),
44+
[],
45+
{}
46+
);
47+
topology.connect(function () {
48+
cursor.next(function () {
49+
expect(cursor.session).to.exist;
50+
topology.close(done);
51+
});
52+
});
53+
});
54+
});
55+
56+
context('when there is no data bearing server', function () {
57+
beforeEach(function () {
58+
test.server.setMessageHandler(request => {
59+
const doc = request.document;
60+
if (isHello(doc)) {
61+
request.reply({ errmsg: 'network error' });
62+
} else if (doc.aggregate) {
63+
request.reply({
64+
cursor: {
65+
id: Long.fromNumber(1),
66+
ns: 'test.test',
67+
firstBatch: [{ _id: 1, name: 'test' }]
68+
},
69+
ok: 1
70+
});
71+
}
72+
});
73+
});
74+
75+
it('does not set the session on the cursor', function (done) {
76+
const topology = new Topology(test.server.hostAddress(), {
77+
serverSelectionTimeoutMS: 1000
78+
});
79+
const cursor = new AggregationCursor(
80+
topology,
81+
MongoDBNamespace.fromString('test.test'),
82+
[],
83+
{}
84+
);
85+
topology.connect(function () {
86+
cursor.next(function () {
87+
expect(cursor.session).to.not.exist;
88+
topology.close(done);
89+
});
90+
});
91+
});
92+
});
93+
94+
context('when a data bearing server becomes available', function () {
95+
beforeEach(function () {
96+
// Set the count of times hello has been called.
97+
let helloCalls = 0;
98+
test.server.setMessageHandler(request => {
99+
const doc = request.document;
100+
if (isHello(doc)) {
101+
// After the first hello call errors indicating no data bearing server is
102+
// available, any subsequent hello call should succeed after server selection.
103+
// This gives us a data bearing server available for the next call.
104+
request.reply(helloCalls > 0 ? mock.HELLO : { errmsg: 'network error' });
105+
helloCalls++;
106+
} else if (doc.aggregate) {
107+
request.reply({
108+
cursor: {
109+
id: Long.fromNumber(1),
110+
ns: 'test.test',
111+
firstBatch: [{ _id: 1, name: 'test' }]
112+
},
113+
ok: 1
114+
});
115+
}
116+
});
117+
});
118+
119+
it('sets the session on the cursor', function (done) {
120+
const topology = new Topology(test.server.hostAddress(), {
121+
serverSelectionTimeoutMS: 1000
122+
});
123+
const cursor = new AggregationCursor(
124+
topology,
125+
MongoDBNamespace.fromString('test.test'),
126+
[],
127+
{}
128+
);
129+
topology.connect(function () {
130+
cursor.next(function () {
131+
expect(cursor.session).to.exist;
132+
topology.close(done);
133+
});
134+
});
135+
});
136+
});
137+
});
138+
});

test/unit/cursor/find_cursor.test.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,118 @@ const { FindCursor } = require('../../../src/cursor/find_cursor');
1010

1111
const test = {};
1212
describe('Find Cursor', function () {
13+
describe('#next', function () {
14+
afterEach(function () {
15+
mock.cleanup();
16+
});
17+
beforeEach(async function () {
18+
test.server = await mock.createServer();
19+
});
20+
21+
context('when there is a data bearing server', function () {
22+
beforeEach(function () {
23+
test.server.setMessageHandler(request => {
24+
const doc = request.document;
25+
if (isHello(doc)) {
26+
request.reply(mock.HELLO);
27+
} else if (doc.find) {
28+
request.reply({
29+
cursor: {
30+
id: Long.fromNumber(1),
31+
ns: 'test.test',
32+
firstBatch: [{ _id: 1, name: 'test' }]
33+
},
34+
ok: 1
35+
});
36+
}
37+
});
38+
});
39+
40+
it('sets the session on the cursor', function (done) {
41+
const topology = new Topology(test.server.hostAddress());
42+
const cursor = new FindCursor(topology, MongoDBNamespace.fromString('test.test'), {}, {});
43+
topology.connect(function () {
44+
cursor.next(function () {
45+
expect(cursor.session).to.exist;
46+
topology.close(done);
47+
});
48+
});
49+
});
50+
});
51+
52+
context('when there is no data bearing server', function () {
53+
beforeEach(function () {
54+
test.server.setMessageHandler(request => {
55+
const doc = request.document;
56+
if (isHello(doc)) {
57+
request.reply({ errmsg: 'network error' });
58+
} else if (doc.find) {
59+
request.reply({
60+
cursor: {
61+
id: Long.fromNumber(1),
62+
ns: 'test.test',
63+
firstBatch: [{ _id: 1, name: 'test' }]
64+
},
65+
ok: 1
66+
});
67+
}
68+
});
69+
});
70+
71+
it('does not set the session on the cursor', function (done) {
72+
const topology = new Topology(test.server.hostAddress(), {
73+
serverSelectionTimeoutMS: 1000
74+
});
75+
const cursor = new FindCursor(topology, MongoDBNamespace.fromString('test.test'), {}, {});
76+
topology.connect(function () {
77+
cursor.next(function () {
78+
expect(cursor.session).to.not.exist;
79+
topology.close(done);
80+
});
81+
});
82+
});
83+
});
84+
85+
context('when a data bearing server becomes available', function () {
86+
beforeEach(function () {
87+
// Set the count of times hello has been called.
88+
let helloCalls = 0;
89+
test.server.setMessageHandler(request => {
90+
const doc = request.document;
91+
if (isHello(doc)) {
92+
// After the first hello call errors indicating no data bearing server is
93+
// available, any subsequent hello call should succeed after server selection.
94+
// This gives us a data bearing server available for the next call.
95+
request.reply(helloCalls > 0 ? mock.HELLO : { errmsg: 'network error' });
96+
helloCalls++;
97+
} else if (doc.find) {
98+
request.reply({
99+
cursor: {
100+
id: Long.fromNumber(1),
101+
ns: 'test.test',
102+
firstBatch: [{ _id: 1, name: 'test' }]
103+
},
104+
ok: 1
105+
});
106+
}
107+
});
108+
});
109+
110+
it('sets the session on the cursor', function (done) {
111+
const topology = new Topology(test.server.hostAddress(), {
112+
serverSelectionTimeoutMS: 1000
113+
});
114+
const cursor = new FindCursor(topology, MongoDBNamespace.fromString('test.test'), {}, {});
115+
topology.connect(function () {
116+
cursor.next(function () {
117+
expect(cursor.session).to.exist;
118+
topology.close(done);
119+
});
120+
});
121+
});
122+
});
123+
});
124+
13125
describe('Response', function () {
14126
afterEach(() => mock.cleanup());
15127
beforeEach(() => {

0 commit comments

Comments
 (0)