diff options
-rw-r--r-- | src/mongo/db/logical_clock.cpp | 21 | ||||
-rw-r--r-- | src/mongo/db/logical_clock.h | 2 | ||||
-rw-r--r-- | src/mongo/db/logical_clock_test.cpp | 115 | ||||
-rw-r--r-- | src/mongo/db/logical_clock_test_fixture.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/logical_clock_test_fixture.h | 2 |
5 files changed, 148 insertions, 2 deletions
diff --git a/src/mongo/db/logical_clock.cpp b/src/mongo/db/logical_clock.cpp index 35861cf9ea2..d8e4d097ef4 100644 --- a/src/mongo/db/logical_clock.cpp +++ b/src/mongo/db/logical_clock.cpp @@ -69,6 +69,11 @@ public: namespace { const auto getLogicalClock = ServiceContext::declareDecoration<std::unique_ptr<LogicalClock>>(); + +bool lessThanOrEqualToMaxPossibleTime(LogicalTime time, uint64_t nTicks) { + return time.asTimestamp().getSecs() <= LogicalClock::kMaxSignedInt && + time.asTimestamp().getInc() <= (LogicalClock::kMaxSignedInt - nTicks); +} } LogicalClock* LogicalClock::get(ServiceContext* service) { @@ -108,7 +113,7 @@ Status LogicalClock::advanceClusterTime(const LogicalTime newTime) { LogicalTime LogicalClock::reserveTicks(uint64_t nTicks) { - invariant(nTicks > 0 && nTicks < (1U << 31)); + invariant(nTicks > 0 && nTicks <= kMaxSignedInt); stdx::lock_guard<stdx::mutex> lock(_mutex); @@ -127,7 +132,7 @@ LogicalTime LogicalClock::reserveTicks(uint64_t nTicks) { // in order to preserve compatibility with potentially signed or unsigned integral Timestamp // increment types. It is also unlikely to apply more than 2^31 oplog entries in the span of one // second. - else if (clusterTime.asTimestamp().getInc() >= ((1U << 31) - nTicks)) { + else if (clusterTime.asTimestamp().getInc() > (kMaxSignedInt - nTicks)) { log() << "Exceeded maximum allowable increment value within one second. Moving clusterTime " "forward to the next second."; @@ -136,6 +141,10 @@ LogicalTime LogicalClock::reserveTicks(uint64_t nTicks) { clusterTime = LogicalTime(Timestamp(clusterTime.asTimestamp().getSecs() + 1, 0)); } + uassert(40482, + "cluster time cannot be advanced beyond its maximum value", + lessThanOrEqualToMaxPossibleTime(clusterTime, nTicks)); + // Save the next cluster time. clusterTime.addTicks(1); _clusterTime = clusterTime; @@ -154,6 +163,10 @@ void LogicalClock::setClusterTimeFromTrustedSource(LogicalTime newTime) { // Rate limit checks are skipped here so a server with no activity for longer than // maxAcceptableLogicalClockDriftSecs seconds can still have its cluster time initialized. + uassert(40483, + "cluster time cannot be advanced beyond its maximum value", + lessThanOrEqualToMaxPossibleTime(newTime, 0)); + if (newTime > _clusterTime) { _clusterTime = newTime; } @@ -174,6 +187,10 @@ Status LogicalClock::_passesRateLimiter_inlock(LogicalTime newTime) { << "."); } + uassert(40484, + "cluster time cannot be advanced beyond its maximum value", + lessThanOrEqualToMaxPossibleTime(newTime, 0)); + return Status::OK(); } diff --git a/src/mongo/db/logical_clock.h b/src/mongo/db/logical_clock.h index 3dcbb3e150d..a1be25c3265 100644 --- a/src/mongo/db/logical_clock.h +++ b/src/mongo/db/logical_clock.h @@ -46,6 +46,8 @@ public: static LogicalClock* get(OperationContext* ctx); static void set(ServiceContext* service, std::unique_ptr<LogicalClock> logicalClock); + static const uint32_t kMaxSignedInt = ((1U << 31) - 1); + static constexpr Seconds kMaxAcceptableLogicalClockDriftSecs = Seconds(365 * 24 * 60 * 60); // 1 year diff --git a/src/mongo/db/logical_clock_test.cpp b/src/mongo/db/logical_clock_test.cpp index 8e7a1da3547..2130b5e9944 100644 --- a/src/mongo/db/logical_clock_test.cpp +++ b/src/mongo/db/logical_clock_test.cpp @@ -49,6 +49,10 @@ std::string kDummyNamespaceString = "test.foo"; using LogicalClockTest = LogicalClockTestFixture; +LogicalTime buildLogicalTime(unsigned secs, unsigned inc) { + return LogicalTime(Timestamp(secs, inc)); +} + // Check that the initial time does not change during logicalClock creation. TEST_F(LogicalClockTest, roundtrip) { Timestamp tX(1); @@ -215,5 +219,116 @@ TEST_F(LogicalClockTest, WallClockSetTooFarInFuture) { ASSERT_TRUE(getClock()->getClusterTime() == nextTime); } +// Verify the behavior of advancing cluster time around the max allowed values. +TEST_F(LogicalClockTest, ReserveTicksBehaviorAroundMaxTime) { + unsigned maxVal = LogicalClock::kMaxSignedInt; + + // Verify clock can be advanced near the max values. + + // Can always advance to the max value for the inc field. + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal - 1, maxVal - 1)); + getClock()->reserveTicks(1); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal - 1, maxVal)); + + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal - 1, maxVal - 5)); + getClock()->reserveTicks(5); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal - 1, maxVal)); + + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(0, maxVal - 1)); + getClock()->reserveTicks(1); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(0, maxVal)); + + // Can overflow inc into seconds to reach max seconds value. + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal - 1, maxVal)); + getClock()->reserveTicks(1); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, 1)); + + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal - 1, maxVal - 5)); + getClock()->reserveTicks(10); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, 10)); + + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal - 1, 1)); + getClock()->reserveTicks(maxVal); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, maxVal)); + + // Can advance inc field when seconds field is at the max value. + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal, 1)); + getClock()->reserveTicks(1); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, 2)); + + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal, 1)); + getClock()->reserveTicks(100); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, 101)); + + // Can advance to the max value for both the inc and seconds fields. + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal, maxVal - 1)); + getClock()->reserveTicks(1); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, maxVal)); + + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal, maxVal - 5)); + getClock()->reserveTicks(5); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, maxVal)); + + // Verify scenarios where the clock cannot be advanced. + + // Can't overflow inc into seconds when seconds field is at the max value. + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal, maxVal)); + ASSERT_THROWS(getClock()->reserveTicks(1), std::exception); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, maxVal)); + + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal, maxVal)); + ASSERT_THROWS(getClock()->reserveTicks(5), std::exception); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, maxVal)); + + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal, maxVal - 1)); + ASSERT_THROWS(getClock()->reserveTicks(2), std::exception); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, maxVal - 1)); + + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(maxVal, maxVal - 11)); + ASSERT_THROWS(getClock()->reserveTicks(12), std::exception); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, maxVal - 11)); +} + +// Verify behavior of advancing cluster time when the wall clock is near the max allowed value. +TEST_F(LogicalClockTest, ReserveTicksBehaviorWhenWallClockNearMaxTime) { + unsigned maxVal = LogicalClock::kMaxSignedInt; + + // Can be set to the max possible time by catching up to the wall clock. + setMockClockSourceTime(Date_t::fromDurationSinceEpoch(Seconds(maxVal))); + + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(1, 1)); + getClock()->reserveTicks(1); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(maxVal, 1)); + + // Should fail when wall clock would advance cluster time beyond the max allowed time. + setMockClockSourceTime(Date_t::max()); + + resetClock()->setClusterTimeFromTrustedSource(buildLogicalTime(1, 1)); + ASSERT_THROWS(getClock()->reserveTicks(1), std::exception); + ASSERT_TRUE(getClock()->getClusterTime() == buildLogicalTime(1, 1)); +} + +// Verify the clock rejects logical times greater than the max allowed time. +TEST_F(LogicalClockTest, RejectsLogicalTimesGreaterThanMaxTime) { + unsigned maxVal = LogicalClock::kMaxSignedInt; + + // A logical time can be greater than the maximum value allowed because the signed integer + // maximum is used for legacy compatibility, but these fields are stored as unsigned integers. + auto beyondMaxTime = buildLogicalTime(maxVal + 1, maxVal + 1); + + // The clock can't be initialized to a time greater than the max possible. + resetClock(); + ASSERT_THROWS(getClock()->setClusterTimeFromTrustedSource(beyondMaxTime), std::exception); + ASSERT_TRUE(getClock()->getClusterTime() == LogicalTime()); + + // The time can't be advanced through metadata to a time greater than the max possible. + // Advance the wall clock close enough to the new value so the rate check is passed. + auto almostMaxSecs = + Seconds(maxVal) - LogicalClock::kMaxAcceptableLogicalClockDriftSecs + Seconds(10); + setMockClockSourceTime(Date_t::fromDurationSinceEpoch(almostMaxSecs)); + ASSERT_THROWS(getClock()->advanceClusterTime(beyondMaxTime), std::exception); + ASSERT_TRUE(getClock()->getClusterTime() == LogicalTime()); +} + } // unnamed namespace } // namespace mongo diff --git a/src/mongo/db/logical_clock_test_fixture.cpp b/src/mongo/db/logical_clock_test_fixture.cpp index 588ff7dd766..a63d29162e6 100644 --- a/src/mongo/db/logical_clock_test_fixture.cpp +++ b/src/mongo/db/logical_clock_test_fixture.cpp @@ -72,6 +72,16 @@ void LogicalClockTestFixture::tearDown() { ShardingMongodTestFixture::tearDown(); } +LogicalClock* LogicalClockTestFixture::resetClock() { + auto service = getServiceContext(); + auto logicalClock = stdx::make_unique<LogicalClock>(service); + + LogicalClock::set(service, std::move(logicalClock)); + _clock = LogicalClock::get(service); + + return _clock; +} + LogicalClock* LogicalClockTestFixture::getClock() const { return _clock; } diff --git a/src/mongo/db/logical_clock_test_fixture.h b/src/mongo/db/logical_clock_test_fixture.h index 9a3115b79d3..20cb973c445 100644 --- a/src/mongo/db/logical_clock_test_fixture.h +++ b/src/mongo/db/logical_clock_test_fixture.h @@ -57,6 +57,8 @@ protected: void tearDown() override; + LogicalClock* resetClock(); + LogicalClock* getClock() const; ClockSourceMock* getMockClockSource() const; |