Status doTxn(OperationContext* opCtx, const std::string& dbName, const BSONObj& doTxnCmd, BSONObjBuilder* result) { auto txnNumber = opCtx->getTxnNumber(); uassert(ErrorCodes::InvalidOptions, "doTxn can only be run with a transaction ID.", txnNumber); auto txnParticipant = TransactionParticipant::get(opCtx); uassert(ErrorCodes::InvalidOptions, "doTxn must be run within a transaction", txnParticipant); invariant(txnParticipant->inMultiDocumentTransaction()); invariant(opCtx->getWriteUnitOfWork()); uassert( ErrorCodes::InvalidOptions, "doTxn supports only CRUD opts.", _areOpsCrudOnly(doTxnCmd)); auto hasPrecondition = _hasPrecondition(doTxnCmd); // Acquire global lock in IX mode so that the replication state check will remain valid. Lock::GlobalLock globalLock(opCtx, MODE_IX); auto replCoord = repl::ReplicationCoordinator::get(opCtx); bool userInitiatedWritesAndNotPrimary = opCtx->writesAreReplicated() && !replCoord->canAcceptWritesForDatabase(opCtx, dbName); if (userInitiatedWritesAndNotPrimary) return Status(ErrorCodes::NotMaster, str::stream() << "Not primary while applying ops to database " << dbName); int numApplied = 0; try { BSONObjBuilder intermediateResult; // The transaction takes place in a global unit of work, so the precondition check // and the writes will share the same snapshot. if (hasPrecondition) { uassertStatusOK(_checkPrecondition(opCtx, doTxnCmd, result)); } numApplied = 0; uassertStatusOK(_doTxn(opCtx, dbName, doTxnCmd, &intermediateResult, &numApplied)); txnParticipant->commitUnpreparedTransaction(opCtx); result->appendElements(intermediateResult.obj()); } catch (const DBException& ex) { txnParticipant->abortActiveUnpreparedOrStashPreparedTransaction(opCtx); BSONArrayBuilder ab; ++numApplied; for (int j = 0; j < numApplied; j++) ab.append(false); result->append("applied", numApplied); result->append("code", ex.code()); result->append("codeName", ErrorCodes::errorString(ex.code())); result->append("errmsg", ex.what()); result->append("results", ab.arr()); return Status(ErrorCodes::UnknownError, ex.what()); } return Status::OK(); }
Status applyOps(OperationContext* opCtx, const std::string& dbName, const BSONObj& applyOpCmd, BSONObjBuilder* result) { bool allowAtomic = false; uassertStatusOK( bsonExtractBooleanFieldWithDefault(applyOpCmd, "allowAtomic", true, &allowAtomic)); auto areOpsCrudOnly = _areOpsCrudOnly(applyOpCmd); auto isAtomic = allowAtomic && areOpsCrudOnly; auto hasPrecondition = _hasPrecondition(applyOpCmd); boost::optional<Lock::GlobalWrite> globalWriteLock; boost::optional<Lock::DBLock> dbWriteLock; // There's only one case where we are allowed to take the database lock instead of the global // lock - no preconditions; only CRUD ops; and non-atomic mode. if (!hasPrecondition && areOpsCrudOnly && !allowAtomic) { dbWriteLock.emplace(opCtx, dbName, MODE_X); } else { globalWriteLock.emplace(opCtx); } bool userInitiatedWritesAndNotPrimary = opCtx->writesAreReplicated() && !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(opCtx, dbName); if (userInitiatedWritesAndNotPrimary) return Status(ErrorCodes::NotMaster, str::stream() << "Not primary while applying ops to database " << dbName); if (hasPrecondition) { auto status = _checkPrecondition(opCtx, applyOpCmd, result); if (!status.isOK()) { return status; } } int numApplied = 0; if (!isAtomic) return _applyOps(opCtx, dbName, applyOpCmd, result, &numApplied); // Perform write ops atomically invariant(globalWriteLock); try { writeConflictRetry(opCtx, "applyOps", dbName, [&] { BSONObjBuilder intermediateResult; WriteUnitOfWork wunit(opCtx); numApplied = 0; { // Suppress replication for atomic operations until end of applyOps. repl::UnreplicatedWritesBlock uwb(opCtx); uassertStatusOK( _applyOps(opCtx, dbName, applyOpCmd, &intermediateResult, &numApplied)); } // Generate oplog entry for all atomic ops collectively. if (opCtx->writesAreReplicated()) { // We want this applied atomically on slaves so we rewrite the oplog entry without // the pre-condition for speed. BSONObjBuilder cmdBuilder; for (auto elem : applyOpCmd) { auto name = elem.fieldNameStringData(); if (name == kPreconditionFieldName) continue; if (name == "bypassDocumentValidation") continue; cmdBuilder.append(elem); } const BSONObj cmdRewritten = cmdBuilder.done(); auto opObserver = getGlobalServiceContext()->getOpObserver(); invariant(opObserver); opObserver->onApplyOps(opCtx, dbName, cmdRewritten); } wunit.commit(); result->appendElements(intermediateResult.obj()); }); } catch (const DBException& ex) { if (ex.getCode() == ErrorCodes::NamespaceNotFound) { // Retry in non-atomic mode, since MMAP cannot implicitly create a new database // within an active WriteUnitOfWork. return _applyOps(opCtx, dbName, applyOpCmd, result, &numApplied); } BSONArrayBuilder ab; ++numApplied; for (int j = 0; j < numApplied; j++) ab.append(false); result->append("applied", numApplied); result->append("code", ex.getCode()); result->append("codeName", ErrorCodes::errorString(ErrorCodes::fromInt(ex.getCode()))); result->append("errmsg", ex.what()); result->append("results", ab.arr()); return Status(ErrorCodes::UnknownError, ex.what()); } return Status::OK(); }