diff options
Diffstat (limited to 'jstests/core/txns/multi_statement_transaction_abort.js')
-rw-r--r-- | jstests/core/txns/multi_statement_transaction_abort.js | 504 |
1 files changed, 253 insertions, 251 deletions
diff --git a/jstests/core/txns/multi_statement_transaction_abort.js b/jstests/core/txns/multi_statement_transaction_abort.js index 3e8e8a62758..a7946af8eda 100644 --- a/jstests/core/txns/multi_statement_transaction_abort.js +++ b/jstests/core/txns/multi_statement_transaction_abort.js @@ -1,255 +1,257 @@ // Test basic multi-statement transaction abort. // @tags: [uses_transactions, uses_snapshot_read_concern] (function() { - "use strict"; - - const dbName = "test"; - const collName = "multi_statement_transaction_abort"; - const testDB = db.getSiblingDB(dbName); - const testColl = testDB[collName]; - - testDB.runCommand({drop: collName, writeConcern: {w: "majority"}}); - - assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}})); - let txnNumber = 0; - - const sessionOptions = {causalConsistency: false}; - const session = testDB.getMongo().startSession(sessionOptions); - const sessionDb = session.getDatabase(dbName); - - jsTest.log("Insert two documents in a transaction and abort"); - - // Insert a doc within the transaction. - assert.commandWorked(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "insert-1"}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber), - startTransaction: true, - autocommit: false - })); - - // Insert a doc within a transaction. - assert.commandWorked(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "insert-2"}], - txnNumber: NumberLong(txnNumber), - autocommit: false - })); - - // Cannot read with default read concern. - assert.eq(null, testColl.findOne({_id: "insert-1"})); - // Cannot read with default read concern. - assert.eq(null, testColl.findOne({_id: "insert-2"})); - - // abortTransaction can only be run on the admin database. - assert.commandWorked(sessionDb.adminCommand({ - abortTransaction: 1, - writeConcern: {w: "majority"}, - txnNumber: NumberLong(txnNumber), - autocommit: false - })); - - // Read with default read concern cannot see the aborted transaction. - assert.eq(null, testColl.findOne({_id: "insert-1"})); - assert.eq(null, testColl.findOne({_id: "insert-2"})); - - jsTest.log("Insert two documents in a transaction and commit"); - - // Insert a doc with the same _id in a new transaction should work. - txnNumber++; - assert.commandWorked(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "insert-1"}, {_id: "insert-2"}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber), - startTransaction: true, - autocommit: false - })); - // commitTransaction can only be called on the admin database. - assert.commandWorked(sessionDb.adminCommand({ - commitTransaction: 1, - writeConcern: {w: "majority"}, - txnNumber: NumberLong(txnNumber), - autocommit: false - })); - // Read with default read concern sees the committed transaction. - assert.eq({_id: "insert-1"}, testColl.findOne({_id: "insert-1"})); - assert.eq({_id: "insert-2"}, testColl.findOne({_id: "insert-2"})); - - jsTest.log("Cannot abort empty transaction because it's not in progress"); - txnNumber++; - // abortTransaction can only be called on the admin database. - let res = sessionDb.adminCommand({ - abortTransaction: 1, - writeConcern: {w: "majority"}, - txnNumber: NumberLong(txnNumber), - autocommit: false - }); - assert.commandFailedWithCode(res, ErrorCodes.NoSuchTransaction); - assert.eq(res.errorLabels, ["TransientTransactionError"]); - - jsTest.log("Abort transaction on duplicated key errors"); - assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}})); - assert.commandWorked(testColl.insert({_id: "insert-1"}, {writeConcern: {w: "majority"}})); - txnNumber++; - // The first insert works well. - assert.commandWorked(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "insert-2"}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber), - startTransaction: true, - autocommit: false - })); - // But the second insert throws duplicated index key error. - res = assert.commandFailedWithCode(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "insert-1", x: 0}], - txnNumber: NumberLong(txnNumber), - autocommit: false - }), - ErrorCodes.DuplicateKey); - // DuplicateKey is not a transient error. - assert.eq(res.errorLabels, null); - - // The error aborts the transaction. - // commitTransaction can only be called on the admin database. - assert.commandFailedWithCode(sessionDb.adminCommand({ - commitTransaction: 1, - writeConcern: {w: "majority"}, - txnNumber: NumberLong(txnNumber), - autocommit: false - }), - ErrorCodes.NoSuchTransaction); - // Verify the documents are the same. - assert.eq({_id: "insert-1"}, testColl.findOne({_id: "insert-1"})); - assert.eq(null, testColl.findOne({_id: "insert-2"})); - - jsTest.log("Abort transaction on write conflict errors"); - assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}})); - txnNumber++; - const session2 = testDB.getMongo().startSession(sessionOptions); - const sessionDb2 = session2.getDatabase(dbName); - // Insert a doc from session 1. - assert.commandWorked(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "insert-1", from: 1}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber), - startTransaction: true, - autocommit: false - })); - let txnNumber2 = 0; - // Insert a doc from session 2 that doesn't conflict with session 1. - assert.commandWorked(sessionDb2.runCommand({ - insert: collName, - documents: [{_id: "insert-2", from: 2}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber2), - startTransaction: true, - autocommit: false - })); - // Insert a doc from session 2 that conflicts with session 1. - res = sessionDb2.runCommand({ - insert: collName, - documents: [{_id: "insert-1", from: 2}], - txnNumber: NumberLong(txnNumber2), - autocommit: false - }); - assert.commandFailedWithCode(res, ErrorCodes.WriteConflict); - assert.eq(res.errorLabels, ["TransientTransactionError"]); - - // Session 1 isn't affected. - // commitTransaction can only be called on the admin database. - assert.commandWorked(sessionDb.adminCommand({ - commitTransaction: 1, - writeConcern: {w: "majority"}, - txnNumber: NumberLong(txnNumber), - autocommit: false - })); - // Transaction on session 2 is aborted. - assert.commandFailedWithCode(sessionDb2.adminCommand({ - commitTransaction: 1, - writeConcern: {w: "majority"}, - txnNumber: NumberLong(txnNumber2), - autocommit: false - }), - ErrorCodes.NoSuchTransaction); - // Verify the documents only reflect the first transaction. - assert.eq({_id: "insert-1", from: 1}, testColl.findOne({_id: "insert-1"})); - assert.eq(null, testColl.findOne({_id: "insert-2"})); - - jsTest.log("Higher transaction number aborts existing running transaction."); - txnNumber++; - assert.commandWorked(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "running-txn-1"}, {_id: "running-txn-2"}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber), - startTransaction: true, - autocommit: false - })); - // A higher txnNumber aborts the old and inserts the same document. - txnNumber++; - assert.commandWorked(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "running-txn-2"}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber), - startTransaction: true, - autocommit: false - })); - // commitTransaction can only be called on the admin database. - assert.commandWorked(sessionDb.adminCommand({ - commitTransaction: 1, - writeConcern: {w: "majority"}, - txnNumber: NumberLong(txnNumber), - autocommit: false - })); - // Read with default read concern sees the committed transaction but cannot see the aborted one. - assert.eq(null, testColl.findOne({_id: "running-txn-1"})); - assert.eq({_id: "running-txn-2"}, testColl.findOne({_id: "running-txn-2"})); - - jsTest.log("Higher transaction number aborts existing running snapshot read."); - assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}})); - assert.commandWorked( - testColl.insert([{doc: 1}, {doc: 2}, {doc: 3}], {writeConcern: {w: "majority"}})); - txnNumber++; - // Perform a snapshot read under a new transaction. - let runningReadResult = assert.commandWorked(sessionDb.runCommand({ - find: collName, - batchSize: 2, - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber), - startTransaction: true, - autocommit: false - })); - - // The cursor has not been exhausted. - assert(runningReadResult.hasOwnProperty("cursor"), tojson(runningReadResult)); - assert.neq(0, runningReadResult.cursor.id, tojson(runningReadResult)); - - txnNumber++; - // Perform a second snapshot read under a new transaction. - let newReadResult = assert.commandWorked(sessionDb.runCommand({ - find: collName, - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber), - startTransaction: true, - autocommit: false - })); - - // The cursor has been exhausted. - assert(newReadResult.hasOwnProperty("cursor"), tojson(newReadResult)); - assert.eq(0, newReadResult.cursor.id, tojson(newReadResult)); - // commitTransaction can only be called on the admin database. - assert.commandWorked(sessionDb.adminCommand({ - commitTransaction: 1, - writeConcern: {w: "majority"}, - txnNumber: NumberLong(txnNumber), - autocommit: false - })); - - session.endSession(); +"use strict"; + +const dbName = "test"; +const collName = "multi_statement_transaction_abort"; +const testDB = db.getSiblingDB(dbName); +const testColl = testDB[collName]; + +testDB.runCommand({drop: collName, writeConcern: {w: "majority"}}); + +assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}})); +let txnNumber = 0; + +const sessionOptions = { + causalConsistency: false +}; +const session = testDB.getMongo().startSession(sessionOptions); +const sessionDb = session.getDatabase(dbName); + +jsTest.log("Insert two documents in a transaction and abort"); + +// Insert a doc within the transaction. +assert.commandWorked(sessionDb.runCommand({ + insert: collName, + documents: [{_id: "insert-1"}], + readConcern: {level: "snapshot"}, + txnNumber: NumberLong(txnNumber), + startTransaction: true, + autocommit: false +})); + +// Insert a doc within a transaction. +assert.commandWorked(sessionDb.runCommand({ + insert: collName, + documents: [{_id: "insert-2"}], + txnNumber: NumberLong(txnNumber), + autocommit: false +})); + +// Cannot read with default read concern. +assert.eq(null, testColl.findOne({_id: "insert-1"})); +// Cannot read with default read concern. +assert.eq(null, testColl.findOne({_id: "insert-2"})); + +// abortTransaction can only be run on the admin database. +assert.commandWorked(sessionDb.adminCommand({ + abortTransaction: 1, + writeConcern: {w: "majority"}, + txnNumber: NumberLong(txnNumber), + autocommit: false +})); + +// Read with default read concern cannot see the aborted transaction. +assert.eq(null, testColl.findOne({_id: "insert-1"})); +assert.eq(null, testColl.findOne({_id: "insert-2"})); + +jsTest.log("Insert two documents in a transaction and commit"); + +// Insert a doc with the same _id in a new transaction should work. +txnNumber++; +assert.commandWorked(sessionDb.runCommand({ + insert: collName, + documents: [{_id: "insert-1"}, {_id: "insert-2"}], + readConcern: {level: "snapshot"}, + txnNumber: NumberLong(txnNumber), + startTransaction: true, + autocommit: false +})); +// commitTransaction can only be called on the admin database. +assert.commandWorked(sessionDb.adminCommand({ + commitTransaction: 1, + writeConcern: {w: "majority"}, + txnNumber: NumberLong(txnNumber), + autocommit: false +})); +// Read with default read concern sees the committed transaction. +assert.eq({_id: "insert-1"}, testColl.findOne({_id: "insert-1"})); +assert.eq({_id: "insert-2"}, testColl.findOne({_id: "insert-2"})); + +jsTest.log("Cannot abort empty transaction because it's not in progress"); +txnNumber++; +// abortTransaction can only be called on the admin database. +let res = sessionDb.adminCommand({ + abortTransaction: 1, + writeConcern: {w: "majority"}, + txnNumber: NumberLong(txnNumber), + autocommit: false +}); +assert.commandFailedWithCode(res, ErrorCodes.NoSuchTransaction); +assert.eq(res.errorLabels, ["TransientTransactionError"]); + +jsTest.log("Abort transaction on duplicated key errors"); +assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}})); +assert.commandWorked(testColl.insert({_id: "insert-1"}, {writeConcern: {w: "majority"}})); +txnNumber++; +// The first insert works well. +assert.commandWorked(sessionDb.runCommand({ + insert: collName, + documents: [{_id: "insert-2"}], + readConcern: {level: "snapshot"}, + txnNumber: NumberLong(txnNumber), + startTransaction: true, + autocommit: false +})); +// But the second insert throws duplicated index key error. +res = assert.commandFailedWithCode(sessionDb.runCommand({ + insert: collName, + documents: [{_id: "insert-1", x: 0}], + txnNumber: NumberLong(txnNumber), + autocommit: false +}), + ErrorCodes.DuplicateKey); +// DuplicateKey is not a transient error. +assert.eq(res.errorLabels, null); + +// The error aborts the transaction. +// commitTransaction can only be called on the admin database. +assert.commandFailedWithCode(sessionDb.adminCommand({ + commitTransaction: 1, + writeConcern: {w: "majority"}, + txnNumber: NumberLong(txnNumber), + autocommit: false +}), + ErrorCodes.NoSuchTransaction); +// Verify the documents are the same. +assert.eq({_id: "insert-1"}, testColl.findOne({_id: "insert-1"})); +assert.eq(null, testColl.findOne({_id: "insert-2"})); + +jsTest.log("Abort transaction on write conflict errors"); +assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}})); +txnNumber++; +const session2 = testDB.getMongo().startSession(sessionOptions); +const sessionDb2 = session2.getDatabase(dbName); +// Insert a doc from session 1. +assert.commandWorked(sessionDb.runCommand({ + insert: collName, + documents: [{_id: "insert-1", from: 1}], + readConcern: {level: "snapshot"}, + txnNumber: NumberLong(txnNumber), + startTransaction: true, + autocommit: false +})); +let txnNumber2 = 0; +// Insert a doc from session 2 that doesn't conflict with session 1. +assert.commandWorked(sessionDb2.runCommand({ + insert: collName, + documents: [{_id: "insert-2", from: 2}], + readConcern: {level: "snapshot"}, + txnNumber: NumberLong(txnNumber2), + startTransaction: true, + autocommit: false +})); +// Insert a doc from session 2 that conflicts with session 1. +res = sessionDb2.runCommand({ + insert: collName, + documents: [{_id: "insert-1", from: 2}], + txnNumber: NumberLong(txnNumber2), + autocommit: false +}); +assert.commandFailedWithCode(res, ErrorCodes.WriteConflict); +assert.eq(res.errorLabels, ["TransientTransactionError"]); + +// Session 1 isn't affected. +// commitTransaction can only be called on the admin database. +assert.commandWorked(sessionDb.adminCommand({ + commitTransaction: 1, + writeConcern: {w: "majority"}, + txnNumber: NumberLong(txnNumber), + autocommit: false +})); +// Transaction on session 2 is aborted. +assert.commandFailedWithCode(sessionDb2.adminCommand({ + commitTransaction: 1, + writeConcern: {w: "majority"}, + txnNumber: NumberLong(txnNumber2), + autocommit: false +}), + ErrorCodes.NoSuchTransaction); +// Verify the documents only reflect the first transaction. +assert.eq({_id: "insert-1", from: 1}, testColl.findOne({_id: "insert-1"})); +assert.eq(null, testColl.findOne({_id: "insert-2"})); + +jsTest.log("Higher transaction number aborts existing running transaction."); +txnNumber++; +assert.commandWorked(sessionDb.runCommand({ + insert: collName, + documents: [{_id: "running-txn-1"}, {_id: "running-txn-2"}], + readConcern: {level: "snapshot"}, + txnNumber: NumberLong(txnNumber), + startTransaction: true, + autocommit: false +})); +// A higher txnNumber aborts the old and inserts the same document. +txnNumber++; +assert.commandWorked(sessionDb.runCommand({ + insert: collName, + documents: [{_id: "running-txn-2"}], + readConcern: {level: "snapshot"}, + txnNumber: NumberLong(txnNumber), + startTransaction: true, + autocommit: false +})); +// commitTransaction can only be called on the admin database. +assert.commandWorked(sessionDb.adminCommand({ + commitTransaction: 1, + writeConcern: {w: "majority"}, + txnNumber: NumberLong(txnNumber), + autocommit: false +})); +// Read with default read concern sees the committed transaction but cannot see the aborted one. +assert.eq(null, testColl.findOne({_id: "running-txn-1"})); +assert.eq({_id: "running-txn-2"}, testColl.findOne({_id: "running-txn-2"})); + +jsTest.log("Higher transaction number aborts existing running snapshot read."); +assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}})); +assert.commandWorked( + testColl.insert([{doc: 1}, {doc: 2}, {doc: 3}], {writeConcern: {w: "majority"}})); +txnNumber++; +// Perform a snapshot read under a new transaction. +let runningReadResult = assert.commandWorked(sessionDb.runCommand({ + find: collName, + batchSize: 2, + readConcern: {level: "snapshot"}, + txnNumber: NumberLong(txnNumber), + startTransaction: true, + autocommit: false +})); + +// The cursor has not been exhausted. +assert(runningReadResult.hasOwnProperty("cursor"), tojson(runningReadResult)); +assert.neq(0, runningReadResult.cursor.id, tojson(runningReadResult)); + +txnNumber++; +// Perform a second snapshot read under a new transaction. +let newReadResult = assert.commandWorked(sessionDb.runCommand({ + find: collName, + readConcern: {level: "snapshot"}, + txnNumber: NumberLong(txnNumber), + startTransaction: true, + autocommit: false +})); + +// The cursor has been exhausted. +assert(newReadResult.hasOwnProperty("cursor"), tojson(newReadResult)); +assert.eq(0, newReadResult.cursor.id, tojson(newReadResult)); +// commitTransaction can only be called on the admin database. +assert.commandWorked(sessionDb.adminCommand({ + commitTransaction: 1, + writeConcern: {w: "majority"}, + txnNumber: NumberLong(txnNumber), + autocommit: false +})); + +session.endSession(); }()); |