summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/db/logical_clock.cpp21
-rw-r--r--src/mongo/db/logical_clock.h2
-rw-r--r--src/mongo/db/logical_clock_test.cpp115
-rw-r--r--src/mongo/db/logical_clock_test_fixture.cpp10
-rw-r--r--src/mongo/db/logical_clock_test_fixture.h2
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;