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
|
/*
* Tests that read operations that encounter prepare conflicts are killed during
* stepdown to prevent deadlocks between prepare conflicts and state transitions.
*
* @tags: [uses_transactions, uses_prepare_transaction]
*/
(function() {
"use strict";
load("jstests/core/txns/libs/prepare_helpers.js");
load("jstests/libs/check_log.js");
// Start one of the nodes with priority: 0 to avoid elections.
var rst = new ReplSetTest({nodes: [{}, {rsConfig: {priority: 0}}]});
rst.startSet();
rst.initiate();
let primary = rst.getPrimary();
const dbName = "test";
const collName = "kill_reads_with_prepare_conflicts_during_step_down";
const primaryDB = primary.getDB(dbName);
// Used to make sure that the correct amount of operations were killed on this node
// during stepdown.
const primaryAdmin = primary.getDB("admin");
const primaryColl = primaryDB[collName];
let session = primary.startSession();
const sessionID = session.getSessionId();
let sessionDB = session.getDatabase(dbName);
const sessionColl = sessionDB.getCollection(collName);
assert.commandWorked(primaryAdmin.adminCommand(
{configureFailPoint: "WTPrintPrepareConflictLog", mode: "alwaysOn"}));
// Insert a document that we will later modify in a transaction.
assert.commandWorked(primaryColl.insert({_id: 1}));
jsTestLog("Start a transaction and prepare it");
session.startTransaction();
assert.commandWorked(sessionColl.update({_id: 1}, {_id: 1, a: 1}));
const prepareTimestamp = PrepareHelpers.prepareTransaction(session);
TestData.dbName = dbName;
TestData.collName = collName;
const readBlockedOnPrepareConflictThread = startParallelShell(() => {
const parallelTestDB = db.getSiblingDB(TestData.dbName);
const parallelTestCollName = TestData.collName;
// Advance the clusterTime with another insert.
let res = assert.commandWorked(parallelTestDB.runCommand(
{insert: parallelTestCollName, documents: [{advanceClusterTime: 1}]}));
assert(res.hasOwnProperty("$clusterTime"), res);
assert(res.$clusterTime.hasOwnProperty("clusterTime"), res);
const clusterTime = res.$clusterTime.clusterTime;
jsTestLog("Using afterClusterTime: " + clusterTime);
// The following read should block on the prepared transaction since it will be
// reading a conflicting document using an afterClusterTime later than the
// prepareTimestamp.
assert.commandFailedWithCode(parallelTestDB.runCommand({
find: parallelTestCollName,
filter: {_id: 1},
readConcern: {afterClusterTime: clusterTime}
}),
ErrorCodes.InterruptedDueToReplStateChange);
}, primary.port);
jsTestLog("Waiting for failpoint");
checkLog.contains(primary, "WTPrintPrepareConflictLog fail point enabled");
// Once we have confirmed that the find command has hit a prepare conflict, we can perform
// a step down.
jsTestLog("Stepping down primary");
assert.commandWorked(
primaryAdmin.adminCommand({replSetStepDown: 60 * 10 /* 10 minutes */, force: true}));
readBlockedOnPrepareConflictThread();
rst.waitForState(primary, ReplSetTest.State.SECONDARY);
// Validate that the read operation got killed during step down.
let replMetrics =
assert.commandWorked(primaryAdmin.adminCommand({serverStatus: 1})).metrics.repl;
assert.eq(replMetrics.stepDown.userOperationsKilled, 1);
// Allow the primary to be re-elected, and wait for it.
assert.commandWorked(primaryAdmin.adminCommand({replSetFreeze: 0}));
primary = rst.getPrimary();
// Make sure we can successfully commit the prepared transaction.
jsTestLog("Restoring shell session state");
session = PrepareHelpers.createSessionWithGivenId(primary, sessionID);
sessionDB = session.getDatabase(dbName);
// The transaction on this session should have a txnNumber of 0. We explicitly set this
// since createSessionWithGivenId does not restore the current txnNumber in the shell.
session.setTxnNumber_forTesting(0);
const txnNumber = session.getTxnNumber_forTesting();
jsTestLog("Committing transaction");
// Commit the transaction.
assert.commandWorked(sessionDB.adminCommand({
commitTransaction: 1,
commitTimestamp: prepareTimestamp,
txnNumber: NumberLong(txnNumber),
autocommit: false,
}));
rst.stopSet();
})();
|