ChunkVersion forceShardFilteringMetadataRefresh(OperationContext* opCtx, const NamespaceString& nss, bool forceRefreshFromThisThread) { invariant(!opCtx->lockState()->isLocked()); invariant(!opCtx->getClient()->isInDirectClient()); auto const shardingState = ShardingState::get(opCtx); invariant(shardingState->canAcceptShardedCommands()); const auto routingInfo = uassertStatusOK(Grid::get(opCtx)->catalogCache()->getCollectionRoutingInfoWithRefresh( opCtx, nss, forceRefreshFromThisThread)); const auto cm = routingInfo.cm(); if (!cm) { // No chunk manager, so unsharded. // Exclusive collection lock needed since we're now changing the metadata AutoGetCollection autoColl(opCtx, nss, MODE_IX, MODE_X); auto css = CollectionShardingState::get(opCtx, nss); css->refreshMetadata(opCtx, nullptr); return ChunkVersion::UNSHARDED(); } { AutoGetCollection autoColl(opCtx, nss, MODE_IS); auto metadata = CollectionShardingState::get(opCtx, nss)->getMetadata(opCtx); // We already have newer version if (metadata && metadata->getCollVersion().epoch() == cm->getVersion().epoch() && metadata->getCollVersion() >= cm->getVersion()) { LOG(1) << "Skipping refresh of metadata for " << nss << " " << metadata->getCollVersion() << " with an older " << cm->getVersion(); return metadata->getShardVersion(); } } // Exclusive collection lock needed since we're now changing the metadata AutoGetCollection autoColl(opCtx, nss, MODE_IX, MODE_X); auto css = CollectionShardingState::get(opCtx, nss); auto metadata = css->getMetadata(opCtx); // We already have newer version if (metadata && metadata->getCollVersion().epoch() == cm->getVersion().epoch() && metadata->getCollVersion() >= cm->getVersion()) { LOG(1) << "Skipping refresh of metadata for " << nss << " " << metadata->getCollVersion() << " with an older " << cm->getVersion(); return metadata->getShardVersion(); } std::unique_ptr<CollectionMetadata> newCollectionMetadata = stdx::make_unique<CollectionMetadata>(cm, shardingState->getShardName()); css->refreshMetadata(opCtx, std::move(newCollectionMetadata)); return css->getMetadata(opCtx)->getShardVersion(); }
void ChunkManagerTargeter::noteStaleResponse(const ShardEndpoint& endpoint, const StaleConfigInfo& staleInfo) { dassert(!_needsTargetingRefresh); ChunkVersion remoteShardVersion; if (!staleInfo.getVersionWanted()) { // If we don't have a vWanted sent, assume the version is higher than our current version. remoteShardVersion = getShardVersion(*_routingInfo, endpoint.shardName); remoteShardVersion.incMajor(); } else { remoteShardVersion = *staleInfo.getVersionWanted(); } ShardVersionMap::iterator it = _remoteShardVersions.find(endpoint.shardName); if (it == _remoteShardVersions.end()) { _remoteShardVersions.insert(std::make_pair(endpoint.shardName, remoteShardVersion)); } else { ChunkVersion& previouslyNotedVersion = it->second; if (previouslyNotedVersion.epoch() == remoteShardVersion.epoch()) { if (previouslyNotedVersion.isOlderThan(remoteShardVersion)) { previouslyNotedVersion = remoteShardVersion; } } else { // Epoch changed midway while applying the batch so set the version to something // unique // and non-existent to force a reload when refreshIsNeeded is called. previouslyNotedVersion = ChunkVersion::IGNORED(); } } }
Status onShardVersionMismatch(OperationContext* opCtx, const NamespaceString& nss, ChunkVersion shardVersionReceived, bool forceRefreshFromThisThread) noexcept { invariant(!opCtx->lockState()->isLocked()); invariant(!opCtx->getClient()->isInDirectClient()); auto const shardingState = ShardingState::get(opCtx); invariant(shardingState->canAcceptShardedCommands()); LOG(2) << "Metadata refresh requested for " << nss.ns() << " at shard version " << shardVersionReceived; ShardingStatistics::get(opCtx).countStaleConfigErrors.addAndFetch(1); // Ensure any ongoing migrations have completed before trying to do the refresh. This wait is // just an optimization so that MongoS does not exhaust its maximum number of StaleConfig retry // attempts while the migration is being committed. try { auto& oss = OperationShardingState::get(opCtx); oss.waitForMigrationCriticalSectionSignal(opCtx); } catch (const DBException& ex) { return ex.toStatus(); } const auto currentShardVersion = [&] { AutoGetCollection autoColl(opCtx, nss, MODE_IS); const auto currentMetadata = CollectionShardingState::get(opCtx, nss)->getMetadata(opCtx); if (currentMetadata) { return currentMetadata->getShardVersion(); } return ChunkVersion::UNSHARDED(); }(); if (currentShardVersion.epoch() == shardVersionReceived.epoch() && currentShardVersion.majorVersion() >= shardVersionReceived.majorVersion()) { // Don't need to remotely reload if we're in the same epoch and the requested version is // smaller than the one we know about. This means that the remote side is behind. return Status::OK(); } try { forceShardFilteringMetadataRefresh(opCtx, nss, forceRefreshFromThisThread); return Status::OK(); } catch (const DBException& ex) { log() << "Failed to refresh metadata for collection" << nss << causedBy(redact(ex)); return ex.toStatus(); } }
bool CollectionShardingState::_checkShardVersionOk(OperationContext* txn, string* errmsg, ChunkVersion* expectedShardVersion, ChunkVersion* actualShardVersion) { Client* client = txn->getClient(); // Operations using the DBDirectClient are unversioned. if (client->isInDirectClient()) { return true; } if (!repl::ReplicationCoordinator::get(txn)->canAcceptWritesForDatabase(_nss.db())) { // Right now connections to secondaries aren't versioned at all. return true; } const auto& oss = OperationShardingState::get(txn); // If there is a version attached to the OperationContext, use it as the received version. // Otherwise, get the received version from the ShardedConnectionInfo. if (oss.hasShardVersion()) { *expectedShardVersion = oss.getShardVersion(_nss); } else { ShardedConnectionInfo* info = ShardedConnectionInfo::get(client, false); if (!info) { // There is no shard version information on either 'txn' or 'client'. This means that // the operation represented by 'txn' is unversioned, and the shard version is always OK // for unversioned operations. return true; } *expectedShardVersion = info->getVersion(_nss.ns()); } if (ChunkVersion::isIgnoredVersion(*expectedShardVersion)) { return true; } // Set this for error messaging purposes before potentially returning false. auto metadata = getMetadata(); *actualShardVersion = metadata ? metadata->getShardVersion() : ChunkVersion::UNSHARDED(); if (_sourceMgr && _sourceMgr->getMigrationCriticalSectionSignal()) { *errmsg = str::stream() << "migration commit in progress for " << _nss.ns(); // Set migration critical section on operation sharding state: operation will wait for the // migration to finish before returning failure and retrying. OperationShardingState::get(txn).setMigrationCriticalSectionSignal( _sourceMgr->getMigrationCriticalSectionSignal()); return false; } if (expectedShardVersion->isWriteCompatibleWith(*actualShardVersion)) { return true; } // // Figure out exactly why not compatible, send appropriate error message // The versions themselves are returned in the error, so not needed in messages here // // Check epoch first, to send more meaningful message, since other parameters probably won't // match either. if (actualShardVersion->epoch() != expectedShardVersion->epoch()) { *errmsg = str::stream() << "version epoch mismatch detected for " << _nss.ns() << ", " << "the collection may have been dropped and recreated"; return false; } if (!actualShardVersion->isSet() && expectedShardVersion->isSet()) { *errmsg = str::stream() << "this shard no longer contains chunks for " << _nss.ns() << ", " << "the collection may have been dropped"; return false; } if (actualShardVersion->isSet() && !expectedShardVersion->isSet()) { *errmsg = str::stream() << "this shard contains versioned chunks for " << _nss.ns() << ", " << "but no version set in request"; return false; } if (actualShardVersion->majorVersion() != expectedShardVersion->majorVersion()) { // Could be > or < - wanted is > if this is the source of a migration, wanted < if this is // the target of a migration *errmsg = str::stream() << "version mismatch detected for " << _nss.ns(); return false; } // Those are all the reasons the versions can mismatch MONGO_UNREACHABLE; }
bool CollectionShardingState::_checkShardVersionOk(OperationContext* opCtx, string* errmsg, ChunkVersion* expectedShardVersion, ChunkVersion* actualShardVersion) { Client* client = opCtx->getClient(); auto& oss = OperationShardingState::get(opCtx); // If there is a version attached to the OperationContext, use it as the received version. // Otherwise, get the received version from the ShardedConnectionInfo. if (oss.hasShardVersion()) { *expectedShardVersion = oss.getShardVersion(_nss); } else { ShardedConnectionInfo* info = ShardedConnectionInfo::get(client, false); if (!info) { // There is no shard version information on either 'opCtx' or 'client'. This means that // the operation represented by 'opCtx' is unversioned, and the shard version is always // OK for unversioned operations. return true; } *expectedShardVersion = info->getVersion(_nss.ns()); } // An operation with read concern 'available' should never have shardVersion set. invariant(repl::ReadConcernArgs::get(opCtx).getLevel() != repl::ReadConcernLevel::kAvailableReadConcern); if (ChunkVersion::isIgnoredVersion(*expectedShardVersion)) { return true; } // Set this for error messaging purposes before potentially returning false. auto metadata = getMetadata(); *actualShardVersion = metadata ? metadata->getShardVersion() : ChunkVersion::UNSHARDED(); if (_sourceMgr) { const bool isReader = !opCtx->lockState()->isWriteLocked(); auto criticalSectionSignal = _sourceMgr->getMigrationCriticalSectionSignal(isReader); if (criticalSectionSignal) { *errmsg = str::stream() << "migration commit in progress for " << _nss.ns(); // Set migration critical section on operation sharding state: operation will wait for // the migration to finish before returning failure and retrying. oss.setMigrationCriticalSectionSignal(criticalSectionSignal); return false; } } if (expectedShardVersion->isWriteCompatibleWith(*actualShardVersion)) { return true; } // // Figure out exactly why not compatible, send appropriate error message // The versions themselves are returned in the error, so not needed in messages here // // Check epoch first, to send more meaningful message, since other parameters probably won't // match either. if (actualShardVersion->epoch() != expectedShardVersion->epoch()) { *errmsg = str::stream() << "version epoch mismatch detected for " << _nss.ns() << ", " << "the collection may have been dropped and recreated"; return false; } if (!actualShardVersion->isSet() && expectedShardVersion->isSet()) { *errmsg = str::stream() << "this shard no longer contains chunks for " << _nss.ns() << ", " << "the collection may have been dropped"; return false; } if (actualShardVersion->isSet() && !expectedShardVersion->isSet()) { *errmsg = str::stream() << "this shard contains versioned chunks for " << _nss.ns() << ", " << "but no version set in request"; return false; } if (actualShardVersion->majorVersion() != expectedShardVersion->majorVersion()) { // Could be > or < - wanted is > if this is the source of a migration, wanted < if this is // the target of a migration *errmsg = str::stream() << "version mismatch detected for " << _nss.ns(); return false; } // Those are all the reasons the versions can mismatch MONGO_UNREACHABLE; }
void CollectionShardingState::checkShardVersionOrThrow(OperationContext* opCtx) { const auto optReceivedShardVersion = getOperationReceivedVersion(opCtx, _nss); if (!optReceivedShardVersion) return; const auto& receivedShardVersion = *optReceivedShardVersion; if (ChunkVersion::isIgnoredVersion(receivedShardVersion)) { return; } // An operation with read concern 'available' should never have shardVersion set. invariant(repl::ReadConcernArgs::get(opCtx).getLevel() != repl::ReadConcernLevel::kAvailableReadConcern); const auto metadata = getCurrentMetadata(); const auto wantedShardVersion = metadata->isSharded() ? metadata->getShardVersion() : ChunkVersion::UNSHARDED(); auto criticalSectionSignal = [&] { auto csrLock = CSRLock::lock(opCtx, this); return _critSec.getSignal(opCtx->lockState()->isWriteLocked() ? ShardingMigrationCriticalSection::kWrite : ShardingMigrationCriticalSection::kRead); }(); if (criticalSectionSignal) { // Set migration critical section on operation sharding state: operation will wait for the // migration to finish before returning failure and retrying. auto& oss = OperationShardingState::get(opCtx); oss.setMigrationCriticalSectionSignal(criticalSectionSignal); uasserted(StaleConfigInfo(_nss, receivedShardVersion, wantedShardVersion), str::stream() << "migration commit in progress for " << _nss.ns()); } if (receivedShardVersion.isWriteCompatibleWith(wantedShardVersion)) { return; } // // Figure out exactly why not compatible, send appropriate error message // The versions themselves are returned in the error, so not needed in messages here // StaleConfigInfo sci(_nss, receivedShardVersion, wantedShardVersion); uassert(std::move(sci), str::stream() << "epoch mismatch detected for " << _nss.ns() << ", " << "the collection may have been dropped and recreated", wantedShardVersion.epoch() == receivedShardVersion.epoch()); if (!wantedShardVersion.isSet() && receivedShardVersion.isSet()) { uasserted(std::move(sci), str::stream() << "this shard no longer contains chunks for " << _nss.ns() << ", " << "the collection may have been dropped"); } if (wantedShardVersion.isSet() && !receivedShardVersion.isSet()) { uasserted(std::move(sci), str::stream() << "this shard contains chunks for " << _nss.ns() << ", " << "but the client expects unsharded collection"); } if (wantedShardVersion.majorVersion() != receivedShardVersion.majorVersion()) { // Could be > or < - wanted is > if this is the source of a migration, wanted < if this is // the target of a migration uasserted(std::move(sci), str::stream() << "version mismatch detected for " << _nss.ns()); } // Those are all the reasons the versions can mismatch MONGO_UNREACHABLE; }