summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSpencer T Brody <spencer@mongodb.com>2018-09-12 14:24:53 -0400
committerSpencer T Brody <spencer@mongodb.com>2018-09-13 14:47:18 -0400
commite54a379fa19a91bd5e59752fc705d9a98e5ba453 (patch)
tree77d0ef5a88d6a92fd0b2af4e42b82273bc226f3b
parent7c13a75b928ace3f65c9553352689dc0a6d0ca83 (diff)
downloadmongo-e54a379fa19a91bd5e59752fc705d9a98e5ba453.tar.gz
SERVER-35817 Allow shutdowns while prepared transactions are in-flight
-rw-r--r--jstests/replsets/shutdown_with_prepared_transaction.js38
-rw-r--r--src/mongo/db/db.cpp4
-rw-r--r--src/mongo/db/kill_sessions_local.cpp9
-rw-r--r--src/mongo/db/kill_sessions_local.h5
-rw-r--r--src/mongo/db/transaction_participant.cpp12
-rw-r--r--src/mongo/db/transaction_participant.h12
6 files changed, 76 insertions, 4 deletions
diff --git a/jstests/replsets/shutdown_with_prepared_transaction.js b/jstests/replsets/shutdown_with_prepared_transaction.js
new file mode 100644
index 00000000000..387d7474ef3
--- /dev/null
+++ b/jstests/replsets/shutdown_with_prepared_transaction.js
@@ -0,0 +1,38 @@
+/**
+ * Tests that a server can still be shut down while it has prepared transactions pending.
+ *
+ * @tags: [uses_transactions]
+ */
+(function() {
+ "use strict";
+ load("jstests/core/txns/libs/prepare_helpers.js");
+
+ const replTest = new ReplSetTest({nodes: 1});
+ replTest.startSet();
+ replTest.initiate();
+
+ const conn = replTest.getPrimary();
+
+ const dbName = "test";
+ const collName = "shutdown_with_prepared_txn";
+ const testDB = conn.getDB(dbName);
+ const testColl = testDB.getCollection(collName);
+
+ assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}}));
+
+ const session = conn.startSession({causalConsistency: false});
+ const sessionDB = session.getDatabase(dbName);
+ const sessionColl = sessionDB.getCollection(collName);
+
+ jsTestLog("Starting a simple transaction and putting it into prepare");
+
+ session.startTransaction();
+ assert.commandWorked(sessionColl.insert({_id: 1}));
+
+ PrepareHelpers.prepareTransaction(session);
+
+ jsTestLog("Shutting down the set with the transaction still in prepare state");
+ // Skip validation during ReplSetTest cleanup since validate() will block behind the prepared
+ // transaction's locks when trying to take a collection X lock.
+ replTest.stopSet(null /*signal*/, false /*forRestart*/, {skipValidation: true});
+}());
diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp
index cc570bcdae5..abe28452d2e 100644
--- a/src/mongo/db/db.cpp
+++ b/src/mongo/db/db.cpp
@@ -892,9 +892,7 @@ void shutdownTask() {
ShardingInitializationMongoD::get(serviceContext)->shutDown(opCtx);
// Destroy all stashed transaction resources, in order to release locks.
- SessionKiller::Matcher matcherAllSessions(
- KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)});
- killSessionsLocalKillTransactions(opCtx, matcherAllSessions);
+ killSessionsLocalShutdownAllTransactions(opCtx);
}
serviceContext->setKillAllOperations();
diff --git a/src/mongo/db/kill_sessions_local.cpp b/src/mongo/db/kill_sessions_local.cpp
index 0c2b0cf9641..0406a818284 100644
--- a/src/mongo/db/kill_sessions_local.cpp
+++ b/src/mongo/db/kill_sessions_local.cpp
@@ -88,4 +88,13 @@ void killAllExpiredTransactions(OperationContext* opCtx) {
});
}
+void killSessionsLocalShutdownAllTransactions(OperationContext* opCtx) {
+ SessionKiller::Matcher matcherAllSessions(
+ KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)});
+ SessionCatalog::get(opCtx)->scanSessions(
+ opCtx, matcherAllSessions, [](OperationContext* opCtx, Session* session) {
+ TransactionParticipant::getFromNonCheckedOutSession(session)->shutdown();
+ });
+}
+
} // namespace mongo
diff --git a/src/mongo/db/kill_sessions_local.h b/src/mongo/db/kill_sessions_local.h
index a21bcec4ae5..ea8f442b325 100644
--- a/src/mongo/db/kill_sessions_local.h
+++ b/src/mongo/db/kill_sessions_local.h
@@ -53,4 +53,9 @@ void killSessionsLocalKillTransactions(OperationContext* opCtx,
*/
void killAllExpiredTransactions(OperationContext* opCtx);
+/**
+ * Run during shutdown to kill all in-progress transactions, including those in prepare.
+ */
+void killSessionsLocalShutdownAllTransactions(OperationContext* opCtx);
+
} // namespace mongo
diff --git a/src/mongo/db/transaction_participant.cpp b/src/mongo/db/transaction_participant.cpp
index 33eb0d866ae..1d9fd5e4321 100644
--- a/src/mongo/db/transaction_participant.cpp
+++ b/src/mongo/db/transaction_participant.cpp
@@ -398,8 +398,11 @@ TransactionParticipant::SideTransactionBlock::~SideTransactionBlock() {
}
}
void TransactionParticipant::_stashActiveTransaction(WithLock, OperationContext* opCtx) {
- invariant(_activeTxnNumber == opCtx->getTxnNumber());
+ if (_inShutdown) {
+ return;
+ }
+ invariant(_activeTxnNumber == opCtx->getTxnNumber());
{
stdx::lock_guard<stdx::mutex> lm(_metricsMutex);
_transactionMetricsObserver.onStash(ServerTransactionsMetrics::get(opCtx),
@@ -746,6 +749,13 @@ void TransactionParticipant::_commitTransaction(stdx::unique_lock<stdx::mutex> l
_cleanUpTxnResourceOnOpCtx(lk, opCtx, TransactionState::kCommitted);
}
+void TransactionParticipant::shutdown() {
+ stdx::lock_guard<stdx::mutex> lock(_mutex);
+
+ _inShutdown = true;
+ _txnResourceStash = boost::none;
+}
+
void TransactionParticipant::abortArbitraryTransaction() {
stdx::lock_guard<stdx::mutex> lock(_mutex);
diff --git a/src/mongo/db/transaction_participant.h b/src/mongo/db/transaction_participant.h
index 6946c1a24c0..a32b8d095d0 100644
--- a/src/mongo/db/transaction_participant.h
+++ b/src/mongo/db/transaction_participant.h
@@ -142,6 +142,16 @@ public:
static TransactionParticipant* getFromNonCheckedOutSession(Session* session);
/**
+ * Kills the transaction if it is running, ensuring that it releases all resources, even if the
+ * transaction is in prepare(). Avoids writing any oplog entries or making any changes to the
+ * transaction table. State for prepared transactions will be re-constituted at startup.
+ * Note that we don't take any active steps to prevent continued use of this
+ * TransactionParticipant after shutdown() is called, but we rely on callers to not
+ * continue using the TransactionParticipant once we are in shutdown.
+ */
+ void shutdown();
+
+ /**
* Called for speculative transactions to fix the optime of the snapshot to read from.
*/
void setSpeculativeTransactionOpTimeToLastApplied(OperationContext* opCtx);
@@ -552,6 +562,8 @@ private:
// Protects the member variables below.
mutable stdx::mutex _mutex;
+ bool _inShutdown{false};
+
// Holds transaction resources between network operations.
boost::optional<TxnResources> _txnResourceStash;