diff options
author | Lingzhi Deng <lingzhi.deng@mongodb.com> | 2019-05-20 00:49:33 -0400 |
---|---|---|
committer | Lingzhi Deng <lingzhi.deng@mongodb.com> | 2019-05-23 16:46:30 -0400 |
commit | 8a89b076d28a904c200e491698ef1169dd8a7254 (patch) | |
tree | dc32fa3bdc62cdd9f8b9b84777f0df8f03f43cec /jstests/replsets/prepare_conflict_read_concern_behavior.js | |
parent | 47d4eca3fcdfa8eed0f1bef28021c8603452dec3 (diff) | |
download | mongo-8a89b076d28a904c200e491698ef1169dd8a7254.tar.gz |
SERVER-40938: disallow afterClusterTime and ignore prepare conflicts for dbhash and map-reduce
Diffstat (limited to 'jstests/replsets/prepare_conflict_read_concern_behavior.js')
-rw-r--r-- | jstests/replsets/prepare_conflict_read_concern_behavior.js | 113 |
1 files changed, 111 insertions, 2 deletions
diff --git a/jstests/replsets/prepare_conflict_read_concern_behavior.js b/jstests/replsets/prepare_conflict_read_concern_behavior.js index 4327f7d86c5..ebf714f5fb2 100644 --- a/jstests/replsets/prepare_conflict_read_concern_behavior.js +++ b/jstests/replsets/prepare_conflict_read_concern_behavior.js @@ -3,6 +3,13 @@ * and afterClusterTime reads are the only reads that should block on a prepared transaction. Reads * that happen as part of a write should also block on a prepared transaction. * + * Also test that dbHash and mapReduce, which acquire collection S locks for reads, do not block on + * a prepared transaction on secondaries. Otherwise, it would cause deadlocks when the prepared + * transaction reacquires locks (since locks were yielded on secondaries) at commit time. This test + * makes sure dbHash and mapReduce do not accept a non local read concern or afterClusterTime and so + * it is safe for the two commands to ignore prepare conflicts for reads. This test also makes sure + * mapReduce that does writes is not allowed to run on secondaries. + * * @tags: [uses_transactions, uses_prepare_transaction] */ @@ -10,7 +17,7 @@ "use strict"; load("jstests/core/txns/libs/prepare_helpers.js"); - const replTest = new ReplSetTest({nodes: 1}); + const replTest = new ReplSetTest({nodes: 2}); replTest.startSet(); replTest.initiate(); @@ -25,6 +32,9 @@ const testColl = testDB.getCollection(collName); const testColl2 = testDB.getCollection(collName2); + const secondary = replTest.getSecondary(); + const secondaryTestDB = secondary.getDB(dbName); + // Turn off timestamp reaping so that clusterTimeBeforePrepare doesn't get too old. assert.commandWorked(testDB.adminCommand({ configureFailPoint: "WTPreserveSnapshotHistoryIndefinitely", @@ -57,6 +67,34 @@ return res; }; + const dbHash = function(read_concern, timeout, db) { + let res = db.runCommand({ + dbHash: 1, + readConcern: read_concern, + maxTimeMS: timeout, + }); + + return res; + }; + + const mapReduce = function(read_concern, timeout, db, outOptions = {inline: 1}) { + let map = function() { + emit(this.a, this.a); + }; + let reduce = function(key, vals) { + return 1; + }; + let res = db.runCommand({ + mapReduce: collName, + map: map, + reduce: reduce, + out: outOptions, + readConcern: read_concern, + maxTimeMS: timeout, + }); + return res; + }; + assert.commandWorked( testColl.insert({_id: 1, in_prepared_txn: false}, {writeConcern: {w: "majority"}})); assert.commandWorked(testColl.insert({_id: 2, in_prepared_txn: false})); @@ -125,6 +163,77 @@ collName2, 1)); + // dbHash does not accept a non local read concern or afterClusterTime and it also sets + // ignore_prepare=true during its execution. Therefore, dbHash should never get prepare + // conflicts on secondaries. dbHash acquires collection S lock for reads and it will be + // blocked by a prepared transaction that writes to the same collection if it is run on + // primaries. + jsTestLog("Test dbHash doesn't support afterClusterTime read."); + assert.commandFailedWithCode( + dbHash({level: 'local', afterClusterTime: clusterTimeAfterPrepare}, + failureTimeout, + secondaryTestDB), + ErrorCodes.InvalidOptions); + + jsTestLog("Test dbHash doesn't support read concern other than local."); + assert.commandWorked(dbHash({level: 'local'}, successTimeout, secondaryTestDB)); + assert.commandFailedWithCode(dbHash({level: 'available'}, failureTimeout, secondaryTestDB), + ErrorCodes.InvalidOptions); + assert.commandFailedWithCode(dbHash({level: 'majority'}, failureTimeout, secondaryTestDB), + ErrorCodes.InvalidOptions); + assert.commandFailedWithCode(dbHash({level: 'snapshot'}, failureTimeout, secondaryTestDB), + ErrorCodes.InvalidOptions); + assert.commandFailedWithCode( + dbHash({level: 'linearizable'}, failureTimeout, secondaryTestDB), + ErrorCodes.InvalidOptions); + + jsTestLog("Test dbHash on secondary doesn't block on a prepared transaction."); + assert.commandWorked(dbHash({}, successTimeout, secondaryTestDB)); + jsTestLog("Test dbHash on primary blocks on collection S lock which conflicts with " + + "a prepared transaction."); + assert.commandFailedWithCode(dbHash({}, failureTimeout, testDB), + ErrorCodes.MaxTimeMSExpired); + + // mapReduce does not accept a non local read concern or afterClusterTime and it also sets + // ignore_prepare=true during its read phase. As mapReduce that writes is not allowed to run + // on secondaries, mapReduce should never get prepare conflicts on secondaries. mapReduce + // acquires collection S lock for reads and it will be blocked by a prepared transaction + // that writes to the same collection if it is run on primaries. + jsTestLog("Test mapReduce doesn't support afterClusterTime read."); + assert.commandFailedWithCode( + mapReduce({level: 'local', afterClusterTime: clusterTimeAfterPrepare}, + failureTimeout, + secondaryTestDB), + ErrorCodes.InvalidOptions); + + jsTestLog("Test mapReduce doesn't support read concern other than local."); + assert.commandWorked(mapReduce({level: 'local'}, successTimeout, secondaryTestDB)); + assert.commandFailedWithCode( + mapReduce({level: 'available'}, failureTimeout, secondaryTestDB), + ErrorCodes.InvalidOptions); + assert.commandFailedWithCode( + mapReduce({level: 'majority'}, failureTimeout, secondaryTestDB), + ErrorCodes.InvalidOptions); + assert.commandFailedWithCode( + mapReduce({level: 'snapshot'}, failureTimeout, secondaryTestDB), + ErrorCodes.InvalidOptions); + assert.commandFailedWithCode( + mapReduce({level: 'linearizable'}, failureTimeout, secondaryTestDB), + ErrorCodes.InvalidOptions); + + jsTestLog("Test mapReduce that writes is not allowed to run on secondaries."); + // It currently returns ErrorCodes.PrimarySteppedDown in this case. + assert.commandFailedWithCode(mapReduce({}, failureTimeout, secondaryTestDB, "outColl"), + [ErrorCodes.InvalidOptions, ErrorCodes.PrimarySteppedDown]); + + jsTestLog("Test mapReduce on secondary doesn't block on a prepared transaction."); + assert.commandWorked(mapReduce({}, successTimeout, secondaryTestDB)); + + jsTestLog("Test mapReduce on primary blocks on collection S lock which conflicts with " + + "a prepared transaction."); + assert.commandFailedWithCode(mapReduce({}, failureTimeout, testDB), + ErrorCodes.MaxTimeMSExpired); + jsTestLog("Test read from an update blocks on a prepared transaction."); assert.commandFailedWithCode(testDB.runCommand({ update: collName, @@ -145,7 +254,7 @@ jsTestLog("Test read with read concern 'snapshot' and a read timestamp after " + "prepareTimestamp on non-prepared documents doesn't block on a prepared " + "transaction."); - assert.commandWorked(read({}, failureTimeout, sessionDB2, collName2, 1)); + assert.commandWorked(read({}, successTimeout, sessionDB2, collName2, 1)); jsTestLog("Test read with read concern 'snapshot' and a read timestamp after " + "prepareTimestamp blocks on a prepared transaction."); |