summaryrefslogtreecommitdiff
path: root/jstests/sharding/txn_single_write_shard_failover.js
blob: 50aaa139ffebcc6192660f9e349d00ba97404536 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
 * Runs a single-write-shard transaction which commits, but for which the client retries commit and
 * a read-only shard fails over before the second commit attempt.
 *
 * The second commit attempt should still return a commit decision.
 *
 * This test was written to reproduce the bug in the original single-write-shard transaction commit
 * optimization where if a read-only shard failed over before the client sent a second commit
 * attempt, the second commit attempt would return NoSuchTransaction with TransientTransactionError,
 * causing the client to retry the whole transaction at a higher transaction number and the
 * transaction's write to be applied twice.
 *
 * @tags: [
 *   uses_multi_shard_transaction,
 *   uses_transactions,
 * ]
 */

(function() {
'use strict';

load("jstests/libs/fail_point_util.js");

const db1Name = "db1";
const coll1Name = "foo";
const ns1 = db1Name + "." + coll1Name;

const db2Name = "db2";
const coll2Name = "bar";
const ns2 = db2Name + "." + coll2Name;

const st = new ShardingTest({
    shards: {rs0: {nodes: 2}, rs1: {nodes: 1}},
    config: TestData.configShard ? undefined : 1,
    other: {
        mongosOptions: {verbose: 3},
    }
});

jsTest.log("Create two databases on different primary shards.");
// enableSharding creates the databases.
assert.commandWorked(st.s.adminCommand({enableSharding: db1Name}));
assert.commandWorked(st.s.adminCommand({enableSharding: db2Name}));
assert.commandWorked(st.s.adminCommand({movePrimary: db1Name, to: st.shard0.shardName}));
assert.commandWorked(st.s.adminCommand({movePrimary: db2Name, to: st.shard1.shardName}));

jsTest.log("Insert data on both shards.");
// This ensures all nodes refresh their routing caches.
st.s.getDB(db1Name).getCollection(coll1Name).insert({_id: "dummy"});
st.s.getDB(db2Name).getCollection(coll2Name).insert({_id: "dummy"});

jsTest.log("Run a single-write-shard transaction and commit it.");
const session = st.s.startSession();
session.startTransaction();
session.getDatabase(db1Name).getCollection(coll1Name).findOne({_id: "readOperationOnShard0"});
session.getDatabase(db2Name).getCollection(coll2Name).insert({_id: "writeOperationOnShard1"});
// Use adminCommand so we can pass writeConcern.
assert.commandWorked(st.s.adminCommand({
    commitTransaction: 1,
    lsid: session.getSessionId(),
    txnNumber: session.getTxnNumber_forTesting(),
    autocommit: false,
    writeConcern: {w: "majority"},
}));

jsTest.log("Induce a failover on the read shard.");
assert.commandWorked(st.rs0.getPrimary().adminCommand({replSetStepDown: 60, force: true}));

jsTest.log("Make second attempt to commit, should still return that the transaction committed");
assert.commandWorked(session.commitTransaction_forTesting());

st.stop();
})();