/** * Insert an item at the back of this queue. * * @param item the item to insert **/ void push(T &&item) { emplace(std::move(item)); }
boost::optional<Date_t> CollectionRangeDeleter::cleanUpNextRange( OperationContext* opCtx, NamespaceString const& nss, OID const& epoch, int maxToDelete, CollectionRangeDeleter* forTestOnly) { StatusWith<int> wrote = 0; auto range = boost::optional<ChunkRange>(boost::none); auto notification = DeleteNotification(); { UninterruptibleLockGuard noInterrupt(opCtx->lockState()); AutoGetCollection autoColl(opCtx, nss, MODE_IX); auto* const collection = autoColl.getCollection(); auto* const css = CollectionShardingRuntime::get(opCtx, nss); auto& metadataManager = css->_metadataManager; auto* const self = forTestOnly ? forTestOnly : &metadataManager->_rangesToClean; const auto scopedCollectionMetadata = metadataManager->getActiveMetadata(metadataManager, boost::none); if (!scopedCollectionMetadata) { LOG(0) << "Abandoning any range deletions because the metadata for " << nss.ns() << " was reset"; stdx::lock_guard<stdx::mutex> lk(css->_metadataManager->_managerLock); css->_metadataManager->_clearAllCleanups(lk); return boost::none; } const auto& metadata = *scopedCollectionMetadata; if (!forTestOnly && (!collection || !metadata->isSharded())) { if (!collection) { LOG(0) << "Abandoning any range deletions left over from dropped " << nss.ns(); } else { LOG(0) << "Abandoning any range deletions left over from previously sharded" << nss.ns(); } stdx::lock_guard<stdx::mutex> lk(css->_metadataManager->_managerLock); css->_metadataManager->_clearAllCleanups(lk); return boost::none; } if (!forTestOnly && metadata->getCollVersion().epoch() != epoch) { LOG(1) << "Range deletion task for " << nss.ns() << " epoch " << epoch << " woke;" << " (current is " << metadata->getCollVersion() << ")"; return boost::none; } bool writeOpLog = false; { stdx::lock_guard<stdx::mutex> scopedLock(css->_metadataManager->_managerLock); if (self->isEmpty()) { LOG(1) << "No further range deletions scheduled on " << nss.ns(); return boost::none; } auto& orphans = self->_orphans; if (orphans.empty()) { // We have delayed deletions; see if any are ready. auto& df = self->_delayedOrphans.front(); if (df.whenToDelete > Date_t::now()) { LOG(0) << "Deferring deletion of " << nss.ns() << " range " << redact(df.range.toString()) << " until " << df.whenToDelete; return df.whenToDelete; } // Move a single range from _delayedOrphans to _orphans orphans.splice(orphans.end(), self->_delayedOrphans, self->_delayedOrphans.begin()); LOG(1) << "Proceeding with deferred deletion of " << nss.ns() << " range " << redact(orphans.front().range.toString()); writeOpLog = true; } invariant(!orphans.empty()); const auto& frontRange = orphans.front().range; range.emplace(frontRange.getMin().getOwned(), frontRange.getMax().getOwned()); notification = orphans.front().notification; } invariant(range); if (writeOpLog) { // Secondaries will watch for this update, and kill any queries that may depend on // documents in the range -- excepting any queries with a read-concern option // 'ignoreChunkMigration' try { AutoGetCollection autoAdmin( opCtx, NamespaceString::kServerConfigurationNamespace, MODE_IX); Helpers::upsert(opCtx, NamespaceString::kServerConfigurationNamespace.ns(), BSON("_id" << "startRangeDeletion" << "ns" << nss.ns() << "epoch" << epoch << "min" << range->getMin() << "max" << range->getMax())); } catch (const DBException& e) { stdx::lock_guard<stdx::mutex> scopedLock(css->_metadataManager->_managerLock); css->_metadataManager->_clearAllCleanups( scopedLock, e.toStatus("cannot push startRangeDeletion record to Op Log," " abandoning scheduled range deletions")); return boost::none; } } try { wrote = self->_doDeletion( opCtx, collection, metadata->getKeyPattern(), *range, maxToDelete); } catch (const DBException& e) { wrote = e.toStatus(); warning() << e.what(); } } // drop autoColl if (!wrote.isOK() || wrote.getValue() == 0) { if (wrote.isOK()) { LOG(0) << "No documents remain to delete in " << nss << " range " << redact(range->toString()); } // Wait for majority replication even when wrote isn't OK or == 0, because it might have // been OK and/or > 0 previously, and the deletions must be persistent before notifying // clients in _pop(). LOG(0) << "Waiting for majority replication of local deletions in " << nss.ns() << " range " << redact(range->toString()); repl::ReplClientInfo::forClient(opCtx->getClient()).setLastOpToSystemLastOpTime(opCtx); const auto clientOpTime = repl::ReplClientInfo::forClient(opCtx->getClient()).getLastOp(); // Wait for replication outside the lock const auto status = [&] { try { WriteConcernResult unusedWCResult; return waitForWriteConcern( opCtx, clientOpTime, kMajorityWriteConcern, &unusedWCResult); } catch (const DBException& e) { return e.toStatus(); } }(); // Get the lock again to finish off this range (including notifying, if necessary). // Don't allow lock interrupts while cleaning up. UninterruptibleLockGuard noInterrupt(opCtx->lockState()); AutoGetCollection autoColl(opCtx, nss, MODE_IX); auto* const css = CollectionShardingRuntime::get(opCtx, nss); auto& metadataManager = css->_metadataManager; auto* const self = forTestOnly ? forTestOnly : &metadataManager->_rangesToClean; stdx::lock_guard<stdx::mutex> scopedLock(css->_metadataManager->_managerLock); if (!status.isOK()) { LOG(0) << "Error when waiting for write concern after removing " << nss << " range " << redact(range->toString()) << " : " << redact(status.reason()); // If range were already popped (e.g. by dropping nss during the waitForWriteConcern // above) its notification would have been triggered, so this check suffices to ensure // that it is safe to pop the range here if (!notification.ready()) { invariant(!self->isEmpty() && self->_orphans.front().notification == notification); LOG(0) << "Abandoning deletion of latest range in " << nss.ns() << " after local " << "deletions because of replication failure"; self->_pop(status); } } else { LOG(0) << "Finished deleting documents in " << nss.ns() << " range " << redact(range->toString()); self->_pop(wrote.getStatus()); } if (!self->_orphans.empty()) { LOG(1) << "Deleting " << nss.ns() << " range " << redact(self->_orphans.front().range.toString()) << " next."; } return Date_t::now() + stdx::chrono::milliseconds{rangeDeleterBatchDelayMS.load()}; } invariant(range); invariant(wrote.getStatus()); invariant(wrote.getValue() > 0); notification.abandon(); return Date_t::now() + stdx::chrono::milliseconds{rangeDeleterBatchDelayMS.load()}; }
/** * Insert an item at the back of this queue. * * @param item the item to insert **/ void push(const T &item) { emplace(item); }
// Create a substitution that sends each occurrence of `d` to // its a corresponding `t`. Note that the kind and type of `t` // must agree with that of `d`. // // FIXME: If we use this during template argument deduction, do we // need this to act as a unifier (i.e. reject re-mappings of // previously deduced terms?). Probably. inline void Substitution::send(Decl& d, Term& t) { emplace(&d, &t); }