/** * Copyright (C) 2020-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mongo/base/status.h" #include "mongo/base/status_with.h" #include "mongo/base/string_data.h" #include "mongo/bson/bsonmisc.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/timestamp.h" #include "mongo/client/dbclient_cursor.h" #include "mongo/db/admission/execution_admission_context.h" #include "mongo/db/client.h" #include "mongo/db/cluster_role.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/logical_time.h" #include "mongo/db/logical_time_validator.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" #include "mongo/db/persistent_task_store.h" #include "mongo/db/query/find_command.h" #include "mongo/db/repl/optime.h" #include "mongo/db/repl/replica_set_aware_service.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/s/topology_time_ticker.h" #include "mongo/db/server_options.h" #include "mongo/db/service_context.h" #include "mongo/db/vector_clock.h" #include "mongo/db/vector_clock_document_gen.h" #include "mongo/db/vector_clock_mongod.h" #include "mongo/db/vector_clock_mutable.h" #include "mongo/executor/task_executor_pool.h" #include "mongo/logv2/log.h" #include "mongo/logv2/log_attr.h" #include "mongo/logv2/log_component.h" #include "mongo/s/catalog/type_shard.h" #include "mongo/s/grid.h" #include "mongo/util/assert_util.h" #include "mongo/util/decorable.h" #include "mongo/util/future.h" #include "mongo/util/future_impl.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault namespace mongo { namespace { const auto vectorClockMongoDDecoration = ServiceContext::declareDecoration(); const ReplicaSetAwareServiceRegistry::Registerer vectorClockMongoDServiceRegisterer("VectorClockMongoD-ReplicaSetAwareServiceRegistration"); const ServiceContext::ConstructorActionRegisterer vectorClockMongoDRegisterer( "VectorClockMongoD", {"VectorClock"}, [](ServiceContext* service) { VectorClockMongoD::registerVectorClockOnServiceContext( service, &vectorClockMongoDDecoration(service)); }); } // namespace VectorClockMongoD* VectorClockMongoD::get(ServiceContext* serviceContext) { return &vectorClockMongoDDecoration(serviceContext); } VectorClockMongoD::~VectorClockMongoD() { // Wait until _taskIsRunning changes value FROM true _taskIsRunning.wait(true); } void VectorClockMongoD::onStepUpBegin(OperationContext* opCtx, long long term) { stdx::lock_guard lg(_mutex); _durableTime.reset(); } void VectorClockMongoD::onStepDown() { stdx::lock_guard lg(_mutex); _durableTime.reset(); } void VectorClockMongoD::onShutdown() { stdx::lock_guard lg(_mutex); _shutdownInitiated.store(true); } void VectorClockMongoD::onConsistentDataAvailable(OperationContext* opCtx, bool isMajority, bool isRollback) { // TODO (SERVER-91505): Determine if we should reload in-memory states on rollback. if (isRollback) { return; } if (serverGlobalParams.clusterRole.has(ClusterRole::ConfigServer)) { const auto maxTopologyTime{[&opCtx]() -> boost::optional { DBDirectClient client{opCtx}; FindCommandRequest findRequest{NamespaceString::kConfigsvrShardsNamespace}; findRequest.setSort(BSON(ShardType::topologyTime << -1)); findRequest.setLimit(1); auto cursor{client.find(std::move(findRequest))}; invariant(cursor); if (!cursor->more()) { // No shards are available yet. return boost::none; } const auto shardEntry{uassertStatusOK(ShardType::fromBSON(cursor->nextSafe()))}; return shardEntry.getTopologyTime(); }()}; if (maxTopologyTime) { if (isMajority) { // The maxTopologyTime is majority committed. Thus, we can start gossiping it. _advanceComponentTimeTo(Component::TopologyTime, LogicalTime(*maxTopologyTime)); } else { // There is no guarantee that the maxTopologyTime is majority committed and we don't // have a way to obtain the commit time associated with it (init sync scenario). // The only guarantee that we have at this point is that any majority read // that comes afterwards will read, at least, from the initialDataTimestamp. Thus, // we introduce an artificial tick point . const auto initialDataTimestamp = repl::ReplicationCoordinator::get(opCtx)->getMyLastAppliedOpTime(); TopologyTimeTicker::get(opCtx).onNewLocallyCommittedTopologyTimeAvailable( initialDataTimestamp.getTimestamp(), *maxTopologyTime); } } } } void VectorClockMongoD::onBecomeArbiter() { // The node has become an arbiter, hence will not need logical clock for external operations. _disable(); if (auto validator = LogicalTimeValidator::get(_service)) { validator->stopKeyManager(); } } SharedSemiFuture VectorClockMongoD::waitForDurableConfigTime() { auto time = getTime(); stdx::unique_lock ul(_mutex); if (_durableTime && _durableTime->configTime() >= time.configTime()) return SharedSemiFuture(); return _enqueueWaiterAndStartDurableTaskIfNeeded(ul, std::move(time)); } SharedSemiFuture VectorClockMongoD::waitForDurableTopologyTime() { auto time = getTime(); stdx::unique_lock ul(_mutex); if (_durableTime && _durableTime->topologyTime() >= time.topologyTime()) return SharedSemiFuture(); return _enqueueWaiterAndStartDurableTaskIfNeeded(ul, std::move(time)); } SharedSemiFuture VectorClockMongoD::waitForDurable() { auto time = getTime(); stdx::unique_lock ul(_mutex); if (_durableTime && _durableTime->configTime() >= time.configTime() && _durableTime->topologyTime() >= time.topologyTime()) return SharedSemiFuture(); return _enqueueWaiterAndStartDurableTaskIfNeeded(ul, std::move(time)); } VectorClock::VectorTime VectorClockMongoD::recoverDirect(OperationContext* opCtx) { VectorClockDocument durableVectorClock; PersistentTaskStore store(NamespaceString::kVectorClockNamespace); store.forEach(opCtx, BSON(VectorClockDocument::k_idFieldName << durableVectorClock.get_id()), [&, numDocsFound = 0](const auto& doc) mutable { invariant(++numDocsFound == 1); durableVectorClock = doc; return true; }); const auto newDurableTime = VectorTime({VectorClock::kInitialComponentTime, LogicalTime(durableVectorClock.getConfigTime()), LogicalTime(durableVectorClock.getTopologyTime())}); // Make sure the VectorClock advances at least up to the just recovered durable time _advanceTime( {newDurableTime.clusterTime(), newDurableTime.configTime(), newDurableTime.topologyTime()}); LOGV2_DEBUG(1, 6011000, "Recovered persisted vector clock", "configTime"_attr = newDurableTime.configTime(), "topologyTime"_attr = newDurableTime.topologyTime()); return newDurableTime; } SharedSemiFuture VectorClockMongoD::_enqueueWaiterAndStartDurableTaskIfNeeded( WithLock lk, VectorTime time) { if (_shutdownInitiated.load()) { return SharedSemiFuture( Status(ErrorCodes::ShutdownInProgress, "Not persisting vector clock due to shutdown")); } auto& element = _queue.emplace_back(ComparableVectorTime{std::move(time)}, std::make_unique>()); auto retVal = element._promise->getFuture(); if (_taskIsRunning.load()) { // we already have a task running return retVal; } _taskIsRunning.store(true); _createPersisterTask().getAsync([](auto) {}); return retVal; } ExecutorFuture VectorClockMongoD::_createPersisterTask() { return ExecutorFuture(Grid::get(vectorClockMongoDDecoration.owner(this)) ->getExecutorPool() ->getFixedExecutor()) .then([this]() { while (true) { ThreadClient tc( "VectorClockStateOperation", vectorClockMongoDDecoration.owner(this)->getService(ClusterRole::ShardServer)); const auto opCtxHolder = tc->makeOperationContext(); auto* const opCtx = opCtxHolder.get(); // This code is used by the TransactionCoordinator. As a result, we need to // skip ticket acquisition in order to prevent possible deadlock when // participants are in the prepared state. See SERVER-82883 and // SERVER-60682. ScopedAdmissionPriority skipTicketAcquisition( opCtx, AdmissionContext::Priority::kExempt); auto vectorTime = std::invoke([&]() { if (std::invoke([&] { stdx::lock_guard lg(_mutex); return !_durableTime; })) { return recoverDirect(opCtx); } auto vectorTime = getTime(); VectorClockDocument vcd; vcd.setConfigTime(vectorTime.configTime().asTimestamp()); vcd.setTopologyTime(vectorTime.topologyTime().asTimestamp()); PersistentTaskStore store( NamespaceString::kVectorClockNamespace); store.upsert(opCtx, BSON(VectorClockDocument::k_idFieldName << vcd.get_id()), vcd.toBSON(), WriteConcerns::kMajorityWriteConcernNoTimeout); return vectorTime; }); { stdx::lock_guard lg(_mutex); _durableTime = vectorTime; ComparableVectorTime comparableDurableTime{*_durableTime}; _queue.sort(); while (!_queue.empty() && comparableDurableTime >= _queue.front()._time) { _queue.front()._promise->emplaceValue(); _queue.pop_front(); } if (_queue.empty()) { _taskIsRunning.store(false); return; } } } }) .onError([this](Status status) { stdx::lock_guard lg(_mutex); for (const auto& element : _queue) { element._promise->setError(status); } _queue.clear(); _taskIsRunning.store(false); }); } LogicalTime VectorClockMongoD::_tick(Component component, uint64_t nTicks) { invariant(component == Component::ClusterTime); // Although conceptually ClusterTime can only be ticked when a mongod is able to take writes // (ie. primary, or standalone), this is handled at a higher layer. // // ClusterTime is ticked when replacing zero-valued Timestamps with the current time, which // is usually but not necessarily associated with writes. // // ClusterTime is ticked after winning an election, while persisting the stepUp to the // oplog, which is slightly before the repl state is changed to primary. // // As such, ticking ClusterTime is not restricted here based on repl state. return _advanceComponentTimeByTicks(component, nTicks); } void VectorClockMongoD::_tickTo(Component component, LogicalTime newTime) { const bool isClusterTime = component == Component::ClusterTime; const bool isConfigServer = serverGlobalParams.clusterRole.has(ClusterRole::ConfigServer); const bool isConfigOrTopologyTime = component == Component::ConfigTime || component == Component::TopologyTime; // The ClusterTime is allowed to tickTo in certain very limited and trusted cases (eg. // initializing based on oplog timestamps), so we have to allow it here. invariant(isClusterTime || (isConfigServer && isConfigOrTopologyTime)); _advanceComponentTimeTo(component, std::move(newTime)); } } // namespace mongo