mirror of
https://github.com/mongodb/mongo.git
synced 2024-12-01 09:32:32 +01:00
SERVER-33501 Aborted transaction number cannot be reused.
This commit is contained in:
parent
7d09f278a2
commit
2e31167d0e
@ -117,7 +117,7 @@
|
||||
txnNumber: NumberLong(txnNumber),
|
||||
autocommit: false
|
||||
}),
|
||||
ErrorCodes.TransactionAborted);
|
||||
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"}));
|
||||
@ -248,5 +248,54 @@
|
||||
|
||||
// TODO: SERVER-33690 Test the old cursor has been killed when the transaction is aborted.
|
||||
|
||||
jsTest.log("Aborted transaction number cannot be reused.");
|
||||
txnNumber++;
|
||||
assert.commandWorked(sessionDb.runCommand({
|
||||
insert: collName,
|
||||
documents: [{_id: "abort-txn-1"}],
|
||||
readConcern: {level: "snapshot"},
|
||||
txnNumber: NumberLong(txnNumber),
|
||||
startTransaction: true,
|
||||
autocommit: false
|
||||
}));
|
||||
assert.commandWorked(sessionDb.adminCommand({
|
||||
abortTransaction: 1,
|
||||
writeConcern: {w: "majority"},
|
||||
txnNumber: NumberLong(txnNumber),
|
||||
autocommit: false
|
||||
}));
|
||||
|
||||
// Cannot commit the aborted transaction.
|
||||
assert.commandFailedWithCode(
|
||||
sessionDb.adminCommand(
|
||||
{commitTransaction: 1, txnNumber: NumberLong(txnNumber), autocommit: false}),
|
||||
ErrorCodes.NoSuchTransaction);
|
||||
|
||||
// Cannot abort the aborted transaction.
|
||||
assert.commandFailedWithCode(
|
||||
sessionDb.adminCommand(
|
||||
{abortTransaction: 1, txnNumber: NumberLong(txnNumber), autocommit: false}),
|
||||
ErrorCodes.NoSuchTransaction);
|
||||
|
||||
// Cannot continue the aborted transaction.
|
||||
assert.commandFailedWithCode(sessionDb.runCommand({
|
||||
insert: collName,
|
||||
documents: [{_id: "abort-txn-2"}],
|
||||
txnNumber: NumberLong(txnNumber),
|
||||
autocommit: false
|
||||
}),
|
||||
ErrorCodes.NoSuchTransaction);
|
||||
|
||||
// Cannot restart the aborted transaction.
|
||||
assert.commandFailedWithCode(sessionDb.runCommand({
|
||||
insert: collName,
|
||||
documents: [{_id: "abort-txn-2"}],
|
||||
readConcern: {level: "snapshot"},
|
||||
txnNumber: NumberLong(txnNumber),
|
||||
startTransaction: true,
|
||||
autocommit: false
|
||||
}),
|
||||
ErrorCodes.ConflictingOperationInProgress);
|
||||
|
||||
session.endSession();
|
||||
}());
|
||||
|
@ -50,7 +50,7 @@
|
||||
assert.commandFailedWithCode(
|
||||
sessionDb.adminCommand(
|
||||
{commitTransaction: 1, txnNumber: NumberLong(txnNumber), autocommit: false}),
|
||||
ErrorCodes.TransactionAborted);
|
||||
ErrorCodes.NoSuchTransaction);
|
||||
assert.eq(null, testColl.findOne({_id: "doc"}));
|
||||
|
||||
jsTest.log("Cannot implicitly create a collection in a transaction using update.");
|
||||
@ -86,7 +86,7 @@
|
||||
assert.commandFailedWithCode(
|
||||
sessionDb.adminCommand(
|
||||
{commitTransaction: 1, txnNumber: NumberLong(txnNumber), autocommit: false}),
|
||||
ErrorCodes.TransactionAborted);
|
||||
ErrorCodes.NoSuchTransaction);
|
||||
assert.eq(null, testColl.findOne({_id: "doc"}));
|
||||
|
||||
// Update without upsert=true succeeds when the collection does not exist.
|
||||
@ -140,7 +140,7 @@
|
||||
assert.commandFailedWithCode(
|
||||
sessionDb.adminCommand(
|
||||
{commitTransaction: 1, txnNumber: NumberLong(txnNumber), autocommit: false}),
|
||||
ErrorCodes.TransactionAborted);
|
||||
ErrorCodes.NoSuchTransaction);
|
||||
assert.eq(null, testColl.findOne({_id: "doc"}));
|
||||
|
||||
// findAndModify without upsert=true succeeds when the collection does not exist.
|
||||
|
@ -22,16 +22,16 @@ function runCommandAndVerifyResponse(sessionDb, txnNumber, cmdObj, expectSuccess
|
||||
// noop writes may advance the majority commit point past the given atClusterTime
|
||||
// resulting in a SnapshotTooOld error. Eventually the read should succeed, when all
|
||||
// targeted shards are at the same cluster time, so retry until it does.
|
||||
// A snapshot read may also fail with TransactionAborted if it encountered a StaleEpoch
|
||||
// A snapshot read may also fail with NoSuchTransaction if it encountered a StaleEpoch
|
||||
// error while it was running.
|
||||
assert.soon(() => {
|
||||
const res = sessionDb.runCommand(cmdObj);
|
||||
if (!res.ok) {
|
||||
assert(res.code === ErrorCodes.SnapshotTooOld ||
|
||||
res.code === ErrorCodes.TransactionAborted,
|
||||
"expected command to fail with SnapshotTooOld or TransactionAborted, cmd: " +
|
||||
res.code === ErrorCodes.NoSuchTransaction,
|
||||
"expected command to fail with SnapshotTooOld or NoSuchTransaction, cmd: " +
|
||||
tojson(cmdObj) + ", result: " + tojson(res));
|
||||
print("Retrying because of SnapshotTooOld or TransactionAborted error.");
|
||||
print("Retrying because of SnapshotTooOld or NoSuchTransaction error.");
|
||||
txnNumber++;
|
||||
cmdObj.txnNumber = NumberLong(txnNumber);
|
||||
return false;
|
||||
|
@ -349,7 +349,7 @@ void Session::onMigrateCompletedOnPrimary(OperationContext* opCtx,
|
||||
stdx::unique_lock<stdx::mutex> ul(_mutex);
|
||||
|
||||
_checkValid(ul);
|
||||
_checkIsActiveTransaction(ul, txnNumber);
|
||||
_checkIsActiveTransaction(ul, txnNumber, false);
|
||||
|
||||
const auto updateRequest =
|
||||
_makeUpdateRequest(ul, txnNumber, lastStmtIdWriteOpTime, lastStmtIdWriteDate);
|
||||
@ -378,7 +378,7 @@ void Session::invalidate() {
|
||||
repl::OpTime Session::getLastWriteOpTime(TxnNumber txnNumber) const {
|
||||
stdx::lock_guard<stdx::mutex> lg(_mutex);
|
||||
_checkValid(lg);
|
||||
_checkIsActiveTransaction(lg, txnNumber);
|
||||
_checkIsActiveTransaction(lg, txnNumber, false);
|
||||
|
||||
if (!_lastWrittenSessionRecord || _lastWrittenSessionRecord->getTxnNum() != txnNumber)
|
||||
return {};
|
||||
@ -578,7 +578,7 @@ void Session::stashTransactionResources(OperationContext* opCtx) {
|
||||
// Always check '_activeTxnNumber', since it can be modified by migration, which does not check
|
||||
// out the session. We intentionally do not error if _txnState=kAborted, since we expect this
|
||||
// function to be called at the end of the 'abortTransaction' command.
|
||||
_checkIsActiveTransaction(lg, *opCtx->getTxnNumber());
|
||||
_checkIsActiveTransaction(lg, *opCtx->getTxnNumber(), false);
|
||||
|
||||
if (_txnState != MultiDocumentTransactionState::kInProgress &&
|
||||
_txnState != MultiDocumentTransactionState::kInSnapshotRead) {
|
||||
@ -621,8 +621,10 @@ void Session::unstashTransactionResources(OperationContext* opCtx) {
|
||||
|
||||
// Always check '_activeTxnNumber' and '_txnState', since they can be modified by session
|
||||
// kill and migration, which do not check out the session.
|
||||
_checkIsActiveTransaction(lg, *opCtx->getTxnNumber());
|
||||
uassert(ErrorCodes::TransactionAborted,
|
||||
_checkIsActiveTransaction(lg, *opCtx->getTxnNumber(), false);
|
||||
// Throw NoSuchTransaction error instead of TransactionAborted error since this is the entry
|
||||
// point of transaction execution.
|
||||
uassert(ErrorCodes::NoSuchTransaction,
|
||||
str::stream() << "Transaction " << *opCtx->getTxnNumber() << " has been aborted.",
|
||||
_txnState != MultiDocumentTransactionState::kAborted);
|
||||
|
||||
@ -728,10 +730,7 @@ void Session::addTransactionOperation(OperationContext* opCtx,
|
||||
|
||||
// Always check '_activeTxnNumber' and '_txnState', since they can be modified by session kill
|
||||
// and migration, which do not check out the session.
|
||||
_checkIsActiveTransaction(lk, *opCtx->getTxnNumber());
|
||||
uassert(ErrorCodes::TransactionAborted,
|
||||
str::stream() << "Transaction " << *opCtx->getTxnNumber() << " has been aborted.",
|
||||
_txnState != MultiDocumentTransactionState::kAborted);
|
||||
_checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true);
|
||||
|
||||
invariant(_txnState == MultiDocumentTransactionState::kInProgress);
|
||||
invariant(!_autocommit && _activeTxnNumber != kUninitializedTxnNumber);
|
||||
@ -745,10 +744,7 @@ std::vector<repl::ReplOperation> Session::endTransactionAndRetrieveOperations(
|
||||
|
||||
// Always check '_activeTxnNumber' and '_txnState', since they can be modified by session kill
|
||||
// and migration, which do not check out the session.
|
||||
_checkIsActiveTransaction(lk, *opCtx->getTxnNumber());
|
||||
uassert(ErrorCodes::TransactionAborted,
|
||||
str::stream() << "Transaction " << *opCtx->getTxnNumber() << " has been aborted.",
|
||||
_txnState != MultiDocumentTransactionState::kAborted);
|
||||
_checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true);
|
||||
|
||||
invariant(!_autocommit);
|
||||
return std::move(_transactionOperations);
|
||||
@ -759,10 +755,7 @@ void Session::commitTransaction(OperationContext* opCtx) {
|
||||
|
||||
// Always check '_activeTxnNumber' and '_txnState', since they can be modified by session kill
|
||||
// and migration, which do not check out the session.
|
||||
_checkIsActiveTransaction(lk, *opCtx->getTxnNumber());
|
||||
uassert(ErrorCodes::TransactionAborted,
|
||||
str::stream() << "Transaction " << *opCtx->getTxnNumber() << " has been aborted.",
|
||||
_txnState != MultiDocumentTransactionState::kAborted);
|
||||
_checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true);
|
||||
|
||||
if (_txnState == MultiDocumentTransactionState::kCommitted)
|
||||
return;
|
||||
@ -819,7 +812,7 @@ void Session::_checkValid(WithLock) const {
|
||||
_isValid);
|
||||
}
|
||||
|
||||
void Session::_checkIsActiveTransaction(WithLock, TxnNumber txnNumber) const {
|
||||
void Session::_checkIsActiveTransaction(WithLock, TxnNumber txnNumber, bool checkAbort) const {
|
||||
uassert(ErrorCodes::ConflictingOperationInProgress,
|
||||
str::stream() << "Cannot perform operations on transaction " << txnNumber
|
||||
<< " on session "
|
||||
@ -828,13 +821,17 @@ void Session::_checkIsActiveTransaction(WithLock, TxnNumber txnNumber) const {
|
||||
<< _activeTxnNumber
|
||||
<< " is now active.",
|
||||
txnNumber == _activeTxnNumber);
|
||||
|
||||
uassert(ErrorCodes::TransactionAborted,
|
||||
str::stream() << "Transaction " << txnNumber << " has been aborted.",
|
||||
!checkAbort || _txnState != MultiDocumentTransactionState::kAborted);
|
||||
}
|
||||
|
||||
boost::optional<repl::OpTime> Session::_checkStatementExecuted(WithLock wl,
|
||||
TxnNumber txnNumber,
|
||||
StmtId stmtId) const {
|
||||
_checkValid(wl);
|
||||
_checkIsActiveTransaction(wl, txnNumber);
|
||||
_checkIsActiveTransaction(wl, txnNumber, false);
|
||||
// Retries are not detected for multi-document transactions.
|
||||
if (_txnState == MultiDocumentTransactionState::kInProgress)
|
||||
return boost::none;
|
||||
|
@ -334,7 +334,7 @@ private:
|
||||
|
||||
void _setActiveTxn(WithLock, TxnNumber txnNumber);
|
||||
|
||||
void _checkIsActiveTransaction(WithLock, TxnNumber txnNumber) const;
|
||||
void _checkIsActiveTransaction(WithLock, TxnNumber txnNumber, bool checkAbort) const;
|
||||
|
||||
boost::optional<repl::OpTime> _checkStatementExecuted(WithLock,
|
||||
TxnNumber txnNumber,
|
||||
|
@ -792,7 +792,7 @@ TEST_F(SessionTest, ConcurrencyOfUnstashAndAbort) {
|
||||
// An unstash after an abort should uassert.
|
||||
ASSERT_THROWS_CODE(session.unstashTransactionResources(opCtx()),
|
||||
AssertionException,
|
||||
ErrorCodes::TransactionAborted);
|
||||
ErrorCodes::NoSuchTransaction);
|
||||
}
|
||||
|
||||
TEST_F(SessionTest, ConcurrencyOfUnstashAndMigration) {
|
||||
|
Loading…
Reference in New Issue
Block a user