From 94ff51257a099ee6af4a9c41ee61245918227760 Mon Sep 17 00:00:00 2001 From: Kevin Pulo Date: Thu, 16 Apr 2020 14:45:53 +1000 Subject: SERVER-46200 implement basic VectorClock service --- src/mongo/db/SConscript | 47 +++++ src/mongo/db/auth/auth_op_observer.h | 5 + src/mongo/db/client.h | 4 + src/mongo/db/command_generic_argument.cpp | 5 +- src/mongo/db/logical_clock.cpp | 2 + src/mongo/db/repl/SConscript | 1 + src/mongo/db/replica_set_aware_service.h | 50 ++--- src/mongo/db/replica_set_aware_service_test.cpp | 49 ++++- src/mongo/db/s/SConscript | 3 + src/mongo/db/s/balancer/balancer.cpp | 14 +- src/mongo/db/s/balancer/balancer.h | 6 + src/mongo/db/s/config_server_op_observer.cpp | 6 +- src/mongo/db/s/config_server_op_observer_test.cpp | 7 +- src/mongo/db/s/vector_clock_config_server_test.cpp | 115 ++++++++++++ src/mongo/db/s/vector_clock_shard_server_test.cpp | 101 +++++++++++ src/mongo/db/service_entry_point_common.cpp | 3 + src/mongo/db/update/SConscript | 2 + src/mongo/db/vector_clock.cpp | 201 +++++++++++++++++++++ src/mongo/db/vector_clock.h | 148 +++++++++++++++ src/mongo/db/vector_clock_mongod.cpp | 179 ++++++++++++++++++ src/mongo/db/vector_clock_mongod_test.cpp | 101 +++++++++++ src/mongo/db/vector_clock_mutable.cpp | 130 +++++++++++++ src/mongo/db/vector_clock_mutable.h | 66 +++++++ src/mongo/db/vector_clock_trivial.cpp | 100 ++++++++++ 24 files changed, 1297 insertions(+), 48 deletions(-) create mode 100644 src/mongo/db/s/vector_clock_config_server_test.cpp create mode 100644 src/mongo/db/s/vector_clock_shard_server_test.cpp create mode 100644 src/mongo/db/vector_clock.cpp create mode 100644 src/mongo/db/vector_clock.h create mode 100644 src/mongo/db/vector_clock_mongod.cpp create mode 100644 src/mongo/db/vector_clock_mongod_test.cpp create mode 100644 src/mongo/db/vector_clock_mutable.cpp create mode 100644 src/mongo/db/vector_clock_mutable.h create mode 100644 src/mongo/db/vector_clock_trivial.cpp (limited to 'src/mongo/db') diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 47408b66a57..55595f5999b 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -1562,6 +1562,52 @@ env.Library( 'global_settings', 'logical_time', 'service_context', + 'vector_clock_mutable', + ], +) + +env.Library( + target='vector_clock', + source=[ + 'vector_clock.cpp', + ], + LIBDEPS=[ + 'logical_time', + 'service_context', + ], +) + +env.Library( + target='vector_clock_mutable', + source=[ + 'vector_clock_mutable.cpp', + ], + LIBDEPS=[ + 'vector_clock', + ], +) + +env.Library( + target='vector_clock_d', + source=[ + 'vector_clock_mongod.cpp', + ], + LIBDEPS=[ + 'vector_clock_mutable', + ], + LIBDEPS_PRIVATE=[ + 'replica_set_aware_service', + 'server_options_core', + ], +) + +env.Library( + target='vector_clock_trivial', + source=[ + 'vector_clock_trivial.cpp', + ], + LIBDEPS=[ + 'vector_clock_mutable', ], ) @@ -1834,6 +1880,7 @@ envWithAsio.CppUnitTest( 'transaction_participant_retryable_writes_test.cpp', 'transaction_participant_test.cpp', 'update_index_data_test.cpp', + 'vector_clock_mongod_test.cpp', 'write_concern_options_test.cpp', 'error_labels_test.cpp', env.Idlc('commands_test_example.idl')[0], diff --git a/src/mongo/db/auth/auth_op_observer.h b/src/mongo/db/auth/auth_op_observer.h index 66ddf72732e..5920146860e 100644 --- a/src/mongo/db/auth/auth_op_observer.h +++ b/src/mongo/db/auth/auth_op_observer.h @@ -182,6 +182,11 @@ public: void onMajorityCommitPointUpdate(ServiceContext* service, const repl::OpTime& newCommitPoint) final {} + + // Contains the fields of the document that are in the collection's shard key, and "_id". + static BSONObj getDocumentKey(OperationContext* opCtx, + NamespaceString const& nss, + BSONObj const& doc); }; } // namespace mongo diff --git a/src/mongo/db/client.h b/src/mongo/db/client.h index 3ffed27adc2..615902f2c80 100644 --- a/src/mongo/db/client.h +++ b/src/mongo/db/client.h @@ -138,6 +138,10 @@ public: return std::move(_session); } + transport::Session::TagMask getSessionTags() const { + return _session ? _session->getTags() : 0; + } + std::string clientAddress(bool includePort = false) const; const std::string& desc() const { return _desc; diff --git a/src/mongo/db/command_generic_argument.cpp b/src/mongo/db/command_generic_argument.cpp index 85556c371a6..89c3cd20a87 100644 --- a/src/mongo/db/command_generic_argument.cpp +++ b/src/mongo/db/command_generic_argument.cpp @@ -52,7 +52,7 @@ struct SpecialArgRecord { // If that changes, it should be added. When you add to this list, consider whether you // should also change the filterCommandRequestForPassthrough() function. // clang-format off -static constexpr std::array specials{{ +static constexpr std::array specials{{ // /-isGeneric // | /-stripFromRequest // | | /-stripFromReply @@ -84,7 +84,8 @@ static constexpr std::array specials{{ {"lastCommittedOpTime"_sd, 0, 0, 1}, {"readOnly"_sd, 0, 0, 1}, {"comment"_sd, 1, 0, 0}, - {"maxTimeMSOpOnly"_sd, 1, 0, 0}}}; + {"maxTimeMSOpOnly"_sd, 1, 0, 0}, + {"$configTime"_sd, 1, 1, 1}}}; // clang-format on template diff --git a/src/mongo/db/logical_clock.cpp b/src/mongo/db/logical_clock.cpp index 2026667272e..3cb7dc1b192 100644 --- a/src/mongo/db/logical_clock.cpp +++ b/src/mongo/db/logical_clock.cpp @@ -39,6 +39,7 @@ #include "mongo/db/operation_context.h" #include "mongo/db/service_context.h" #include "mongo/db/time_proof_service.h" +#include "mongo/db/vector_clock_mutable.h" #include "mongo/logv2/log.h" namespace mongo { @@ -100,6 +101,7 @@ Status LogicalClock::advanceClusterTime(const LogicalTime newTime) { } LogicalTime LogicalClock::reserveTicks(uint64_t nTicks) { + (void)VectorClockMutable::get(_service)->tick(VectorClock::Component::ClusterTime, nTicks); invariant(nTicks > 0 && nTicks <= kMaxSignedInt); diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index d8acb4fdf42..21dd50fe804 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -25,6 +25,7 @@ env.Library( 'repl_coordinator_interface', '$BUILD_DIR/mongo/db/logical_clock', '$BUILD_DIR/mongo/db/logical_time', + '$BUILD_DIR/mongo/db/vector_clock_mutable', '$BUILD_DIR/mongo/db/storage/flow_control', ], ) diff --git a/src/mongo/db/replica_set_aware_service.h b/src/mongo/db/replica_set_aware_service.h index 0c11a7c2976..4a3d01489a7 100644 --- a/src/mongo/db/replica_set_aware_service.h +++ b/src/mongo/db/replica_set_aware_service.h @@ -40,10 +40,14 @@ namespace mongo { * Using this interface avoids the need to manually hook the various places in * ReplicationCoordinatorExternalStateImpl where these events occur. * - * To define a ReplicaSetAwareService, a class needs to inherit from ReplicaSetAwareService - * (templated on itself), implement the pure virtual methods in ReplicaSetAwareInterface, and define - * a static ReplicaSetAwareServiceRegistry::Registerer to declare the name (and optionally - * pre-requisite services) of the service. + * To define a ReplicaSetAwareService, a class needs to: + * + * 1. Inherit from ReplicaSetAwareService (templated on itself). + * 2. Implement the pure virtual methods in ReplicaSetAwareInterface. + * 3. Store a singleton object of the class somewhere (ideally as a ServiceContext decoration). + * 4. Define a public static `get(ServiceContext*)` function. + * 5. Define a static ReplicaSetAwareServiceRegistry::Registerer object to declare the name (and + * optionally pre-requisite services) of the service. * * If the service should only be active in certain configurations, then the class should override * shouldRegisterReplicaSetAwareService. For the common cases of services that are only active on @@ -57,6 +61,8 @@ namespace mongo { * * class FooService : public ReplicaSetAwareService { * public: + * static FooService* get(ServiceContext* serviceContext); + * * // ... * * private: @@ -79,9 +85,15 @@ namespace mongo { * * namespace { * - * ReplicaSetAwareServiceRegistry::Registerer fooServiceRegisterer("FooService"); + * const auto _fooDecoration = ServiceContext::declareDecoration(); + * + * const ReplicaSetAwareServiceRegistry::Registerer _fooServiceRegisterer("FooService"); * * } // namespace + * + * FooService* FooService::get(ServiceContext* serviceContext) { + * return _fooDecoration(serviceContext); + * } */ /** @@ -175,28 +187,9 @@ class ReplicaSetAwareService : private ReplicaSetAwareInterface { public: virtual ~ReplicaSetAwareService() = default; - /** - * Retrieves the per-serviceContext instance of the ActualService. - */ - static ActualService* get(ServiceContext* serviceContext) { - return &_decoration(serviceContext); - } - - static ActualService* get(OperationContext* operationContext) { - return get(operationContext->getServiceContext()); - } - protected: ReplicaSetAwareService() = default; - /** - * Used when services need to get a reference to the serviceContext that they are decorating. - */ - ServiceContext* getServiceContext() { - auto* actualService = checked_cast(this); - return &_decoration.owner(*actualService); - } - private: friend ReplicaSetAwareServiceRegistry::Registerer; @@ -216,17 +209,8 @@ private: virtual bool shouldRegisterReplicaSetAwareService() const { return true; } - - // The decoration of the actual service on the ServiceContext. - // Definition of this static can't be inline, because it isn't constexpr. - static Decorable::Decoration _decoration; }; -template -Decorable::Decoration - ReplicaSetAwareService::_decoration = - ServiceContext::declareDecoration(); - /** * Convenience version of ReplicaSetAwareService that is only active on config servers. diff --git a/src/mongo/db/replica_set_aware_service_test.cpp b/src/mongo/db/replica_set_aware_service_test.cpp index 869db7355c0..4d1fddcff8e 100644 --- a/src/mongo/db/replica_set_aware_service_test.cpp +++ b/src/mongo/db/replica_set_aware_service_test.cpp @@ -44,15 +44,15 @@ public: int numCallsOnStepDown{0}; protected: - virtual void onStepUpBegin(OperationContext* opCtx) { + void onStepUpBegin(OperationContext* opCtx) override { numCallsOnStepUpBegin++; } - virtual void onStepUpComplete(OperationContext* opCtx) { + void onStepUpComplete(OperationContext* opCtx) override { numCallsOnStepUpComplete++; } - virtual void onStepDown() { + void onStepDown() override { numCallsOnStepDown++; } }; @@ -61,34 +61,57 @@ protected: * Service that's never registered. */ class ServiceA : public TestService { +public: + static ServiceA* get(ServiceContext* serviceContext); + private: - virtual bool shouldRegisterReplicaSetAwareService() const final { + bool shouldRegisterReplicaSetAwareService() const final { return false; } }; +const auto getServiceA = ServiceContext::declareDecoration(); + ReplicaSetAwareServiceRegistry::Registerer serviceARegisterer("ServiceA"); +ServiceA* ServiceA::get(ServiceContext* serviceContext) { + return &getServiceA(serviceContext); +} + /** * Service that's always registered. */ class ServiceB : public TestService { +public: + static ServiceB* get(ServiceContext* serviceContext); + private: - virtual bool shouldRegisterReplicaSetAwareService() const final { + bool shouldRegisterReplicaSetAwareService() const final { return true; } }; +const auto getServiceB = ServiceContext::declareDecoration(); + ReplicaSetAwareServiceRegistry::Registerer serviceBRegisterer("ServiceB"); +ServiceB* ServiceB::get(ServiceContext* serviceContext) { + return &getServiceB(serviceContext); +} + /** * Service that's always registered, depends on ServiceB. */ class ServiceC : public TestService { +public: + static ServiceC* get(ServiceContext* serviceContext); + private: - virtual bool shouldRegisterReplicaSetAwareService() const final { + ServiceContext* getServiceContext(); + + bool shouldRegisterReplicaSetAwareService() const final { return true; } @@ -110,8 +133,18 @@ private: } }; +const auto getServiceC = ServiceContext::declareDecoration(); + ReplicaSetAwareServiceRegistry::Registerer serviceCRegisterer("ServiceC", {"ServiceB"}); +ServiceC* ServiceC::get(ServiceContext* serviceContext) { + return &getServiceC(serviceContext); +} + +ServiceContext* ServiceC::getServiceContext() { + return getServiceC.owner(this); +} + using ReplicaSetAwareServiceTest = ServiceContextTest; @@ -127,9 +160,11 @@ TEST_F(ReplicaSetAwareServiceTest, ReplicaSetAwareService) { ASSERT_EQ(0, a->numCallsOnStepUpBegin); ASSERT_EQ(0, a->numCallsOnStepUpComplete); ASSERT_EQ(0, a->numCallsOnStepDown); + ASSERT_EQ(0, b->numCallsOnStepUpBegin); ASSERT_EQ(0, b->numCallsOnStepUpComplete); ASSERT_EQ(0, b->numCallsOnStepDown); + ASSERT_EQ(0, c->numCallsOnStepUpBegin); ASSERT_EQ(0, c->numCallsOnStepUpComplete); ASSERT_EQ(0, c->numCallsOnStepDown); @@ -144,9 +179,11 @@ TEST_F(ReplicaSetAwareServiceTest, ReplicaSetAwareService) { ASSERT_EQ(0, a->numCallsOnStepUpBegin); ASSERT_EQ(0, a->numCallsOnStepUpComplete); ASSERT_EQ(0, a->numCallsOnStepDown); + ASSERT_EQ(3, b->numCallsOnStepUpBegin); ASSERT_EQ(2, b->numCallsOnStepUpComplete); ASSERT_EQ(1, b->numCallsOnStepDown); + ASSERT_EQ(3, c->numCallsOnStepUpBegin); ASSERT_EQ(2, c->numCallsOnStepUpComplete); ASSERT_EQ(1, c->numCallsOnStepDown); diff --git a/src/mongo/db/s/SConscript b/src/mongo/db/s/SConscript index 66fd73a851d..6ad8bf9f6a8 100644 --- a/src/mongo/db/s/SConscript +++ b/src/mongo/db/s/SConscript @@ -100,6 +100,7 @@ env.Library( '$BUILD_DIR/mongo/db/server_options_core', '$BUILD_DIR/mongo/db/storage/remove_saver', '$BUILD_DIR/mongo/db/transaction', + '$BUILD_DIR/mongo/db/vector_clock_d', '$BUILD_DIR/mongo/s/client/shard_local', '$BUILD_DIR/mongo/s/query/cluster_aggregate', '$BUILD_DIR/mongo/s/sharding_initialization', @@ -359,6 +360,7 @@ env.CppUnitTest( 'sharding_logging_test.cpp', 'start_chunk_clone_request_test.cpp', 'type_shard_identity_test.cpp', + 'vector_clock_config_server_test.cpp', 'wait_for_majority_service_test.cpp', ], LIBDEPS=[ @@ -413,6 +415,7 @@ env.CppUnitTest( 'sharding_initialization_mongod_test.cpp', 'sharding_initialization_op_observer_test.cpp', 'split_vector_test.cpp', + 'vector_clock_shard_server_test.cpp', ], LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/db/auth/authmocks', diff --git a/src/mongo/db/s/balancer/balancer.cpp b/src/mongo/db/s/balancer/balancer.cpp index 1e80510985c..74e1a643016 100644 --- a/src/mongo/db/s/balancer/balancer.cpp +++ b/src/mongo/db/s/balancer/balancer.cpp @@ -156,17 +156,27 @@ void warnOnMultiVersion(const vector& cluste "shardVersions"_attr = shardVersions.done()); } -ReplicaSetAwareServiceRegistry::Registerer balancerRegisterer("Balancer"); +const auto _balancerDecoration = ServiceContext::declareDecoration(); + +const ReplicaSetAwareServiceRegistry::Registerer _balancerRegisterer("Balancer"); } // namespace +Balancer* Balancer::get(ServiceContext* serviceContext) { + return &_balancerDecoration(serviceContext); +} + +Balancer* Balancer::get(OperationContext* operationContext) { + return get(operationContext->getServiceContext()); +} + Balancer::Balancer() : _balancedLastTime(0), _random(std::random_device{}()), _clusterStats(std::make_unique(_random)), _chunkSelectionPolicy( std::make_unique(_clusterStats.get(), _random)), - _migrationManager(getServiceContext()) {} + _migrationManager(_balancerDecoration.owner(this)) {} Balancer::~Balancer() { diff --git a/src/mongo/db/s/balancer/balancer.h b/src/mongo/db/s/balancer/balancer.h index fdbbb79d1d5..eef505a014a 100644 --- a/src/mongo/db/s/balancer/balancer.h +++ b/src/mongo/db/s/balancer/balancer.h @@ -59,6 +59,12 @@ class Balancer : public ReplicaSetAwareServiceConfigSvr { Balancer& operator=(const Balancer&) = delete; public: + /** + * Provide access to the Balancer decoration on ServiceContext. + */ + static Balancer* get(ServiceContext* serviceContext); + static Balancer* get(OperationContext* operationContext); + Balancer(); ~Balancer(); diff --git a/src/mongo/db/s/config_server_op_observer.cpp b/src/mongo/db/s/config_server_op_observer.cpp index 13e1d91378e..a25fac1c989 100644 --- a/src/mongo/db/s/config_server_op_observer.cpp +++ b/src/mongo/db/s/config_server_op_observer.cpp @@ -34,9 +34,9 @@ #include "mongo/db/s/config_server_op_observer.h" #include "mongo/db/s/config/sharding_catalog_manager.h" +#include "mongo/db/vector_clock_mutable.h" #include "mongo/s/catalog/type_config_version.h" #include "mongo/s/cluster_identity_loader.h" -#include "mongo/s/grid.h" namespace mongo { @@ -94,8 +94,8 @@ void ConfigServerOpObserver::onReplicationRollback(OperationContext* opCtx, void ConfigServerOpObserver::onMajorityCommitPointUpdate(ServiceContext* service, const repl::OpTime& newCommitPoint) { - // TODO SERVER-46200: tick the VectorClock's ConfigTime. - Grid::get(service)->advanceConfigOpTimeAuthoritative(newCommitPoint); + VectorClockMutable::get(service)->tickTo(VectorClock::Component::ConfigTime, + LogicalTime(newCommitPoint.getTimestamp())); } } // namespace mongo diff --git a/src/mongo/db/s/config_server_op_observer_test.cpp b/src/mongo/db/s/config_server_op_observer_test.cpp index e7383bdab54..d21f30899c3 100644 --- a/src/mongo/db/s/config_server_op_observer_test.cpp +++ b/src/mongo/db/s/config_server_op_observer_test.cpp @@ -29,6 +29,7 @@ #include "mongo/db/s/config/sharding_catalog_manager.h" #include "mongo/db/s/config_server_op_observer.h" +#include "mongo/db/vector_clock_mutable.h" #include "mongo/s/cluster_identity_loader.h" #include "mongo/s/config_server_test_fixture.h" #include "mongo/unittest/death_test.h" @@ -102,10 +103,12 @@ TEST_F(ConfigServerOpObserverTest, ConfigOpTimeAdvancedWhenMajorityCommitPointAd repl::OpTime b(Timestamp(1, 2), 1); opObserver.onMajorityCommitPointUpdate(getServiceContext(), a); - // TODO SERVER-46200: Verify that configOpTime is a. + const auto aTime = VectorClock::get(getServiceContext())->getTime(); + ASSERT_EQ(a.getTimestamp(), aTime[VectorClock::Component::ConfigTime].asTimestamp()); opObserver.onMajorityCommitPointUpdate(getServiceContext(), b); - // TODO SERVER-46200: Verify that configOpTime is b. + const auto bTime = VectorClock::get(getServiceContext())->getTime(); + ASSERT_EQ(b.getTimestamp(), bTime[VectorClock::Component::ConfigTime].asTimestamp()); } } // namespace diff --git a/src/mongo/db/s/vector_clock_config_server_test.cpp b/src/mongo/db/s/vector_clock_config_server_test.cpp new file mode 100644 index 00000000000..30761daea07 --- /dev/null +++ b/src/mongo/db/s/vector_clock_config_server_test.cpp @@ -0,0 +1,115 @@ +/** + * 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 "mongo/platform/basic.h" + +#include "mongo/db/vector_clock_mutable.h" +#include "mongo/s/config_server_test_fixture.h" +#include "mongo/unittest/death_test.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +using VectorClockConfigServerTest = ConfigServerTestFixture; + +TEST_F(VectorClockConfigServerTest, TickClusterTime) { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + + const auto t0 = vc->getTime(); + ASSERT_EQ(LogicalTime(), t0[VectorClock::Component::ClusterTime]); + + const auto r1 = vc->tick(VectorClock::Component::ClusterTime, 1); + const auto t1 = vc->getTime(); + ASSERT_EQ(r1, t1[VectorClock::Component::ClusterTime]); + ASSERT_GT(r1, t0[VectorClock::Component::ClusterTime]); + + const auto r2 = vc->tick(VectorClock::Component::ClusterTime, 2); + const auto t2 = vc->getTime(); + ASSERT_GT(r2, r1); + ASSERT_GT(t2[VectorClock::Component::ClusterTime], r1); +} + +TEST_F(VectorClockConfigServerTest, TickToConfigTime) { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + + const auto t0 = vc->getTime(); + ASSERT_EQ(LogicalTime(), t0[VectorClock::Component::ConfigTime]); + + vc->tickTo(VectorClock::Component::ConfigTime, LogicalTime(Timestamp(1, 1))); + const auto t1 = vc->getTime(); + ASSERT_EQ(LogicalTime(Timestamp(1, 1)), t1[VectorClock::Component::ConfigTime]); + + vc->tickTo(VectorClock::Component::ConfigTime, LogicalTime(Timestamp(3, 3))); + const auto t2 = vc->getTime(); + ASSERT_EQ(LogicalTime(Timestamp(3, 3)), t2[VectorClock::Component::ConfigTime]); + + vc->tickTo(VectorClock::Component::ConfigTime, LogicalTime(Timestamp(2, 2))); + const auto t3 = vc->getTime(); + ASSERT_EQ(LogicalTime(Timestamp(3, 3)), t3[VectorClock::Component::ConfigTime]); +} + +TEST_F(VectorClockConfigServerTest, GossipOutTest) { + // TODO SERVER-47914: after ClusterTime gossiping has been re-enabled: get the gossipOut + // internal and external, and for each check that $clusterTime and $configTime are there, with + // the right format, and right value. + + // auto sc = getGlobalServiceContext(); + // auto vc = VectorClockMutable::get(sc); + + // const auto r1 = vc->tick(VectorClock::Component::ClusterTime, 1); +} + +TEST_F(VectorClockConfigServerTest, GossipInTest) { + // TODO SERVER-47914: after ClusterTime gossiping has been re-enabled: for each of gossipIn + // internal and external, give it BSON in the correct format, and then check that ClusterTime + // has been advanced (or not), ***and that ConfigTime has not***. + + // auto sc = getGlobalServiceContext(); + // auto vc = VectorClockMutable::get(sc); + + // const auto r1 = vc->tick(VectorClock::Component::ClusterTime, 1); +} + +DEATH_TEST_F(VectorClockConfigServerTest, CannotTickConfigTime, "Hit a MONGO_UNREACHABLE") { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + vc->tick(VectorClock::Component::ConfigTime, 1); +} + +DEATH_TEST_F(VectorClockConfigServerTest, CannotTickToClusterTime, "Hit a MONGO_UNREACHABLE") { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + vc->tickTo(VectorClock::Component::ClusterTime, LogicalTime()); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/s/vector_clock_shard_server_test.cpp b/src/mongo/db/s/vector_clock_shard_server_test.cpp new file mode 100644 index 00000000000..594e7d86f59 --- /dev/null +++ b/src/mongo/db/s/vector_clock_shard_server_test.cpp @@ -0,0 +1,101 @@ +/** + * 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 "mongo/platform/basic.h" + +#include "mongo/db/vector_clock_mutable.h" +#include "mongo/s/shard_server_test_fixture.h" +#include "mongo/unittest/death_test.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +using VectorClockShardServerTest = ShardServerTestFixture; + +TEST_F(VectorClockShardServerTest, TickClusterTime) { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + + const auto t0 = vc->getTime(); + ASSERT_EQ(LogicalTime(), t0[VectorClock::Component::ClusterTime]); + + const auto r1 = vc->tick(VectorClock::Component::ClusterTime, 1); + const auto t1 = vc->getTime(); + ASSERT_EQ(r1, t1[VectorClock::Component::ClusterTime]); + ASSERT_GT(r1, t0[VectorClock::Component::ClusterTime]); + + const auto r2 = vc->tick(VectorClock::Component::ClusterTime, 2); + const auto t2 = vc->getTime(); + ASSERT_GT(r2, r1); + ASSERT_GT(t2[VectorClock::Component::ClusterTime], r1); +} + +TEST_F(VectorClockShardServerTest, GossipOutTest) { + // TODO SERVER-47914: after ClusterTime gossiping has been re-enabled: get the gossipOut + // internal and external, and for each check that $clusterTime and $configTime are there, with + // the right format, and right value. + + // auto sc = getGlobalServiceContext(); + // auto vc = VectorClockMutable::get(sc); + + // const auto r1 = vc->tick(VectorClock::Component::ClusterTime, 1); +} + +TEST_F(VectorClockShardServerTest, GossipInTest) { + // TODO SERVER-47914: after ClusterTime gossiping has been re-enabled: for each of gossipIn + // internal and external, give it BSON in the correct format, and then check that ClusterTime + // and ConfigTime have been advanced (or not). + + // auto sc = getGlobalServiceContext(); + // auto vc = VectorClockMutable::get(sc); + + // const auto r1 = vc->tick(VectorClock::Component::ClusterTime, 1); +} + +DEATH_TEST_F(VectorClockShardServerTest, CannotTickConfigTime, "Hit a MONGO_UNREACHABLE") { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + vc->tick(VectorClock::Component::ConfigTime, 1); +} + +DEATH_TEST_F(VectorClockShardServerTest, CannotTickToClusterTime, "Hit a MONGO_UNREACHABLE") { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + vc->tickTo(VectorClock::Component::ClusterTime, LogicalTime()); +} + +DEATH_TEST_F(VectorClockShardServerTest, CannotTickToConfigTime, "Hit a MONGO_UNREACHABLE") { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + vc->tickTo(VectorClock::Component::ConfigTime, LogicalTime()); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index 3ac44b65d25..0a3072214e9 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -82,6 +82,7 @@ #include "mongo/db/stats/top.h" #include "mongo/db/transaction_participant.h" #include "mongo/db/transaction_validation.h" +#include "mongo/db/vector_clock.h" #include "mongo/logv2/log.h" #include "mongo/rpc/factory.h" #include "mongo/rpc/get_status_from_command_result.h" @@ -443,6 +444,8 @@ void appendClusterAndOperationTime(OperationContext* opCtx, return; } + VectorClock::get(opCtx)->gossipOut(metadataBob, opCtx->getClient()->getSessionTags()); + // Authorized clients always receive operationTime and dummy signed $clusterTime. if (LogicalTimeValidator::isAuthorizedToAdvanceClock(opCtx)) { auto operationTime = computeOperationTime(opCtx, startTime); diff --git a/src/mongo/db/update/SConscript b/src/mongo/db/update/SConscript index 2dc99fd8716..681e61d0944 100644 --- a/src/mongo/db/update/SConscript +++ b/src/mongo/db/update/SConscript @@ -50,6 +50,7 @@ env.Library( '$BUILD_DIR/mongo/db/logical_clock', '$BUILD_DIR/mongo/db/pipeline/pipeline', '$BUILD_DIR/mongo/db/update_index_data', + '$BUILD_DIR/mongo/db/vector_clock_mutable', 'update_common', ], ) @@ -106,6 +107,7 @@ env.CppUnitTest( '$BUILD_DIR/mongo/db/query/query_planner', '$BUILD_DIR/mongo/db/query/query_test_service_context', '$BUILD_DIR/mongo/db/service_context_test_fixture', + '$BUILD_DIR/mongo/db/vector_clock_trivial', 'update', 'update_common', 'update_driver', diff --git a/src/mongo/db/vector_clock.cpp b/src/mongo/db/vector_clock.cpp new file mode 100644 index 00000000000..5be53064537 --- /dev/null +++ b/src/mongo/db/vector_clock.cpp @@ -0,0 +1,201 @@ +/** + * 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 "mongo/platform/basic.h" + +#include "mongo/db/vector_clock.h" + +namespace mongo { + +namespace { + +const auto vectorClockDecoration = ServiceContext::declareDecoration(); + +} // namespace + +VectorClock* VectorClock::get(ServiceContext* service) { + return vectorClockDecoration(service); +} + +VectorClock* VectorClock::get(OperationContext* ctx) { + return get(ctx->getClient()->getServiceContext()); +} + +VectorClock::VectorClock() = default; + +VectorClock::~VectorClock() = default; + +void VectorClock::registerVectorClockOnServiceContext(ServiceContext* service, + VectorClock* vectorClock) { + invariant(!vectorClock->_service); + vectorClock->_service = service; + auto& clock = vectorClockDecoration(service); + invariant(!clock); + clock = std::move(vectorClock); +} + +VectorClock::VectorTime VectorClock::getTime() const { + stdx::lock_guard lock(_mutex); + return VectorTime(_vectorTime); +} + +void VectorClock::_advanceTime(LogicalTimeArray&& newTime) { + stdx::lock_guard lock(_mutex); + auto it = _vectorTime.begin(); + auto newIt = newTime.begin(); + for (; it != _vectorTime.end() && newIt != newTime.end(); ++it, ++newIt) { + if (*newIt > *it) { + *it = std::move(*newIt); + } + } +} + +class VectorClock::GossipFormat { +public: + class Plain; + class Signed; + + static const ComponentArray> _formatters; + + GossipFormat(std::string fieldName) : _fieldName(fieldName) {} + virtual ~GossipFormat() = default; + + virtual void out(BSONObjBuilder* out, LogicalTime time, Component component) const = 0; + virtual LogicalTime in(const BSONObj& in, Component component) const = 0; + + const std::string _fieldName; +}; + +class VectorClock::GossipFormat::Plain : public VectorClock::GossipFormat { +public: + using GossipFormat::GossipFormat; + virtual ~Plain() = default; + + void out(BSONObjBuilder* out, LogicalTime time, Component component) const override { + out->append(_fieldName, time.asTimestamp()); + } + + LogicalTime in(const BSONObj& in, Component component) const override { + const auto componentElem(in[_fieldName]); + if (componentElem.eoo()) { + // Nothing to gossip in. + return LogicalTime(); + } + uassert(ErrorCodes::BadValue, + str::stream() << _fieldName << " is not a Timestamp", + componentElem.type() == bsonTimestamp); + return LogicalTime(componentElem.timestamp()); + } +}; + +class VectorClock::GossipFormat::Signed : public VectorClock::GossipFormat { +public: + using GossipFormat::GossipFormat; + virtual ~Signed() = default; + + void out(BSONObjBuilder* out, LogicalTime time, Component component) const override { + // TODO SERVER-47914: make this do the actual proper signing + BSONObjBuilder bob; + bob.append("time", time.asTimestamp()); + bob.append("signature", 0); + out->append(_fieldName, bob.done()); + } + + LogicalTime in(const BSONObj& in, Component component) const override { + // TODO SERVER-47914: make this do the actual proper signing + const auto componentElem(in[_fieldName]); + if (componentElem.eoo()) { + // Nothing to gossip in. + return LogicalTime(); + } + uassert(ErrorCodes::BadValue, + str::stream() << _fieldName << " is not a sub-object", + componentElem.isABSONObj()); + const auto subobj = componentElem.embeddedObject(); + const auto timeElem(subobj["time"]); + uassert(ErrorCodes::FailedToParse, "No time found", !timeElem.eoo()); + uassert(ErrorCodes::BadValue, + str::stream() << "time is not a Timestamp", + timeElem.type() == bsonTimestamp); + return LogicalTime(timeElem.timestamp()); + } +}; + +// TODO SERVER-47914: update $clusterTimeNew to $clusterTime once LogicalClock is migrated into +// VectorClock. +const VectorClock::ComponentArray> + VectorClock::GossipFormat::_formatters{ + std::make_unique("$clusterTimeNew"), + std::make_unique("$configTime")}; + +void VectorClock::gossipOut(BSONObjBuilder* outMessage, + const transport::Session::TagMask clientSessionTags) const { + if (clientSessionTags & transport::Session::kInternalClient) { + _gossipOutInternal(outMessage); + } else { + _gossipOutExternal(outMessage); + } +} + +void VectorClock::gossipIn(const BSONObj& inMessage, + const transport::Session::TagMask clientSessionTags) { + if (clientSessionTags & transport::Session::kInternalClient) { + _advanceTime(_gossipInInternal(inMessage)); + } else { + _advanceTime(_gossipInExternal(inMessage)); + } +} + +void VectorClock::_gossipOutComponent(BSONObjBuilder* out, + VectorTime time, + Component component) const { + GossipFormat::_formatters[component]->out(out, time[component], component); +} + +void VectorClock::_gossipInComponent(const BSONObj& in, + LogicalTimeArray* newTime, + Component component) { + (*newTime)[component] = GossipFormat::_formatters[component]->in(in, component); +} + +std::string VectorClock::_componentName(Component component) { + return GossipFormat::_formatters[component]->_fieldName; +} + +bool VectorClock::isEnabled() const { + stdx::lock_guard lock(_mutex); + return _isEnabled; +} + +void VectorClock::disable() { + stdx::lock_guard lock(_mutex); + _isEnabled = false; +} + +} // namespace mongo diff --git a/src/mongo/db/vector_clock.h b/src/mongo/db/vector_clock.h new file mode 100644 index 00000000000..b7381652e9c --- /dev/null +++ b/src/mongo/db/vector_clock.h @@ -0,0 +1,148 @@ +/** + * 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. + */ + +#pragma once + +#include + +#include "mongo/db/logical_time.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/service_context.h" +#include "mongo/platform/mutex.h" +#include "mongo/transport/session.h" + +namespace mongo { + +class VectorClockMutable; + +/** + * The VectorClock service provides a collection of cluster-wide logical clocks (including the + * clusterTime), that are used to provide causal-consistency to various other services. + */ +class VectorClock { +public: + enum class Component : uint8_t { + ClusterTime = 0, + ConfigTime = 1, + _kNumComponents = 2, + }; + +private: + template + class ComponentArray + : public std::array(Component::_kNumComponents)> { + public: + const T& operator[](Component component) const { + invariant(component != Component::_kNumComponents); + return std::array(Component::_kNumComponents)>:: + operator[](static_cast(component)); + } + + T& operator[](Component component) { + invariant(component != Component::_kNumComponents); + return std::array(Component::_kNumComponents)>:: + operator[](static_cast(component)); + } + + private: + const T& operator[](unsigned long i) const; + T& operator[](unsigned long i); + }; + +protected: + using LogicalTimeArray = ComponentArray; + +public: + class VectorTime { + public: + LogicalTime operator[](Component component) const { + return _time[component]; + } + + private: + friend class VectorClock; + + explicit VectorTime(LogicalTimeArray time) : _time(time) {} + + const LogicalTimeArray _time; + }; + + // Decorate ServiceContext with VectorClock* which points to the actual vector clock + // implementation. + static VectorClock* get(ServiceContext* service); + static VectorClock* get(OperationContext* ctx); + + static void registerVectorClockOnServiceContext(ServiceContext* service, + VectorClock* vectorClock); + + VectorTime getTime() const; + + // Gossipping + void gossipOut(BSONObjBuilder* outMessage, + const transport::Session::TagMask clientSessionTags) const; + void gossipIn(const BSONObj& inMessage, const transport::Session::TagMask clientSessionTags); + + bool isEnabled() const; + void disable(); + +protected: + VectorClock(); + virtual ~VectorClock(); + + static std::string _componentName(Component component); + + // Internal Gossipping API + virtual void _gossipOutInternal(BSONObjBuilder* out) const = 0; + virtual void _gossipOutExternal(BSONObjBuilder* out) const = 0; + virtual LogicalTimeArray _gossipInInternal(const BSONObj& in) = 0; + virtual LogicalTimeArray _gossipInExternal(const BSONObj& in) = 0; + + void _gossipOutComponent(BSONObjBuilder* out, VectorTime time, Component component) const; + void _gossipInComponent(const BSONObj& in, LogicalTimeArray* newTime, Component component); + + // Used to atomically advance the time of several components (eg. during gossip-in). + void _advanceTime(LogicalTimeArray&& newTime); + + ServiceContext* _service{nullptr}; + + // The mutex protects _vectorTime and _isEnabled. + // + // Note that ConfigTime is advanced under the ReplicationCoordinator mutex, so to avoid + // potential deadlocks the ReplicationCoordator mutex should never be acquired whilst the + // VectorClock mutex is held. + mutable Mutex _mutex = MONGO_MAKE_LATCH("VectorClock::_mutex"); + + LogicalTimeArray _vectorTime; + bool _isEnabled{true}; + +private: + class GossipFormat; +}; + +} // namespace mongo diff --git a/src/mongo/db/vector_clock_mongod.cpp b/src/mongo/db/vector_clock_mongod.cpp new file mode 100644 index 00000000000..69d2eb2e7c1 --- /dev/null +++ b/src/mongo/db/vector_clock_mongod.cpp @@ -0,0 +1,179 @@ +/** + * 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 "mongo/platform/basic.h" + +#include "mongo/db/replica_set_aware_service.h" +#include "mongo/db/vector_clock_mutable.h" + +namespace mongo { +namespace { + +/** + * Vector clock implementation for mongod. + */ +class VectorClockMongoD : public VectorClockMutable, + public ReplicaSetAwareService { + VectorClockMongoD(const VectorClockMongoD&) = delete; + VectorClockMongoD& operator=(const VectorClockMongoD&) = delete; + +public: + static VectorClockMongoD* get(ServiceContext* serviceContext); + + VectorClockMongoD(); + virtual ~VectorClockMongoD(); + + LogicalTime tick(Component component, uint64_t nTicks) override; + void tickTo(Component component, LogicalTime newTime) override; + +protected: + void _gossipOutInternal(BSONObjBuilder* out) const override; + void _gossipOutExternal(BSONObjBuilder* out) const override; + LogicalTimeArray _gossipInInternal(const BSONObj& in) override; + LogicalTimeArray _gossipInExternal(const BSONObj& in) override; + +private: + enum class ReplState { + Unset, + StepUpBegin, + StepUpComplete, + StepDown, + }; + + void onStepUpBegin(OperationContext* opCtx) override; + void onStepUpComplete(OperationContext* opCtx) override; + void onStepDown() override; + + ReplState _replState{ReplState::Unset}; +}; + +const auto vectorClockMongoDDecoration = ServiceContext::declareDecoration(); + +const ReplicaSetAwareServiceRegistry::Registerer vectorClockMongoDRegisterer( + "VectorClockMongoD-ReplicaSetAwareServiceRegistration"); + +VectorClockMongoD* VectorClockMongoD::get(ServiceContext* serviceContext) { + return &vectorClockMongoDDecoration(serviceContext); +} + +ServiceContext::ConstructorActionRegisterer _registerer( + "VectorClockMongoD-VectorClockRegistration", + {}, + [](ServiceContext* service) { + VectorClockMongoD::registerVectorClockOnServiceContext( + service, &vectorClockMongoDDecoration(service)); + }, + {}); + +VectorClockMongoD::VectorClockMongoD() = default; + +VectorClockMongoD::~VectorClockMongoD() = default; + +void VectorClockMongoD::onStepUpBegin(OperationContext* opCtx) { + _replState = ReplState::StepUpBegin; +} + +void VectorClockMongoD::onStepUpComplete(OperationContext* opCtx) { + _replState = ReplState::StepUpComplete; +} + +void VectorClockMongoD::onStepDown() { + _replState = ReplState::StepDown; +} + +void VectorClockMongoD::_gossipOutInternal(BSONObjBuilder* out) const { + VectorTime now = getTime(); + // TODO SERVER-47914: re-enable gossipping of VectorClock's ClusterTime once LogicalClock has + // been migrated into VectorClock. + // _gossipOutComponent(out, now, Component::ClusterTime); + if (serverGlobalParams.clusterRole == ClusterRole::ShardServer || + serverGlobalParams.clusterRole == ClusterRole::ConfigServer) { + _gossipOutComponent(out, now, Component::ConfigTime); + } +} + +void VectorClockMongoD::_gossipOutExternal(BSONObjBuilder* out) const { + // TODO SERVER-47914: re-enable gossipping of VectorClock's ClusterTime once LogicalClock has + // been migrated into VectorClock. + // VectorTime now = getTime(); + // _gossipOutComponent(out, now, Component::ClusterTime); +} + +VectorClock::LogicalTimeArray VectorClockMongoD::_gossipInInternal(const BSONObj& in) { + LogicalTimeArray newTime; + // TODO SERVER-47914: re-enable gossipping of VectorClock's ClusterTime once LogicalClock has + // been migrated into VectorClock. + // _gossipInComponent(in, &newTime, Component::ClusterTime); + if (serverGlobalParams.clusterRole == ClusterRole::ShardServer) { + _gossipInComponent(in, &newTime, Component::ConfigTime); + } + return newTime; +} + +VectorClock::LogicalTimeArray VectorClockMongoD::_gossipInExternal(const BSONObj& in) { + LogicalTimeArray newTime; + // TODO SERVER-47914: re-enable gossipping of VectorClock's ClusterTime once LogicalClock has + // been migrated into VectorClock. + // _gossipInComponent(in, &newTime, Component::ClusterTime); + return newTime; +} + +LogicalTime VectorClockMongoD::tick(Component component, uint64_t nTicks) { + if (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); + } + + // tick is not permitted in other circumstances. + MONGO_UNREACHABLE; +} + +void VectorClockMongoD::tickTo(Component component, LogicalTime newTime) { + if (component == Component::ConfigTime && + serverGlobalParams.clusterRole == ClusterRole::ConfigServer) { + _advanceComponentTimeTo(component, std::move(newTime)); + return; + } + + // tickTo is not permitted in other circumstances. + MONGO_UNREACHABLE; +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/vector_clock_mongod_test.cpp b/src/mongo/db/vector_clock_mongod_test.cpp new file mode 100644 index 00000000000..1a443ca68ba --- /dev/null +++ b/src/mongo/db/vector_clock_mongod_test.cpp @@ -0,0 +1,101 @@ +/** + * 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 "mongo/platform/basic.h" + +#include "mongo/db/service_context_test_fixture.h" +#include "mongo/db/vector_clock_mutable.h" +#include "mongo/unittest/death_test.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +using VectorClockMongoDTest = ServiceContextTest; + +TEST_F(VectorClockMongoDTest, TickClusterTime) { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + + const auto t0 = vc->getTime(); + ASSERT_EQ(LogicalTime(), t0[VectorClock::Component::ClusterTime]); + + const auto r1 = vc->tick(VectorClock::Component::ClusterTime, 1); + const auto t1 = vc->getTime(); + ASSERT_EQ(r1, t1[VectorClock::Component::ClusterTime]); + ASSERT_GT(r1, t0[VectorClock::Component::ClusterTime]); + + const auto r2 = vc->tick(VectorClock::Component::ClusterTime, 2); + const auto t2 = vc->getTime(); + ASSERT_GT(r2, r1); + ASSERT_GT(t2[VectorClock::Component::ClusterTime], r1); +} + +TEST_F(VectorClockMongoDTest, GossipOutTest) { + // TODO SERVER-47914: after ClusterTime gossiping has been re-enabled: get the gossipOut + // internal and external, and for each check that $clusterTime is there, with the right format, + // and right value, and not configTime. + + // auto sc = getGlobalServiceContext(); + // auto vc = VectorClockMutable::get(sc); + + // const auto r1 = vc->tick(VectorClock::Component::ClusterTime, 1); +} + +TEST_F(VectorClockMongoDTest, GossipInTest) { + // TODO SERVER-47914: after ClusterTime gossiping has been re-enabled: for each of gossipIn + // internal and external, give it BSON in the correct format, and then check that ClusterTime + // has been advanced (or not), and that ConfigTime has not. + + // auto sc = getGlobalServiceContext(); + // auto vc = VectorClockMutable::get(sc); + + // const auto r1 = vc->tick(VectorClock::Component::ClusterTime, 1); +} + +DEATH_TEST_F(VectorClockMongoDTest, CannotTickConfigTime, "Hit a MONGO_UNREACHABLE") { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + vc->tick(VectorClock::Component::ConfigTime, 1); +} + +DEATH_TEST_F(VectorClockMongoDTest, CannotTickToClusterTime, "Hit a MONGO_UNREACHABLE") { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + vc->tickTo(VectorClock::Component::ClusterTime, LogicalTime()); +} + +DEATH_TEST_F(VectorClockMongoDTest, CannotTickToConfigTime, "Hit a MONGO_UNREACHABLE") { + auto sc = getGlobalServiceContext(); + auto vc = VectorClockMutable::get(sc); + vc->tickTo(VectorClock::Component::ConfigTime, LogicalTime()); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/vector_clock_mutable.cpp b/src/mongo/db/vector_clock_mutable.cpp new file mode 100644 index 00000000000..6e18fbd028a --- /dev/null +++ b/src/mongo/db/vector_clock_mutable.cpp @@ -0,0 +1,130 @@ +/** + * 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. + */ + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault + +#include + +#include "mongo/platform/basic.h" + +#include "mongo/db/vector_clock_mutable.h" + +#include "mongo/logv2/log.h" + +namespace mongo { + +namespace { + +const auto vectorClockMutableDecoration = ServiceContext::declareDecoration(); + +} // namespace + +VectorClockMutable* VectorClockMutable::get(ServiceContext* service) { + return vectorClockMutableDecoration(service); +} + +VectorClockMutable* VectorClockMutable::get(OperationContext* ctx) { + return get(ctx->getClient()->getServiceContext()); +} + +VectorClockMutable::VectorClockMutable() = default; + +VectorClockMutable::~VectorClockMutable() = default; + +void VectorClockMutable::registerVectorClockOnServiceContext( + ServiceContext* service, VectorClockMutable* vectorClockMutable) { + VectorClock::registerVectorClockOnServiceContext(service, vectorClockMutable); + auto& clock = vectorClockMutableDecoration(service); + invariant(!clock); + clock = std::move(vectorClockMutable); +} + +bool VectorClockMutable::_lessThanOrEqualToMaxPossibleTime(LogicalTime time, uint64_t nTicks) { + return time.asTimestamp().getSecs() <= std::numeric_limits::max() && + time.asTimestamp().getInc() <= (std::numeric_limits::max() - nTicks); +} + +LogicalTime VectorClockMutable::_advanceComponentTimeByTicks(Component component, uint64_t nTicks) { + invariant(nTicks > 0 && nTicks <= std::numeric_limits::max()); + + stdx::lock_guard lock(_mutex); + + LogicalTime time = _vectorTime[component]; + + const unsigned wallClockSecs = + durationCount(_service->getFastClockSource()->now().toDurationSinceEpoch()); + unsigned timeSecs = time.asTimestamp().getSecs(); + + // Synchronize time with wall clock time, if time was behind in seconds. + if (timeSecs < wallClockSecs) { + time = LogicalTime(Timestamp(wallClockSecs, 0)); + } + // If reserving 'nTicks' would force the time's increment field to exceed (2^31-1), + // overflow by moving to the next second. We use the signed integer maximum as an overflow point + // in order to preserve compatibility with potentially signed or unsigned integral Timestamp + // increment types. It is also unlikely to tick a clock by more than 2^31 in the span of one + // second. + else if (time.asTimestamp().getInc() > (std::numeric_limits::max() - nTicks)) { + + // TODO SERVER-47914: update this log id back to 20709. + LOGV2(4620000 /*20709*/, + "Exceeded maximum allowable increment value within one second. Moving time forward " + "to the next second.", + "vectorClockComponent"_attr = _componentName(component)); + + // Move time forward to the next second + time = LogicalTime(Timestamp(time.asTimestamp().getSecs() + 1, 0)); + } + + // TODO SERVER-47914: update this uassert id back to 40482. + uassert(4620001 /*40482*/, + str::stream() << _componentName(component) + << " cannot be advanced beyond the maximum cluster time value", + _lessThanOrEqualToMaxPossibleTime(time, nTicks)); + + // Save the next time. + time.addTicks(1); + _vectorTime[component] = time; + + // Add the rest of the requested ticks if needed. + if (nTicks > 1) { + _vectorTime[component].addTicks(nTicks - 1); + } + + return time; +} + +void VectorClockMutable::_advanceComponentTimeTo(Component component, LogicalTime&& newTime) { + stdx::lock_guard lock(_mutex); + if (newTime > _vectorTime[component]) { + _vectorTime[component] = std::move(newTime); + } +} + +} // namespace mongo diff --git a/src/mongo/db/vector_clock_mutable.h b/src/mongo/db/vector_clock_mutable.h new file mode 100644 index 00000000000..7e99aa32f2a --- /dev/null +++ b/src/mongo/db/vector_clock_mutable.h @@ -0,0 +1,66 @@ +/** + * 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. + */ + +#pragma once + +#include "mongo/db/vector_clock.h" + +namespace mongo { + +/** + * A vector clock service that additionally permits being advanced authoritatively ("ticking"). + * + * Only linked in contexts where ticking is allowed, ie. mongod, embedded, mongod-based unittests. + */ +class VectorClockMutable : public VectorClock { +public: + // Decorate ServiceContext with VectorClockMutable*, that will resolve to the mutable vector + // clock implementation. + static VectorClockMutable* get(ServiceContext* service); + static VectorClockMutable* get(OperationContext* ctx); + + static void registerVectorClockOnServiceContext(ServiceContext* service, + VectorClockMutable* vectorClockMutable); + + // Ticking + virtual LogicalTime tick(Component component, uint64_t nTicks) = 0; + virtual void tickTo(Component component, LogicalTime newTime) = 0; + +protected: + static bool _lessThanOrEqualToMaxPossibleTime(LogicalTime time, uint64_t nTicks); + + VectorClockMutable(); + virtual ~VectorClockMutable(); + + // Internal Ticking API + LogicalTime _advanceComponentTimeByTicks(Component component, uint64_t nTicks); + void _advanceComponentTimeTo(Component component, LogicalTime&& newTime); +}; + +} // namespace mongo diff --git a/src/mongo/db/vector_clock_trivial.cpp b/src/mongo/db/vector_clock_trivial.cpp new file mode 100644 index 00000000000..88f00a512ea --- /dev/null +++ b/src/mongo/db/vector_clock_trivial.cpp @@ -0,0 +1,100 @@ +/** + * 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 "mongo/platform/basic.h" + +#include "mongo/db/vector_clock_mutable.h" + +namespace mongo { +namespace { + +/** + * Vector clock implementation for non-distributed environments (embedded, some unittests). + */ +class VectorClockTrivial : public VectorClockMutable { + VectorClockTrivial(const VectorClockTrivial&) = delete; + VectorClockTrivial& operator=(const VectorClockTrivial&) = delete; + +public: + VectorClockTrivial(); + virtual ~VectorClockTrivial(); + + LogicalTime tick(Component component, uint64_t nTicks) override; + void tickTo(Component component, LogicalTime newTime) override; + +protected: + void _gossipOutInternal(BSONObjBuilder* out) const override; + void _gossipOutExternal(BSONObjBuilder* out) const override; + LogicalTimeArray _gossipInInternal(const BSONObj& in) override; + LogicalTimeArray _gossipInExternal(const BSONObj& in) override; +}; + +const auto vectorClockTrivialDecoration = ServiceContext::declareDecoration(); + +ServiceContext::ConstructorActionRegisterer _registerer( + "VectorClockTrivial-VectorClockRegistration", + {}, + [](ServiceContext* service) { + VectorClockTrivial::registerVectorClockOnServiceContext( + service, &vectorClockTrivialDecoration(service)); + }, + {}); + +VectorClockTrivial::VectorClockTrivial() = default; + +VectorClockTrivial::~VectorClockTrivial() = default; + +void VectorClockTrivial::_gossipOutInternal(BSONObjBuilder* out) const { + // Clocks are not gossipped in trivial (non-distributed) environments. +} + +void VectorClockTrivial::_gossipOutExternal(BSONObjBuilder* out) const { + // Clocks are not gossipped in trivial (non-distributed) environments. +} + +VectorClock::LogicalTimeArray VectorClockTrivial::_gossipInInternal(const BSONObj& in) { + // Clocks are not gossipped in trivial (non-distributed) environments. + return {}; +} + +VectorClock::LogicalTimeArray VectorClockTrivial::_gossipInExternal(const BSONObj& in) { + // Clocks are not gossipped in trivial (non-distributed) environments. + return {}; +} + +LogicalTime VectorClockTrivial::tick(Component component, uint64_t nTicks) { + return _advanceComponentTimeByTicks(component, nTicks); +} + +void VectorClockTrivial::tickTo(Component component, LogicalTime newTime) { + _advanceComponentTimeTo(component, std::move(newTime)); +} + +} // namespace +} // namespace mongo -- cgit v1.2.1