summaryrefslogtreecommitdiff
path: root/jstests/sharding/internal_sessions_reaping.js
blob: 7e2e71fe3345d7dc6380aff98731f95060f20bf7 (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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/**
 * Tests that the lifetime of the config.transactions and config.image_collection entries for
 * child sessions is tied to the lifetime of the config.system.sessions entry for their parent
 * sessions.
 *
 * @tags: [requires_fcv_51, featureFlagInternalTransactions]
 */

(function() {
"use strict";

// This test makes assertions about the number of sessions, which are not compatible with
// implicit sessions.
TestData.disableImplicitSessions = true;

const st = new ShardingTest({
    shards: 1,
    shardOptions: {
        setParameter: {
            maxSessions: 1,
            TransactionRecordMinimumLifetimeMinutes: 0,
            storeFindAndModifyImagesInSideCollection: true
        }
    }
});

const shard0Rst = st.rs0;
const shard0Primary = shard0Rst.getPrimary();

const kDbName = "testDb";
const kCollName = "testColl";

const kConfigSessionsNs = "config.system.sessions";
const kConfigTxnsNs = "config.transactions";
const kImageCollNs = "config.image_collection";
const kOplogCollNs = "local.oplog.rs";

let sessionsCollOnPrimary = shard0Primary.getCollection(kConfigSessionsNs);
let transactionsCollOnPrimary = shard0Primary.getCollection(kConfigTxnsNs);
let imageCollOnPrimary = shard0Primary.getCollection(kImageCollNs);
let oplogCollOnPrimary = shard0Primary.getCollection(kOplogCollNs);
let testDB = shard0Primary.getDB(kDbName);

assert.commandWorked(testDB.createCollection(kCollName));
assert.commandWorked(shard0Primary.adminCommand({refreshLogicalSessionCacheNow: 1}));

const sessionUUID = UUID();
const parentLsid = {
    id: sessionUUID
};

const kInternalTxnNumber = NumberLong(0);

let numTransactionsCollEntries = 0;
let numImageCollEntries = 0;

assert.commandWorked(
    testDB.runCommand({insert: kCollName, documents: [{_id: 0}], lsid: parentLsid}));

const childLsid0 = {
    id: sessionUUID,
    txnUUID: UUID()
};
assert.commandWorked(testDB.runCommand({
    update: kCollName,
    updates: [{q: {_id: 0}, u: {$set: {a: 0}}}],
    lsid: childLsid0,
    txnNumber: kInternalTxnNumber,
    startTransaction: true,
    autocommit: false
}));
assert.commandWorked(testDB.adminCommand(
    {commitTransaction: 1, lsid: childLsid0, txnNumber: kInternalTxnNumber, autocommit: false}));
numTransactionsCollEntries++;
assert.eq(numTransactionsCollEntries, transactionsCollOnPrimary.find().itcount());

jsTest.log("Verify that the config.transactions entry for the internal transaction for " +
           "the non-retryable update did not get reaped after command returned");
assert.eq(numTransactionsCollEntries, transactionsCollOnPrimary.find().itcount());

const parentTxnNumber1 = NumberLong(1);

assert.commandWorked(testDB.runCommand({
    update: kCollName,
    updates: [{q: {_id: 0}, u: {$set: {b: 0}}}],
    lsid: parentLsid,
    txnNumber: parentTxnNumber1,
    stmtId: NumberInt(0)
}));
numTransactionsCollEntries++;

const childLsid1 = {
    id: sessionUUID,
    txnNumber: parentTxnNumber1,
    txnUUID: UUID()
};
assert.commandWorked(testDB.runCommand({
    update: kCollName,
    updates: [{q: {_id: 0}, u: {$set: {c: 0}}}],
    lsid: childLsid1,
    txnNumber: kInternalTxnNumber,
    stmtId: NumberInt(1),
    startTransaction: true,
    autocommit: false
}));
assert.commandWorked(testDB.adminCommand(
    {commitTransaction: 1, lsid: childLsid1, txnNumber: kInternalTxnNumber, autocommit: false}));
numTransactionsCollEntries++;

const parentTxnNumber2 = NumberLong(2);

assert.commandWorked(testDB.runCommand({
    findAndModify: kCollName,
    query: {_id: 0},
    update: {$set: {d: 0}},
    lsid: parentLsid,
    txnNumber: parentTxnNumber2,
    stmtId: NumberInt(0)
}));
numImageCollEntries++;

jsTest.log("Verify that the config.transactions entry for the retryable internal transaction for " +
           "the update did not get reaped although there is already a new retryable write");
assert.eq(numTransactionsCollEntries, transactionsCollOnPrimary.find().itcount());

const childLsid2 = {
    id: sessionUUID,
    txnNumber: parentTxnNumber2,
    txnUUID: UUID()
};
assert.commandWorked(testDB.runCommand({
    findAndModify: kCollName,
    query: {_id: 0},
    update: {$set: {e: 0}},
    lsid: childLsid2,
    txnNumber: kInternalTxnNumber,
    stmtId: NumberInt(1),
    startTransaction: true,
    autocommit: false
}));
assert.commandWorked(testDB.adminCommand(
    {commitTransaction: 1, lsid: childLsid2, txnNumber: kInternalTxnNumber, autocommit: false}));
numTransactionsCollEntries++;
numImageCollEntries++;

const parentTxnNumber3 = NumberLong(3);

assert.commandWorked(testDB.runCommand({
    insert: kCollName,
    documents: [{_id: 1}],
    lsid: parentLsid,
    txnNumber: parentTxnNumber3,
    stmtId: NumberInt(0)
}));

jsTest.log("Verify that the config.transactions entry for the retryable internal transaction for " +
           "the findAndModify did not get reaped although there is already a new retryable write");
assert.eq(numTransactionsCollEntries, transactionsCollOnPrimary.find().itcount());
assert.eq(numImageCollEntries, imageCollOnPrimary.find().itcount());

assert.eq({_id: 0, a: 0, b: 0, c: 0, d: 0, e: 0},
          testDB.getCollection(kCollName).findOne({_id: 0}));
assert.eq({_id: 1}, testDB.getCollection(kCollName).findOne({_id: 1}));

assert.commandWorked(shard0Primary.adminCommand({refreshLogicalSessionCacheNow: 1}));

assert.eq(1, sessionsCollOnPrimary.find({"_id.id": sessionUUID}).itcount());
assert.eq(numTransactionsCollEntries, transactionsCollOnPrimary.find().itcount());
assert.eq(numImageCollEntries, imageCollOnPrimary.find().itcount());

assert.commandWorked(shard0Primary.adminCommand({reapLogicalSessionCacheNow: 1}));

jsTest.log("Verify that the config.transactions entries for internal transactions did not get " +
           "reaped although they are expired since the config.system.sessions entry for the " +
           "parent session still has not been deleted");

assert.eq(1, sessionsCollOnPrimary.find({"_id.id": sessionUUID}).itcount());
assert.eq(numTransactionsCollEntries,
          transactionsCollOnPrimary.find().itcount(),
          tojson(transactionsCollOnPrimary.find().toArray()));
assert.eq(numImageCollEntries, imageCollOnPrimary.find().itcount());

// Remove the session doc so the parent session gets reaped when reapLogicalSessionCacheNow is run.
assert.commandWorked(sessionsCollOnPrimary.remove({}));
assert.commandWorked(shard0Primary.adminCommand({reapLogicalSessionCacheNow: 1}));

jsTest.log("Verify that the config.transactions entries got reaped since the " +
           "config.system.sessions entry for the parent session had already been deleted");
assert.eq(0, sessionsCollOnPrimary.find().itcount());
assert.eq(0,
          transactionsCollOnPrimary.find().itcount(),
          tojson(transactionsCollOnPrimary.find().toArray()));
assert.eq(0, imageCollOnPrimary.find().itcount());

// Validate that writes to config.transactions do not generate oplog entries, with the exception of
// deletions.
assert.eq(numTransactionsCollEntries,
          oplogCollOnPrimary.find({op: 'd', ns: kConfigTxnsNs}).itcount());
assert.eq(0, oplogCollOnPrimary.find({op: {'$ne': 'd'}, ns: kConfigTxnsNs}).itcount());

st.stop();
})();