summaryrefslogtreecommitdiff
path: root/jstests/replsets/reconstruct_prepared_transactions_initial_sync_index_build.js
blob: c09bc058739e10ebfe4f799bf1dc0648bcf6be02 (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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/**
 * Tests that initial sync successfully applies a prepare oplog entry during oplog application phase
 * of initial sync.  Additionally, we will test that a background index build blocks this particular
 * situation until the index build is finished.
 *
 * @tags: [
 *     uses_transactions,
 *     uses_prepare_transaction,
 * ]
 */

(function() {
"use strict";

load("jstests/libs/check_log.js");
load("jstests/core/txns/libs/prepare_helpers.js");

const replTest = new ReplSetTest({nodes: 2});
replTest.startSet();

const config = replTest.getReplSetConfig();
// Increase the election timeout so that we do not accidentally trigger an election while the
// secondary is restarting.
config.settings = {
    "electionTimeoutMillis": 12 * 60 * 60 * 1000
};
replTest.initiate(config);

const primary = replTest.getPrimary();
let secondary = replTest.getSecondary();

const dbName = "test";
const collName = "reconstruct_prepared_transactions_initial_sync_index_build";
let testDB = primary.getDB(dbName);
let testColl = testDB.getCollection(collName);

assert.commandWorked(testColl.insert({_id: 0}));

jsTestLog("Restarting the secondary");

// Restart the secondary with startClean set to true so that it goes through initial sync. Also
// restart the node with a failpoint turned on that will pause initial sync. This way we can do
// some writes on the sync source while initial sync is paused and know that its operations
// won't be copied during collection cloning. Instead, the writes must be applied during oplog
// application.
replTest.stop(secondary, undefined /* signal */, {skipValidation: true});
secondary = replTest.start(
    secondary,
    {
        startClean: true,
        setParameter: {
            'failpoint.initialSyncHangDuringCollectionClone': tojson(
                {mode: 'alwaysOn', data: {namespace: testColl.getFullName(), numDocsToClone: 1}}),
            'numInitialSyncAttempts': 1
        }
    },
    true /* wait */);

// Wait for failpoint to be reached so we know that collection cloning is paused.
checkLog.contains(secondary, "initialSyncHangDuringCollectionClone fail point enabled");

jsTestLog("Running operations while collection cloning is paused");

// Perform writes while collection cloning is paused so that we know they must be applied during
// the oplog application stage of initial sync.
assert.commandWorked(testColl.insert({_id: 1, a: 1}));
assert.commandWorked(testColl.createIndex({a: 1}));
// Make the index build hang on the secondary so that initial sync gets to the prepared-txn
// reconstruct stage with the index build still running.
assert.commandWorked(
    secondary.adminCommand({configureFailPoint: 'hangAfterStartingIndexBuild', mode: "alwaysOn"}));

let session = primary.startSession();
let sessionDB = session.getDatabase(dbName);
const sessionColl = sessionDB.getCollection(collName);

jsTestLog("Preparing the transaction");

// Prepare a transaction while collection cloning is paused so that its oplog entry must be
// applied during the oplog application phase of initial sync.
session.startTransaction();
assert.commandWorked(sessionColl.update({_id: 1, a: 1}, {_id: 1, a: 2}));
const prepareTimestamp = PrepareHelpers.prepareTransaction(session, {w: 1});

clearRawMongoProgramOutput();
jsTestLog("Resuming initial sync");

// Resume initial sync.
assert.commandWorked(secondary.adminCommand(
    {configureFailPoint: "initialSyncHangDuringCollectionClone", mode: "off"}));

const enableTwoPhaseIndexBuild =
    assert.commandWorked(primary.adminCommand({getParameter: 1, enableTwoPhaseIndexBuild: 1}))
        .enableTwoPhaseIndexBuild;
if (!enableTwoPhaseIndexBuild) {
    // Wait for log message.
    assert.soon(
        () =>
            rawMongoProgramOutput().indexOf(
                "blocking replication until index builds are finished on test.reconstruct_prepared_transactions_initial_sync_index_build, due to prepared transaction") >=
            0,
        "replication not hanging");
}

// Unblock index build.
assert.commandWorked(
    secondary.adminCommand({configureFailPoint: 'hangAfterStartingIndexBuild', mode: "off"}));

// Wait for the secondary to complete initial sync.
replTest.awaitSecondaryNodes();

jsTestLog("Initial sync completed");

secondary.setSlaveOk();
const secondaryColl = secondary.getDB(dbName).getCollection(collName);

// Make sure that while reading from the node that went through initial sync, we can't read
// changes to the documents from the prepared transaction after initial sync. Also, make
// sure that the writes that happened when collection cloning was paused happened.
const res = secondaryColl.find().sort({_id: 1}).toArray();
assert.eq(res, [{_id: 0}, {_id: 1, a: 1}], res);

// Wait for the prepared transaction oplog entry to be majority committed before committing the
// transaction.
PrepareHelpers.awaitMajorityCommitted(replTest, prepareTimestamp);

jsTestLog("Committing the transaction");

assert.commandWorked(PrepareHelpers.commitTransaction(session, prepareTimestamp));
replTest.awaitReplication();

// Make sure that we can see the data from the committed transaction on the secondary.
assert.docEq(secondaryColl.findOne({_id: 1}), {_id: 1, a: 2});

replTest.stopSet();
})();