Skip to content

Commit f8ca0b9

Browse files
committed
SERVER-33374 Test concurrent snapshot read and catalog operations
1 parent 8c64aa1 commit f8ca0b9

File tree

3 files changed

+160
-4
lines changed

3 files changed

+160
-4
lines changed

jstests/concurrency/fsm_workload_helpers/snapshot_read_utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ function doSnapshotFind(sortByAscending, collName, data, findErrorCodes) {
7676
/**
7777
* Performs a snapshot getmore. This function is to be used in conjunction with doSnapshotFind.
7878
*/
79-
function doSnapshotGetMore(collName, data, getMoreErrorCodes) {
79+
function doSnapshotGetMore(collName, data, getMoreErrorCodes, commitTransactionErrorCodes) {
8080
// doSnapshotGetMore may be called even if doSnapshotFind fails to obtain a cursor.
8181
if (!data.cursorId) {
8282
return;
@@ -99,7 +99,7 @@ function doSnapshotGetMore(collName, data, getMoreErrorCodes) {
9999
autocommit: false
100100
};
101101
res = data.sessionDb.adminCommand(commitCmd);
102-
assertWorkedOrFailed(commitCmd, res, [ErrorCodes.NoSuchTransaction]);
102+
assertWorkedOrFailed(commitCmd, res, commitTransactionErrorCodes);
103103
}
104104

105105
/**
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
'use strict';
2+
3+
/**
4+
* Perform snapshot reads that span a find and a getmore concurrently with CRUD operations. The
5+
* snapshot reads and CRUD operations will all contend for locks on db and collName. Since the
6+
* snapshot read does not release its locks until the transaction is committed, it is expected that
7+
* once the read has begun, catalog operations with conflicting locks will block until the read is
8+
* finished. Additionally, index operations running concurrently with the snapshot read may cause
9+
* the read to fail with a SnapshotUnavailable error.
10+
* @tags: [uses_transactions]
11+
*/
12+
13+
load('jstests/concurrency/fsm_workload_helpers/snapshot_read_utils.js');
14+
var $config = (function() {
15+
const data = {numIds: 100, numDocsToInsertPerThread: 5, valueToBeInserted: 1, batchSize: 50};
16+
17+
const states = {
18+
init: function init(db, collName) {
19+
this.session = db.getMongo().startSession({causalConsistency: false});
20+
this.sessionDb = this.session.getDatabase(db.getName());
21+
this.txnNumber = 0;
22+
this.stmtId = 0;
23+
},
24+
25+
snapshotFind: function snapshotFind(db, collName) {
26+
// The ascending snapshot find order is more likely to include documents read, updated,
27+
// and deleted by readDocs, updateDocs, and deleteDocs.
28+
// The descending snapshot find order is more likely to include documents inserted by
29+
// insertDocs.
30+
const sortOptions = [true, false];
31+
const sortByAscending = sortOptions[Random.randInt(2)];
32+
const readErrorCodes = [ErrorCodes.NoSuchTransaction, ErrorCodes.SnapshotUnavailable];
33+
const commitTransactionErrorCodes = readErrorCodes;
34+
doSnapshotFind(sortByAscending, collName, this, readErrorCodes);
35+
if (this.cursorId) {
36+
doSnapshotGetMore(collName, this, readErrorCodes, commitTransactionErrorCodes);
37+
}
38+
},
39+
40+
insertDocs: function insertDocs(db, collName) {
41+
for (let i = 0; i < this.numDocsToInsertPerThread; ++i) {
42+
const res = db[collName].insert({value: this.valueToBeInserted});
43+
assertWhenOwnColl.writeOK(res);
44+
assertWhenOwnColl.eq(1, res.nInserted);
45+
}
46+
},
47+
48+
updateDocs: function updateDocs(db, collName) {
49+
for (let i = 0; i < this.numIds; ++i) {
50+
try {
51+
db[collName].update({_id: i}, {$inc: {value: 1}});
52+
} catch (e) {
53+
// dropIndex can cause queries to throw if these queries yield.
54+
assertAlways.contains(e.code,
55+
[ErrorCodes.QueryPlanKilled, ErrorCodes.OperationFailed],
56+
'unexpected error code: ' + e.code + ': ' + e.message);
57+
}
58+
}
59+
},
60+
61+
readDocs: function readDocs(db, collName) {
62+
for (let i = 0; i < this.numIds; ++i) {
63+
try {
64+
db[collName].findOne({_id: i});
65+
} catch (e) {
66+
// dropIndex can cause queries to throw if these queries yield.
67+
assertAlways.contains(e.code,
68+
[ErrorCodes.QueryPlanKilled, ErrorCodes.OperationFailed],
69+
'unexpected error code: ' + e.code + ': ' + e.message);
70+
}
71+
}
72+
},
73+
74+
deleteDocs: function deleteDocs(db, collName) {
75+
let indexToDelete = Math.floor(Math.random() * this.numIds);
76+
try {
77+
db[collName].deleteOne({_id: indexToDelete});
78+
} catch (e) {
79+
// dropIndex can cause queries to throw if these queries yield.
80+
assertAlways.contains(e.code,
81+
[ErrorCodes.QueryPlanKilled, ErrorCodes.OperationFailed],
82+
'unexpected error code: ' + e.code + ': ' + e.message);
83+
}
84+
},
85+
86+
createIndex: function createIndex(db, collName) {
87+
db[collName].createIndex({value: 1}, {background: true});
88+
},
89+
90+
dropIndex: function dropIndex(db, collName) {
91+
db[collName].dropIndex({value: 1});
92+
}
93+
};
94+
95+
const transitions = {
96+
init: {
97+
snapshotFind: 0.15,
98+
insertDocs: 0.14,
99+
updateDocs: 0.14,
100+
deleteDocs: 0.14,
101+
readDocs: 0.14,
102+
createIndex: 0.15,
103+
dropIndex: 0.14,
104+
},
105+
snapshotFind: {
106+
insertDocs: 0.17,
107+
updateDocs: 0.16,
108+
deleteDocs: 0.17,
109+
readDocs: 0.16,
110+
createIndex: 0.17,
111+
dropIndex: 0.17,
112+
},
113+
insertDocs: {snapshotFind: 1.0},
114+
updateDocs: {snapshotFind: 1.0},
115+
readDocs: {snapshotFind: 1.0},
116+
deleteDocs: {snapshotFind: 1.0},
117+
createIndex: {snapshotFind: 1.0},
118+
dropIndex: {snapshotFind: 1.0},
119+
};
120+
121+
function setup(db, collName, cluster) {
122+
assertWhenOwnColl.commandWorked(db.runCommand({create: collName}));
123+
for (let i = 0; i < this.numIds; ++i) {
124+
const res = db[collName].insert({_id: i, value: this.valueToBeInserted});
125+
assert.writeOK(res);
126+
assert.eq(1, res.nInserted);
127+
}
128+
}
129+
130+
function teardown(db, collName, cluster) {
131+
}
132+
133+
const skip = function skip(cluster) {
134+
// TODO(SERVER-34570) remove isSharded() check once transactions are supported in sharded
135+
// environments.
136+
if (cluster.isSharded() || cluster.isStandalone()) {
137+
return {skip: true, msg: 'only runs in a replica set.'};
138+
}
139+
return {skip: false};
140+
};
141+
142+
return {
143+
threadCount: 5,
144+
iterations: 10,
145+
startState: 'init',
146+
states: states,
147+
transitions: transitions,
148+
setup: setup,
149+
teardown: teardown,
150+
data: data,
151+
skip: skip
152+
};
153+
154+
})();

jstests/concurrency/fsm_workloads/snapshot_read_kill_operations.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ var $config = (function() {
2727
},
2828

2929
snapshotGetMore: function snapshotGetMore(db, collName) {
30-
doSnapshotGetMore(
31-
collName, this, [ErrorCodes.NoSuchTransaction, ErrorCodes.CursorNotFound]);
30+
doSnapshotGetMore(collName,
31+
this,
32+
[ErrorCodes.NoSuchTransaction, ErrorCodes.CursorNotFound],
33+
[ErrorCodes.NoSuchTransaction]);
3234
},
3335

3436
incrementTxnNumber: function incrementTxnNumber(db, collName) {

0 commit comments

Comments
 (0)