Skip to content
This repository was archived by the owner on Feb 4, 2022. It is now read-only.

Commit 7778067

Browse files
committed
feat(retryable-writes): add mongos support for retryable writes
NODE-1105
1 parent 73ac688 commit 7778067

File tree

3 files changed

+113
-11
lines changed

3 files changed

+113
-11
lines changed

lib/topologies/mongos.js

+38-11
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
'use strict';
22

3-
var inherits = require('util').inherits,
3+
const inherits = require('util').inherits,
44
f = require('util').format,
55
EventEmitter = require('events').EventEmitter,
66
BasicCursor = require('../cursor'),
77
Logger = require('../connection/logger'),
88
retrieveBSON = require('../connection/utils').retrieveBSON,
99
MongoError = require('../error').MongoError,
10+
errors = require('../error'),
1011
Server = require('./server'),
1112
clone = require('./shared').clone,
1213
diff = require('./shared').diff,
1314
cloneOptions = require('./shared').cloneOptions,
1415
createClientInfo = require('./shared').createClientInfo,
15-
SessionMixins = require('./shared').SessionMixins;
16+
SessionMixins = require('./shared').SessionMixins,
17+
isRetryableWritesSupported = require('./shared').isRetryableWritesSupported,
18+
txnNumber = require('./shared').txnNumber;
1619

17-
var BSON = retrieveBSON();
20+
const BSON = retrieveBSON();
1821

1922
/**
2023
* @fileOverview The **Mongos** class is a class that represents a Mongos Proxy topology and is
@@ -879,18 +882,42 @@ Mongos.prototype.isDestroyed = function() {
879882

880883
// Execute write operation
881884
var executeWriteOperation = function(self, op, ns, ops, options, callback) {
882-
if (typeof options === 'function') {
883-
(callback = options), (options = {}), (options = options || {});
884-
}
885-
886-
// Ensure we have no options
885+
if (typeof options === 'function') (callback = options), (options = {});
887886
options = options || {};
887+
888888
// Pick a server
889-
var server = pickProxy(self);
889+
let server = pickProxy(self);
890890
// No server found error out
891891
if (!server) return callback(new MongoError('no mongos proxy available'));
892-
// Execute the command
893-
server[op](ns, ops, options, callback);
892+
893+
if (!options.retryWrites || !options.session || !isRetryableWritesSupported(self)) {
894+
// Execute the command
895+
return server[op](ns, ops, options, callback);
896+
}
897+
898+
// increment and assign txnNumber
899+
options.txnNumber = txnNumber(options.session);
900+
901+
server[op](ns, ops, options, (err, result) => {
902+
if (!err) return callback(null, result);
903+
if (err instanceof errors.MongoNetworkError) {
904+
return callback(err);
905+
}
906+
907+
// Pick another server
908+
server = pickProxy(self);
909+
910+
// No server found error out with original error
911+
if (!server) {
912+
return callback(err);
913+
}
914+
915+
// increment and assign txnNumber
916+
options.txnNumber = txnNumber(options.session);
917+
918+
// rerun the operation
919+
server[op](ns, ops, options, callback);
920+
});
894921
};
895922

896923
/**

test/tests/unit/common.js

+14
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ class ReplSetFixture {
9292
}
9393
}
9494

95+
class MongosFixture {
96+
setup(options) {
97+
options = options || {};
98+
const ismaster = options.ismaster ? options.ismaster : mock.DEFAULT_ISMASTER;
99+
return Promise.all([mock.createServer(), mock.createServer()]).then(servers => {
100+
this.servers = servers;
101+
this.defaultFields = Object.assign({}, ismaster, {
102+
msg: 'isdbgrid'
103+
});
104+
});
105+
}
106+
}
107+
95108
/**
96109
* Creates a cluster time for use in unit testing cluster time gossiping and
97110
* causal consistency.
@@ -111,5 +124,6 @@ function genClusterTime(time) {
111124

112125
module.exports = {
113126
ReplSetFixture: ReplSetFixture,
127+
MongosFixture: MongosFixture,
114128
genClusterTime: genClusterTime
115129
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use strict';
2+
var expect = require('chai').expect,
3+
Mongos = require('../../../../lib/topologies/mongos'),
4+
mock = require('../../../mock'),
5+
MongosFixture = require('../common').MongosFixture,
6+
ClientSession = require('../../../../lib/sessions').ClientSession,
7+
ServerSessionPool = require('../../../../lib/sessions').ServerSessionPool;
8+
9+
const test = new MongosFixture();
10+
describe('Retryable Writes (Mongos)', function() {
11+
afterEach(() => mock.cleanup());
12+
beforeEach(() => test.setup({ ismaster: mock.DEFAULT_ISMASTER_36 }));
13+
14+
it('should add `txnNumber` to write commands where `retryWrites` is true', {
15+
metadata: { requires: { topology: ['single'] } },
16+
test: function(done) {
17+
const topology = new Mongos(test.servers.map(server => server.address()), {
18+
connectionTimeout: 3000,
19+
socketTimeout: 0,
20+
haInterval: 10000,
21+
localThresholdMS: 500,
22+
size: 1
23+
});
24+
25+
const sessionPool = new ServerSessionPool(topology);
26+
const session = new ClientSession(topology, sessionPool);
27+
28+
let command = null;
29+
const messageHandler = () => {
30+
return request => {
31+
const doc = request.document;
32+
if (doc.ismaster) {
33+
request.reply(test.defaultFields);
34+
} else if (doc.insert) {
35+
command = doc;
36+
request.reply({ ok: 1 });
37+
}
38+
};
39+
};
40+
41+
test.servers[0].setMessageHandler(messageHandler('MONGOS1'));
42+
test.servers[1].setMessageHandler(messageHandler('MONGOS2'));
43+
44+
topology.once('fullsetup', function() {
45+
topology.insert('test.test', [{ a: 1 }], { retryWrites: true, session: session }, function(
46+
err
47+
) {
48+
expect(err).to.not.exist;
49+
expect(command).to.have.property('txnNumber');
50+
expect(command.txnNumber).to.eql(1);
51+
52+
topology.destroy();
53+
done();
54+
});
55+
});
56+
57+
topology.on('error', done);
58+
topology.connect();
59+
}
60+
});
61+
});

0 commit comments

Comments
 (0)