TEST_F(WiredTigerRecoveryUnitTestFixture, CommitTimestampAfterSetTimestampOnAbort) { boost::optional<Timestamp> commitTs = boost::none; auto opCtx = clientAndCtx1.second.get(); Timestamp ts1(5, 5); Timestamp ts2(6, 6); { WriteUnitOfWork wuow(opCtx); opCtx->recoveryUnit()->onCommit( [&](boost::optional<Timestamp> commitTime) { commitTs = commitTime; }); ASSERT(!commitTs); ASSERT_OK(opCtx->recoveryUnit()->setTimestamp(ts2)); ASSERT(!commitTs); } ASSERT(!commitTs); opCtx->recoveryUnit()->setCommitTimestamp(ts1); ASSERT(!commitTs); { WriteUnitOfWork wuow(opCtx); opCtx->recoveryUnit()->onCommit( [&](boost::optional<Timestamp> commitTime) { commitTs = commitTime; }); ASSERT(!commitTs); } ASSERT(!commitTs); }
TEST_F(WiredTigerRecoveryUnitTestFixture, CommitWithDurableTimestamp) { auto opCtx = clientAndCtx1.second.get(); Timestamp ts1(3, 3); Timestamp ts2(5, 5); opCtx->recoveryUnit()->setCommitTimestamp(ts1); opCtx->recoveryUnit()->setDurableTimestamp(ts2); auto durableTs = opCtx->recoveryUnit()->getDurableTimestamp(); ASSERT_EQ(ts2, durableTs); { WriteUnitOfWork wuow(opCtx); wuow.commit(); } }
TEST_F(SnapshotManagerTests, FailsWithNoCommittedSnapshot) { if (!snapshotManager) return; // This test is only for engines that DO support SnapshotMangers. auto op = makeOperation(); auto ru = op->recoveryUnit(); // Before first snapshot is created. ASSERT_EQ(ru->setReadFromMajorityCommittedSnapshot(), ErrorCodes::ReadConcernMajorityNotAvailableYet); // There is a snapshot but it isn't committed. auto name = prepareAndCreateSnapshot(); ASSERT_EQ(ru->setReadFromMajorityCommittedSnapshot(), ErrorCodes::ReadConcernMajorityNotAvailableYet); // Now there is a committed snapshot. snapshotManager->setCommittedSnapshot(name); ASSERT_OK(ru->setReadFromMajorityCommittedSnapshot()); // Not anymore! snapshotManager->dropAllSnapshots(); ASSERT_EQ(ru->setReadFromMajorityCommittedSnapshot(), ErrorCodes::ReadConcernMajorityNotAvailableYet); }
TEST_F(SnapshotManagerTests, ConsistentIfNotSupported) { if (snapshotManager) return; // This test is only for engines that DON'T support SnapshotMangers. auto op = makeOperation(); auto ru = op->recoveryUnit(); ASSERT(!ru->isReadingFromMajorityCommittedSnapshot()); ASSERT(!ru->getMajorityCommittedSnapshot()); }
TEST_F(WiredTigerRecoveryUnitTestFixture, ChangeIsPassedCommitTimestamp) { boost::optional<Timestamp> commitTs = boost::none; auto opCtx = clientAndCtx1.second.get(); Timestamp ts1(5, 5); opCtx->recoveryUnit()->setCommitTimestamp(ts1); ASSERT(!commitTs); { WriteUnitOfWork wuow(opCtx); opCtx->recoveryUnit()->onCommit( [&](boost::optional<Timestamp> commitTime) { commitTs = commitTime; }); ASSERT(!commitTs); wuow.commit(); ASSERT_EQ(*commitTs, ts1); } ASSERT_EQ(*commitTs, ts1); }
TEST_F(WiredTigerRecoveryUnitTestFixture, CommitWithoutDurableTimestamp) { auto opCtx = clientAndCtx1.second.get(); Timestamp ts1(5, 5); opCtx->recoveryUnit()->setCommitTimestamp(ts1); { WriteUnitOfWork wuow(opCtx); wuow.commit(); } }
TEST_F(SnapshotManagerTests, FailsAfterDropAllSnapshotsWhileYielded) { if (!snapshotManager) return; // This test is only for engines that DO support SnapshotMangers. auto op = makeOperation(); // Start an operation using a committed snapshot. auto name = prepareAndCreateSnapshot(); snapshotManager->setCommittedSnapshot(name); ASSERT_OK(op->recoveryUnit()->setReadFromMajorityCommittedSnapshot()); ASSERT_EQ(itCountOn(op), 0); // acquires a snapshot. // Everything still works until we abandon our snapshot. snapshotManager->dropAllSnapshots(); ASSERT_EQ(itCountOn(op), 0); // Now it doesn't. op->recoveryUnit()->abandonSnapshot(); ASSERT_THROWS_CODE( itCountOn(op), UserException, ErrorCodes::ReadConcernMajorityNotAvailableYet); }
TEST_F(WiredTigerRecoveryUnitTestFixture, ChangeIsPassedEmptyLastTimestampSetOnCommitWithNoTimestamp) { boost::optional<Timestamp> commitTs = boost::none; auto opCtx = clientAndCtx1.second.get(); { WriteUnitOfWork wuow(opCtx); opCtx->recoveryUnit()->onCommit( [&](boost::optional<Timestamp> commitTime) { commitTs = commitTime; }); wuow.commit(); } ASSERT(!commitTs); }
ServiceContext::UniqueOperationContext ServiceContext::makeOperationContext(Client* client) { auto opCtx = std::make_unique<OperationContext>(client, _nextOpId.fetchAndAdd(1)); onCreate(opCtx.get(), _clientObservers); if (!opCtx->lockState()) { opCtx->setLockState(std::make_unique<LockerNoop>()); } if (!opCtx->recoveryUnit()) { opCtx->setRecoveryUnit(std::make_unique<RecoveryUnitNoop>(), WriteUnitOfWork::RecoveryUnitState::kNotInUnitOfWork); } { stdx::lock_guard<Client> lk(*client); client->setOperationContext(opCtx.get()); } return UniqueOperationContext(opCtx.release()); };
void ReplicationRecoveryImpl::_reconstructPreparedTransactions(OperationContext* opCtx) { DBDirectClient client(opCtx); const auto cursor = client.query(NamespaceString::kSessionTransactionsTableNamespace, {BSON("state" << "prepared")}); // Iterate over each entry in the transactions table that has a prepared transaction. while (cursor->more()) { const auto txnRecordObj = cursor->next(); const auto txnRecord = SessionTxnRecord::parse( IDLParserErrorContext("recovering prepared transaction"), txnRecordObj); invariant(txnRecord.getState() == DurableTxnStateEnum::kPrepared); // Get the prepareTransaction oplog entry corresponding to this transactions table entry. invariant(!opCtx->recoveryUnit()->getPointInTimeReadTimestamp()); const auto prepareOpTime = txnRecord.getLastWriteOpTime(); invariant(!prepareOpTime.isNull()); TransactionHistoryIterator iter(prepareOpTime); invariant(iter.hasNext()); const auto prepareOplogEntry = iter.next(opCtx); { // Make a new opCtx so that we can set the lsid when applying the prepare transaction // oplog entry. auto newClient = opCtx->getServiceContext()->makeClient("reconstruct-prepared-transactions"); AlternativeClientRegion acr(newClient); const auto newOpCtx = cc().makeOperationContext(); repl::UnreplicatedWritesBlock uwb(newOpCtx.get()); // Snapshot transaction can never conflict with the PBWM lock. newOpCtx->lockState()->setShouldConflictWithSecondaryBatchApplication(false); // TODO: SERVER-40177 This should be removed once it is guaranteed operations applied on // recovering nodes cannot encounter unnecessary prepare conflicts. newOpCtx->recoveryUnit()->setIgnorePrepared(true); // Checks out the session, applies the operations and prepares the transactions. uassertStatusOK(applyRecoveredPrepareTransaction(newOpCtx.get(), prepareOplogEntry)); } } }
TEST_F(SnapshotManagerTests, BasicFunctionality) { if (!snapshotManager) return; // This test is only for engines that DO support SnapshotMangers. // Snapshot variables are named according to the size of the RecordStore at the time of the // snapshot. auto snap0 = prepareAndCreateSnapshot(); insertRecordAndCommit(); auto snap1 = prepareAndCreateSnapshot(); insertRecordAndCommit(); prepareSnapshot(); insertRecordAndCommit(); auto snap2 = createSnapshot(); { auto op = makeOperation(); WriteUnitOfWork wuow(op); insertRecord(op); prepareSnapshot(); // insert should still be invisible. ASSERT_EQ(itCountOn(snapshotOperation), 3); wuow.commit(); } auto snap3 = createSnapshot(); { auto op = makeOperation(); WriteUnitOfWork wuow(op); insertRecord(op); // rolling back wuow } auto snap4 = prepareAndCreateSnapshot(); // If these fail, everything is busted. snapshotManager->setCommittedSnapshot(snap0); ASSERT_EQ(itCountCommitted(), 0); snapshotManager->setCommittedSnapshot(snap1); ASSERT_EQ(itCountCommitted(), 1); // If this fails, the snapshot is from the 'create' time rather than the 'prepare' time. snapshotManager->setCommittedSnapshot(snap2); ASSERT_EQ(itCountCommitted(), 2); // If this fails, the snapshot contains writes that weren't yet committed. snapshotManager->setCommittedSnapshot(snap3); ASSERT_EQ(itCountCommitted(), 3); // This op should keep its original snapshot until abandoned. auto longOp = makeOperation(); ASSERT_OK(longOp->recoveryUnit()->setReadFromMajorityCommittedSnapshot()); ASSERT_EQ(itCountOn(longOp), 3); // If this fails, the snapshot contains writes that were rolled back. snapshotManager->setCommittedSnapshot(snap4); ASSERT_EQ(itCountCommitted(), 4); // If this fails, longOp changed snapshots at an illegal time. ASSERT_EQ(itCountOn(longOp), 3); // If this fails, snapshots aren't preserved while in use. snapshotManager->cleanupUnneededSnapshots(); ASSERT_EQ(itCountOn(longOp), 3); // If this fails, longOp didn't get a new snapshot when it should have. longOp->recoveryUnit()->abandonSnapshot(); ASSERT_EQ(itCountOn(longOp), 4); }