summaryrefslogtreecommitdiff
path: root/jstests/sharding/txn_being_applied_to_secondary_cannot_be_killed.js
blob: 29bd671886ab27eef8f6bb463e1b60721d177951 (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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
 * Tests that the periodic transaction abort job cannot kill transactions being
 * applied on secondaries.
 *
 * @tags: [uses_transactions, uses_prepare_transaction, uses_multi_shard_transaction]
 */

(function() {
'use strict';

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

const dbName = "test";
const collName = "foo";
const ns = dbName + "." + collName;

// Set to something high enough that a slow machine shouldn't cause our
// transaction to be aborted before committing, but low enough that the test
// won't be unnecessarily slow when we wait for the periodic transaction
// abort job to run.
TestData.transactionLifetimeLimitSeconds = 10;

const rsOpts = {
    // Make secondaries unelectable.
    nodes: [{}, {rsConfig: {priority: 0}}, {rsConfig: {priority: 0}}],
    settings: {chainingAllowed: false, electionTimeoutMillis: ReplSetTest.kForeverMillis}
};
let st = new ShardingTest({mongos: 2, shards: {rs0: rsOpts, rs1: rsOpts, rs2: rsOpts}});

const coordinator = st.shard0;
const participant1 = st.shard1;
const participant2 = st.shard2;

// Create a sharded collection with a chunk on each shard:
// shard0: [-inf, 0)
// shard1: [0, 10)
// shard2: [10, +inf)
assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
assert.commandWorked(st.s.adminCommand({movePrimary: dbName, to: coordinator.shardName}));
assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {_id: 1}}));
assert.commandWorked(st.s.adminCommand({split: ns, middle: {_id: 0}}));
assert.commandWorked(st.s.adminCommand({split: ns, middle: {_id: 10}}));
assert.commandWorked(
    st.s.adminCommand({moveChunk: ns, find: {_id: 0}, to: participant1.shardName}));
assert.commandWorked(
    st.s.adminCommand({moveChunk: ns, find: {_id: 10}, to: participant2.shardName}));
st.refreshCatalogCacheForNs(st.s, ns);

// These forced refreshes are not strictly necessary; they just prevent extra TXN log lines
// from the shards starting, aborting, and restarting the transaction due to needing to
// refresh after the transaction has started.
assert.commandWorked(coordinator.adminCommand({_flushRoutingTableCacheUpdates: ns}));
assert.commandWorked(participant1.adminCommand({_flushRoutingTableCacheUpdates: ns}));
assert.commandWorked(participant2.adminCommand({_flushRoutingTableCacheUpdates: ns}));

// Start a new session and start a transaction on that session.
const session = st.s.startSession();
session.startTransaction();

// Insert a document onto each shard to make this a cross-shard transaction.
assert.commandWorked(session.getDatabase(dbName).runCommand({
    insert: collName,
    documents: [{_id: -5}, {_id: 5}, {_id: 15}],
}));

// Set a failpoint to make oplog application hang on one secondary after applying the
// operations in the transaction but before preparing the TransactionParticipant.
const applyOpsHangBeforePreparingTransaction = "applyOpsHangBeforePreparingTransaction";
const firstSecondary = st.rs0.getSecondary();
let failPoint = configureFailPoint(firstSecondary, applyOpsHangBeforePreparingTransaction);

// Commit the transaction, which will execute two-phase commit.
assert.commandWorked(session.commitTransaction_forTesting());

jsTest.log("Verify that the transaction was committed on all shards.");
// Use assert.soon(), because although coordinateCommitTransaction currently blocks
// until the commit process is fully complete, it will eventually be changed to only
// block until the decision is *written*, so the documents may not be visible
// immediately.
assert.soon(function() {
    return 3 === st.s.getDB(dbName).getCollection(collName).find().itcount();
});

jsTest.log("Waiting for secondary to apply the prepare oplog entry.");
failPoint.wait();

// Wait for the periodic transaction abort job to run while oplog
// application is hanging. The job should run every 10 seconds due to the
// transactionLifetimeLimitSeconds parameter being set to 10 above, so the
// likelihood of it running while sleeping 30 seconds is high. If it does
// not run, the test will trivially pass without testing the desired
// behavior, but it will not cause the test to fail.
sleep(30000);

jsTest.log("Turning off " + applyOpsHangBeforePreparingTransaction + " failpoint.");
// Allow oplog application to continue by turning off the failpoint. The
// transaction should prepare successfully and should not have been aborted
// by the transaction abort job.
failPoint.off();

jsTest.log("Turned off " + applyOpsHangBeforePreparingTransaction + " failpoint.");

st.stop();
})();