summaryrefslogtreecommitdiff
path: root/jstests/replsets/reconstruct_prepared_transactions_initial_sync_on_oplog_seed.js
blob: 30f9c497e486a5fee1d1be75a9d1eb1645097f24 (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
/**
 * Tests that initial sync successfully reconstructs a prepared transaction using the oplog seed
 * entry after failing the first attempt of initial sync and that we can commit the transactions
 * afterwards. During the first attempt of initial sync, there will be some oplog entries that need
 * to be applied and the lastApplied OpTime will be advanced along with the oplog application. We
 * test that the lastApplied and the localSnapshot are properly reset after the first attempt fails.
 * During the second attempt of initial sync, there will be no oplog entries that need to be applied
 * and the prepare oplog entry will be inserted as the oplog seed. We then make sure the oplog seed
 * entry is visible and the prepared transaction is properly reconstructed.
 *
 * @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_on_oplog_seed";

let testDB = primary.getDB(dbName);
let testColl = testDB.getCollection(collName);

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

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

jsTestLog("Restarting the secondary");

// Restart the secondary with startClean set to true so that it goes through initial sync.
replTest.stop(secondary, undefined /* signal */, {skipValidation: true});
secondary = replTest.start(
    secondary,
    {
        startClean: true,
        setParameter: {
            'numInitialSyncAttempts': 2,
            // Fail point to force the first attempt to fail and hang before starting the second
            // attempt.
            'failpoint.failAndHangInitialSync': tojson({mode: 'alwaysOn'}),
            'failpoint.initialSyncHangDuringCollectionClone': tojson(
                {mode: 'alwaysOn', data: {namespace: testColl.getFullName(), numDocsToClone: 0}}),
            'logComponentVerbosity': tojson({'replication': {'initialSync': 2}})
        }
    },
    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 first attempt of initial sync.
assert.commandWorked(testColl.insert({_id: 2}));

jsTestLog("Resuming initial sync");

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

// Wait for failpoint to be reached so we know that first attempt is finishing and is about to
// fail.
checkLog.contains(secondary, "failAndHangInitialSync fail point enabled");

jsTestLog("Preparing the transaction before the second attempt of initial sync");

session.startTransaction();
assert.commandWorked(sessionColl.update({_id: 1}, {_id: 1, a: 1}));
const prepareTimestamp = PrepareHelpers.prepareTransaction(session, {w: 1});

jsTestLog("Resuming initial sync for the second attempt");
// Resume initial sync.
assert.commandWorked(
    secondary.adminCommand({configureFailPoint: "failAndHangInitialSync", mode: "off"}));

// Wait for the secondary to complete initial sync.
replTest.awaitSecondaryNodes();
PrepareHelpers.awaitMajorityCommitted(replTest, prepareTimestamp);

jsTestLog("Initial sync completed");

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

jsTestLog("Checking that the transaction is properly prepared");

// Make sure that we can't read changes to the document from the prepared transaction after
// initial sync.
assert.eq(secondaryColl.findOne({_id: 1}), {_id: 1});

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 if it was
// applied during secondary oplog application.
assert.eq(secondaryColl.findOne({_id: 1}), {_id: 1, a: 1});

replTest.stopSet();
})();