diff options
author | Jack Mulrow <jack.mulrow@mongodb.com> | 2021-05-27 21:21:52 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-06-01 20:50:16 +0000 |
commit | f5920f892b6cc0572140be6b81babf8bf4419278 (patch) | |
tree | 7af6c99325c9820fee141bd6b37f3bbf1309a7e9 | |
parent | 87e10cc39920a7aff6283820686de312d4527df8 (diff) | |
download | mongo-f5920f892b6cc0572140be6b81babf8bf4419278.tar.gz |
SERVER-56981 Verify the blockTimestamp is higher than the timestamp of any donor writes
10 files changed, 23 insertions, 12 deletions
diff --git a/src/mongo/db/op_observer_impl.cpp b/src/mongo/db/op_observer_impl.cpp index d1267aa5768..72594583d3d 100644 --- a/src/mongo/db/op_observer_impl.cpp +++ b/src/mongo/db/op_observer_impl.cpp @@ -1421,8 +1421,8 @@ void OpObserverImpl::onUnpreparedTransactionCommit(OperationContext* opCtx, // Throw TenantMigrationConflict error if the database for the transaction statements is being // migrated. We only need check the namespace of the first statement since a transaction's // statements must all be for the same tenant. - tenant_migration_access_blocker::checkIfCanWriteOrThrow(opCtx, - statements->begin()->getNss().db()); + tenant_migration_access_blocker::checkIfCanWriteOrThrow( + opCtx, statements->begin()->getNss().db(), oplogSlots.back().getTimestamp()); if (MONGO_unlikely(hangAndFailUnpreparedCommitAfterReservingOplogSlot.shouldFail())) { hangAndFailUnpreparedCommitAfterReservingOplogSlot.pauseWhileSet(opCtx); diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp index 4879330a958..dce50640e27 100644 --- a/src/mongo/db/repl/oplog.cpp +++ b/src/mongo/db/repl/oplog.cpp @@ -334,7 +334,7 @@ void _logOpsInner(OperationContext* opCtx, // index build on the donor after the blockTimestamp, plus if an index build fails to commit due // to TenantMigrationConflict, we need to be able to abort the index build and clean up. if (repl::feature_flags::gTenantMigrations.isEnabledAndIgnoreFCV() && !isAbortIndexBuild) { - tenant_migration_access_blocker::checkIfCanWriteOrThrow(opCtx, nss.db()); + tenant_migration_access_blocker::checkIfCanWriteOrThrow(opCtx, nss.db(), timestamps.back()); } Status result = oplogCollection->insertDocumentsForOplog(opCtx, records, timestamps); diff --git a/src/mongo/db/repl/tenant_migration_access_blocker.h b/src/mongo/db/repl/tenant_migration_access_blocker.h index 06a78bb3584..48c0179b8be 100644 --- a/src/mongo/db/repl/tenant_migration_access_blocker.h +++ b/src/mongo/db/repl/tenant_migration_access_blocker.h @@ -56,7 +56,7 @@ public: // Called by all writes and reads against the database. // - virtual Status checkIfCanWrite() = 0; + virtual Status checkIfCanWrite(Timestamp writeTs) = 0; virtual Status waitUntilCommittedOrAborted(OperationContext* opCtx) = 0; diff --git a/src/mongo/db/repl/tenant_migration_access_blocker_util.cpp b/src/mongo/db/repl/tenant_migration_access_blocker_util.cpp index 54985fb0afc..d766bb74341 100644 --- a/src/mongo/db/repl/tenant_migration_access_blocker_util.cpp +++ b/src/mongo/db/repl/tenant_migration_access_blocker_util.cpp @@ -243,14 +243,14 @@ void checkIfLinearizableReadWasAllowedOrThrow(OperationContext* opCtx, StringDat } } -void checkIfCanWriteOrThrow(OperationContext* opCtx, StringData dbName) { +void checkIfCanWriteOrThrow(OperationContext* opCtx, StringData dbName, Timestamp writeTs) { // The migration protocol guarantees the recipient will not get writes until the migration // is committed. auto mtab = TenantMigrationAccessBlockerRegistry::get(opCtx->getServiceContext()) .getTenantMigrationAccessBlockerForDbName(dbName, MtabType::kDonor); if (mtab) { - auto status = mtab->checkIfCanWrite(); + auto status = mtab->checkIfCanWrite(writeTs); mtab->recordTenantMigrationError(status); uassertStatusOK(status); } diff --git a/src/mongo/db/repl/tenant_migration_access_blocker_util.h b/src/mongo/db/repl/tenant_migration_access_blocker_util.h index 530632d1b1b..6ff5d3405a3 100644 --- a/src/mongo/db/repl/tenant_migration_access_blocker_util.h +++ b/src/mongo/db/repl/tenant_migration_access_blocker_util.h @@ -72,7 +72,7 @@ void checkIfLinearizableReadWasAllowedOrThrow(OperationContext* opCtx, StringDat * Throws TenantMigrationConflict if the database is being migrated and the migration is in the * blocking state. Throws TenantMigrationCommitted if it is in committed. */ -void checkIfCanWriteOrThrow(OperationContext* opCtx, StringData dbName); +void checkIfCanWriteOrThrow(OperationContext* opCtx, StringData dbName, Timestamp writeTs); /** * Returns TenantMigrationConflict if the database is being migrated (even if migration is not yet diff --git a/src/mongo/db/repl/tenant_migration_donor_access_blocker.cpp b/src/mongo/db/repl/tenant_migration_donor_access_blocker.cpp index 41f6be0c327..0b25fb1a9d3 100644 --- a/src/mongo/db/repl/tenant_migration_donor_access_blocker.cpp +++ b/src/mongo/db/repl/tenant_migration_donor_access_blocker.cpp @@ -80,11 +80,14 @@ TenantMigrationDonorAccessBlocker::TenantMigrationDonorAccessBlocker( .getOrCreateBlockedOperationsExecutor(); } -Status TenantMigrationDonorAccessBlocker::checkIfCanWrite() { +Status TenantMigrationDonorAccessBlocker::checkIfCanWrite(Timestamp writeTs) { stdx::lock_guard<Latch> lg(_mutex); switch (_state.getState()) { case BlockerState::State::kAllow: + // As a sanity check, we track the highest allowed write timestamp to ensure no + // writes are allowed with a timestamp higher than the block timestamp. + _highestAllowedWriteTimestamp = std::max(writeTs, _highestAllowedWriteTimestamp); case BlockerState::State::kAborted: return Status::OK(); case BlockerState::State::kBlockWrites: @@ -234,6 +237,13 @@ void TenantMigrationDonorAccessBlocker::startBlockingReadsAfter(const Timestamp& invariant(!_commitOpTime); invariant(!_abortOpTime); + invariant( + blockTimestamp > _highestAllowedWriteTimestamp, + str::stream() << "The block timestamp must be higher than the timestamp of any allowed " + "write, blockTimestamp: " + << blockTimestamp.toString() << ", highestAllowedWriteTimestamp: " + << _highestAllowedWriteTimestamp.toString()); + _state.transitionTo(BlockerState::State::kBlockWritesAndReads); _blockTimestamp = blockTimestamp; } diff --git a/src/mongo/db/repl/tenant_migration_donor_access_blocker.h b/src/mongo/db/repl/tenant_migration_donor_access_blocker.h index b11a1a56b9a..51bf14c0bd0 100644 --- a/src/mongo/db/repl/tenant_migration_donor_access_blocker.h +++ b/src/mongo/db/repl/tenant_migration_donor_access_blocker.h @@ -187,7 +187,7 @@ public: // Called by all writes and reads against the database. // - Status checkIfCanWrite() final; + Status checkIfCanWrite(Timestamp writeTs) final; Status waitUntilCommittedOrAborted(OperationContext* opCtx) final; Status checkIfLinearizableReadWasAllowed(OperationContext* opCtx) final; @@ -331,6 +331,7 @@ private: BlockerState _state; + Timestamp _highestAllowedWriteTimestamp; boost::optional<Timestamp> _blockTimestamp; boost::optional<repl::OpTime> _commitOpTime; boost::optional<repl::OpTime> _abortOpTime; diff --git a/src/mongo/db/repl/tenant_migration_recipient_access_blocker.cpp b/src/mongo/db/repl/tenant_migration_recipient_access_blocker.cpp index 3bd49d79934..6428afa32ca 100644 --- a/src/mongo/db/repl/tenant_migration_recipient_access_blocker.cpp +++ b/src/mongo/db/repl/tenant_migration_recipient_access_blocker.cpp @@ -65,7 +65,7 @@ TenantMigrationRecipientAccessBlocker::TenantMigrationRecipientAccessBlocker( .getOrCreateBlockedOperationsExecutor(); } -Status TenantMigrationRecipientAccessBlocker::checkIfCanWrite() { +Status TenantMigrationRecipientAccessBlocker::checkIfCanWrite(Timestamp writeTs) { // This is guaranteed by the migration protocol. The recipient will not get any writes until the // migration is committed on the donor. return Status::OK(); diff --git a/src/mongo/db/repl/tenant_migration_recipient_access_blocker.h b/src/mongo/db/repl/tenant_migration_recipient_access_blocker.h index 4b17dfdac88..fb3050f8b27 100644 --- a/src/mongo/db/repl/tenant_migration_recipient_access_blocker.h +++ b/src/mongo/db/repl/tenant_migration_recipient_access_blocker.h @@ -81,7 +81,7 @@ public: // Called by all writes and reads against the database. // - Status checkIfCanWrite() final; + Status checkIfCanWrite(Timestamp writeTs) final; Status waitUntilCommittedOrAborted(OperationContext* opCtx) final; Status checkIfLinearizableReadWasAllowed(OperationContext* opCtx) final; diff --git a/src/mongo/db/repl/tenant_migration_recipient_access_blocker_test.cpp b/src/mongo/db/repl/tenant_migration_recipient_access_blocker_test.cpp index 4b708cd25fe..ebdea752a36 100644 --- a/src/mongo/db/repl/tenant_migration_recipient_access_blocker_test.cpp +++ b/src/mongo/db/repl/tenant_migration_recipient_access_blocker_test.cpp @@ -125,7 +125,7 @@ TEST_F(TenantMigrationRecipientAccessBlockerTest, NoopFunctions) { getServiceContext(), getMigrationId(), getTenantId(), getDonorConnectionString()); // These functions are noop functions and should not throw even in reject state. - ASSERT_OK(mtab.checkIfCanWrite()); + ASSERT_OK(mtab.checkIfCanWrite(Timestamp())); ASSERT_OK(mtab.checkIfLinearizableReadWasAllowed(opCtx())); ASSERT_OK(mtab.checkIfCanBuildIndex()); } |