diff options
author | Andy Schwerin <schwerin@mongodb.com> | 2016-05-02 10:55:01 -0400 |
---|---|---|
committer | Andy Schwerin <schwerin@mongodb.com> | 2016-05-23 10:28:31 -0400 |
commit | 2e627487ef0475c46143b5f57d3e7c3d3027d5dc (patch) | |
tree | 7ad552be9a3cae113bc3cfd9df0faea78aa50e24 /src | |
parent | c9aac9d6eaba6ef2eb8903f07e997b594e88addc (diff) | |
download | mongo-2e627487ef0475c46143b5f57d3e7c3d3027d5dc.tar.gz |
SERVER-18277 Track elapsed time on cursors using microsecond resolution on OperationContext.
This completes the mechanics of moving max-time tracking to OperationContext and
switching the checkForInterrupt checks to use the service context's fast clock
source, while tracking the amount of execution time remaining on a cursor with
microsecond granularity to ensure that remaining execution time always declines,
even for very brief operations on cursors.
This patch does not complete the transition from wait_for waiting to wait_until
waiting in all places that do waiting based on operation deadlines.
Diffstat (limited to 'src')
22 files changed, 279 insertions, 181 deletions
diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp index 52081f2976b..db22953e211 100644 --- a/src/mongo/db/clientcursor.cpp +++ b/src/mongo/db/clientcursor.cpp @@ -114,7 +114,7 @@ void ClientCursor::init() { _isNoTimeout = false; _idleAgeMillis = 0; - _leftoverMaxTimeMicros = 0; + _leftoverMaxTimeMicros = Microseconds::max(); _pos = 0; if (_queryOptions & QueryOption_NoCursorTimeout) { diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h index b0244aeb8f7..673fd92bf06 100644 --- a/src/mongo/db/clientcursor.h +++ b/src/mongo/db/clientcursor.h @@ -135,10 +135,23 @@ public: return _idleAgeMillis; } - uint64_t getLeftoverMaxTimeMicros() const { + /** + * Returns the amount of time execution time available to this cursor. Only valid at the + * beginning of a getMore request, and only really for use by the maxTime tracking code. + * + * Microseconds::max() == infinity, values less than 1 mean no time left. + */ + Microseconds getLeftoverMaxTimeMicros() const { return _leftoverMaxTimeMicros; } - void setLeftoverMaxTimeMicros(uint64_t leftoverMaxTimeMicros) { + + /** + * Sets the amount of execution time available to this cursor. This is only called when an + * operation that uses a cursor is finishing, to update its remaining time. + * + * Microseconds::max() == infinity, values less than 1 mean no time left. + */ + void setLeftoverMaxTimeMicros(Microseconds leftoverMaxTimeMicros) { _leftoverMaxTimeMicros = leftoverMaxTimeMicros; } @@ -248,8 +261,8 @@ private: // How long has the cursor been idle? int _idleAgeMillis; - // TODO: Document. - uint64_t _leftoverMaxTimeMicros; + // Unused maxTime budget for this cursor. + Microseconds _leftoverMaxTimeMicros = Microseconds::max(); // // The underlying execution machinery. diff --git a/src/mongo/db/commands/getmore_cmd.cpp b/src/mongo/db/commands/getmore_cmd.cpp index b75af0436dd..1ffc0342330 100644 --- a/src/mongo/db/commands/getmore_cmd.cpp +++ b/src/mongo/db/commands/getmore_cmd.cpp @@ -277,7 +277,7 @@ public: // Reset timeout timer on the cursor since the cursor is still in use. cursor->setIdleTime(0); - const bool hasOwnMaxTime = txn->isMaxTimeSet(); + const bool hasOwnMaxTime = txn->hasDeadline(); if (!hasOwnMaxTime) { // There is no time limit set directly on this getMore command. If the cursor is @@ -285,10 +285,15 @@ public: // any leftover time from the maxTimeMS of the operation that spawned this cursor, // applying it to this getMore. if (isCursorAwaitData(cursor)) { - Seconds awaitDataTimeout(1); - txn->setMaxTimeMicros(durationCount<Microseconds>(awaitDataTimeout)); - } else { - txn->setMaxTimeMicros(cursor->getLeftoverMaxTimeMicros()); + uassert(40117, + "Illegal attempt to set operation deadline within DBDirectClient", + !txn->getClient()->isInDirectClient()); + txn->setDeadlineAfterNowBy(Seconds{1}); + } else if (cursor->getLeftoverMaxTimeMicros() < Microseconds::max()) { + uassert(40118, + "Illegal attempt to set operation deadline within DBDirectClient", + !txn->getClient()->isInDirectClient()); + txn->setDeadlineAfterNowBy(cursor->getLeftoverMaxTimeMicros()); } } txn->checkForInterrupt(); // May trigger maxTimeAlwaysTimeOut fail point. @@ -357,7 +362,7 @@ public: ctx.reset(); // Block waiting for data. - Microseconds timeout(static_cast<int64_t>(txn->getRemainingMaxTimeMicros())); + const auto timeout = txn->getRemainingMaxTimeMicros(); notifier->wait(notifierVersion, timeout); notifier.reset(); diff --git a/src/mongo/db/dbcommands.cpp b/src/mongo/db/dbcommands.cpp index aaaf4f7de67..4b4a6089364 100644 --- a/src/mongo/db/dbcommands.cpp +++ b/src/mongo/db/dbcommands.cpp @@ -1352,7 +1352,12 @@ void Command::execCommand(OperationContext* txn, "no such command option $maxTimeMs; use maxTimeMS instead", extractedFields[kQueryOptionMaxTimeMSField].eoo()); - txn->setMaxTimeMicros(static_cast<unsigned long long>(maxTimeMS) * 1000); + if (maxTimeMS > 0) { + uassert(40119, + "Illegal attempt to set operation deadline within DBDirectClient", + !txn->getClient()->isInDirectClient()); + txn->setDeadlineAfterNowBy(Milliseconds{maxTimeMS}); + } // Operations are only versioned against the primary. We also make sure not to redo shard // version handling if this command was issued via the direct client. diff --git a/src/mongo/db/ftdc/ftdc_test.cpp b/src/mongo/db/ftdc/ftdc_test.cpp index fe10d5db21e..b09aa2b6ef5 100644 --- a/src/mongo/db/ftdc/ftdc_test.cpp +++ b/src/mongo/db/ftdc/ftdc_test.cpp @@ -44,6 +44,7 @@ #include "mongo/unittest/unittest.h" #include "mongo/util/clock_source.h" #include "mongo/util/clock_source_mock.h" +#include "mongo/util/tick_source_mock.h" namespace mongo { @@ -114,6 +115,7 @@ MONGO_INITIALIZER_WITH_PREREQUISITES(FTDCTestInit, getGlobalServiceContext()->setFastClockSource(stdx::make_unique<ClockSourceMock>()); getGlobalServiceContext()->setPreciseClockSource(stdx::make_unique<ClockSourceMock>()); + getGlobalServiceContext()->setTickSource(stdx::make_unique<TickSourceMock>()); Client::initThreadIfNotAlready("UnitTest"); diff --git a/src/mongo/db/operation_context.cpp b/src/mongo/db/operation_context.cpp index 2006c4f0470..18c98a047a0 100644 --- a/src/mongo/db/operation_context.cpp +++ b/src/mongo/db/operation_context.cpp @@ -39,6 +39,7 @@ #include "mongo/util/clock_source.h" #include "mongo/util/fail_point_service.h" #include "mongo/util/log.h" +#include "mongo/util/system_tick_source.h" namespace mongo { @@ -72,69 +73,80 @@ MONGO_FP_DECLARE(checkForInterruptFail); } // namespace OperationContext::OperationContext(Client* client, unsigned int opId, Locker* locker) - : _client(client), _opId(opId), _locker(locker) {} + : _client(client), + _opId(opId), + _locker(locker), + _elapsedTime(client ? client->getServiceContext()->getTickSource() + : SystemTickSource::get()) {} void OperationContext::markKilled(ErrorCodes::Error killCode) { invariant(killCode != ErrorCodes::OK); _killCode.compareAndSwap(ErrorCodes::OK, killCode); } -void OperationContext::setDeadlineByDate(Date_t when) { +void OperationContext::setDeadlineAndMaxTime(Date_t when, Microseconds maxTime) { + invariant(!getClient()->isInDirectClient()); + uassert(40120, "Illegal attempt to change operation deadline", !hasDeadline()); _deadline = when; + _maxTime = maxTime; +} + +void OperationContext::setDeadlineByDate(Date_t when) { + Microseconds maxTime; + if (when == Date_t::max()) { + maxTime = Microseconds::max(); + } else { + maxTime = when - getServiceContext()->getFastClockSource()->now(); + if (maxTime < Microseconds::zero()) { + maxTime = Microseconds::zero(); + } + } + setDeadlineAndMaxTime(when, maxTime); } -void OperationContext::setDeadlineRelativeToNow(Milliseconds maxTimeMs) { - auto clock = getServiceContext()->getFastClockSource(); - setDeadlineByDate(clock->now() + clock->getPrecision() + maxTimeMs); +void OperationContext::setDeadlineAfterNowBy(Microseconds maxTime) { + Date_t when; + if (maxTime < Microseconds::zero()) { + maxTime = Microseconds::zero(); + } + if (maxTime == Microseconds::max()) { + when = Date_t::max(); + } else { + auto clock = getServiceContext()->getFastClockSource(); + when = clock->now(); + if (maxTime > Microseconds::zero()) { + when += clock->getPrecision() + maxTime; + } + } + setDeadlineAndMaxTime(when, maxTime); } bool OperationContext::hasDeadlineExpired() const { + if (!hasDeadline()) { + return false; + } if (MONGO_FAIL_POINT(maxTimeNeverTimeOut)) { return false; } - if (hasDeadline() && MONGO_FAIL_POINT(maxTimeAlwaysTimeOut)) { + if (MONGO_FAIL_POINT(maxTimeAlwaysTimeOut)) { return true; } - const auto now = getServiceContext()->getFastClockSource()->now(); - return now >= getDeadline(); -} - -Milliseconds OperationContext::getTimeUntilDeadline() const { - const auto now = getServiceContext()->getFastClockSource()->now(); - return getDeadline() - now; -} - -void OperationContext::setMaxTimeMicros(uint64_t maxTimeMicros) { - const auto maxTimeMicrosSigned = static_cast<int64_t>(maxTimeMicros); - if (maxTimeMicrosSigned <= 0) { - // Do not adjust the deadline if the user specified "0", meaning "forever", or - // if they chose a value too large to represent as a 64-bit signed integer. - return; - } - if (maxTimeMicrosSigned == 1) { - // This indicates that the time is already expired. Set the deadline to the epoch. - setDeadlineByDate(Date_t{}); - return; + // TODO: Remove once all OperationContexts are properly connected to Clients and ServiceContexts + // in tests. + if (MONGO_unlikely(!getClient() || !getServiceContext())) { + return false; } - setDeadlineRelativeToNow(Microseconds{maxTimeMicrosSigned}); -} -bool OperationContext::isMaxTimeSet() const { - return hasDeadline(); + const auto now = getServiceContext()->getFastClockSource()->now(); + return now >= getDeadline(); } -uint64_t OperationContext::getRemainingMaxTimeMicros() const { +Microseconds OperationContext::getRemainingMaxTimeMicros() const { if (!hasDeadline()) { - return 0U; + return Microseconds::max(); } - const auto microsRemaining = durationCount<Microseconds>(getTimeUntilDeadline()); - if (microsRemaining <= 0) { - // If the operation deadline has passed, say there is 1 microsecond remaining, to - // distinguish from 0, which means infinity. - return 1U; - } - return static_cast<uint64_t>(microsRemaining); + return _maxTime - getElapsedTime(); } void OperationContext::checkForInterrupt() { @@ -165,14 +177,10 @@ bool opShouldFail(const OperationContext* opCtx, const BSONObj& failPointInfo) { } // namespace Status OperationContext::checkForInterruptNoAssert() { - // TODO: Remove once all OperationContexts are properly connected to Clients and ServiceContexts - // in tests. - if (MONGO_unlikely(!getClient() || !getServiceContext() || - !getServiceContext()->getFastClockSource())) { - return Status::OK(); - } - - if (getServiceContext() && getServiceContext()->getKillAllOperations()) { + // TODO: Remove the MONGO_likely(getClient()) once all operation contexts are constructed with + // clients. + if (MONGO_likely(getClient() && getServiceContext()) && + getServiceContext()->getKillAllOperations()) { return Status(ErrorCodes::InterruptedAtShutdown, "interrupted at shutdown"); } diff --git a/src/mongo/db/operation_context.h b/src/mongo/db/operation_context.h index 70fae5f4f1b..e586d5cd9d9 100644 --- a/src/mongo/db/operation_context.h +++ b/src/mongo/db/operation_context.h @@ -38,6 +38,7 @@ #include "mongo/platform/atomic_word.h" #include "mongo/util/decorable.h" #include "mongo/util/time_support.h" +#include "mongo/util/timer.h" namespace mongo { @@ -218,6 +219,14 @@ public: } /** + * Returns the amount of time since the operation was constructed. Uses the system's most + * precise tick source, and may not be cheap to call in a tight loop. + */ + Microseconds getElapsedTime() const { + return _elapsedTime.elapsed(); + } + + /** * Sets the deadline for this operation to the given point in time. * * To remove a deadline, pass in Date_t::max(). @@ -225,13 +234,20 @@ public: void setDeadlineByDate(Date_t when); /** - * Sets the deadline for this operation to the maxTimeMs plus the current time reported - * by the ServiceContext. + * Sets the deadline for this operation to the maxTime plus the current time reported + * by the ServiceContext's fast clock source. */ - void setDeadlineRelativeToNow(Milliseconds maxTimeMs); + void setDeadlineAfterNowBy(Microseconds maxTime); template <typename D> - void setDeadlineRelativeToNow(D maxTime) { - setDeadlineRelativeToNow(duration_cast<Milliseconds>(maxTime)); + void setDeadlineAfterNowBy(D maxTime) { + if (maxTime <= D::zero()) { + maxTime = D::zero(); + } + if (maxTime <= Microseconds::max()) { + setDeadlineAfterNowBy(duration_cast<Microseconds>(maxTime)); + } else { + setDeadlineByDate(Date_t::max()); + } } /** @@ -248,35 +264,15 @@ public: return _deadline; } - /** - * Returns the amount of time until the deadline, according to the fast clock on - * ServiceContext. If this value is less than zero, the deadline has passed. - */ - Milliseconds getTimeUntilDeadline() const; - // // Legacy "max time" methods for controlling operation deadlines. // /** - * Sets the amount of time operation this should be allowed to run, units of microseconds. - * The special value 0 is "allow to run indefinitely". - */ - void setMaxTimeMicros(uint64_t maxTimeMicros); - - /** - * Returns true if a time limit has been set on this operation, and false otherwise. - */ - bool isMaxTimeSet() const; - - /** * Returns the number of microseconds remaining for this operation's time limit, or the - * special value 0 if the operation has no time limit. - * - * This method is virtual because some subclasses used for tests override it. It should not - * remain virtual. + * special value Microseconds::max() if the operation has no time limit. */ - virtual uint64_t getRemainingMaxTimeMicros() const; + Microseconds getRemainingMaxTimeMicros() const; protected: OperationContext(Client* client, unsigned int opId, Locker* locker); @@ -290,6 +286,12 @@ private: */ bool hasDeadlineExpired() const; + /** + * Sets the deadline and maxTime as described. It is up to the caller to ensure that + * these correctly correspond. + */ + void setDeadlineAndMaxTime(Date_t when, Microseconds maxTime); + friend class WriteUnitOfWork; Client* const _client; const unsigned int _opId; @@ -306,6 +308,16 @@ private: Date_t _deadline = Date_t::max(); // The timepoint at which this operation exceeds its time limit. + + // Max operation time requested by the user or by the cursor in the case of a getMore with no + // user-specified maxTime. This is tracked with microsecond granularity for the purpose of + // assigning unused execution time back to a cursor at the end of an operation, only. The + // _deadline and the service context's fast clock are the only values consulted for determining + // if the operation's timelimit has been exceeded. + Microseconds _maxTime = Microseconds::max(); + + // Timer counting the elapsed time since the construction of this OperationContext. + Timer _elapsedTime; }; class WriteUnitOfWork { diff --git a/src/mongo/db/operation_context_test.cpp b/src/mongo/db/operation_context_test.cpp index b1210afc358..cae366b845f 100644 --- a/src/mongo/db/operation_context_test.cpp +++ b/src/mongo/db/operation_context_test.cpp @@ -29,11 +29,13 @@ #include "mongo/platform/basic.h" #include "mongo/db/client.h" +#include "mongo/db/operation_context.h" #include "mongo/db/service_context.h" #include "mongo/db/service_context_noop.h" #include "mongo/stdx/memory.h" #include "mongo/unittest/unittest.h" #include "mongo/util/clock_source_mock.h" +#include "mongo/util/tick_source_mock.h" #include "mongo/util/time_support.h" namespace mongo { @@ -47,26 +49,81 @@ public: mockClock = uniqueMockClock.get(); service = stdx::make_unique<ServiceContextNoop>(); service->setFastClockSource(std::move(uniqueMockClock)); + service->setTickSource(stdx::make_unique<TickSourceMock>()); + client = service->makeClient("OperationDeadlineTest"); } ClockSourceMock* mockClock; std::unique_ptr<ServiceContext> service; + ServiceContext::UniqueClient client; }; TEST_F(OperationDeadlineTests, OperationDeadlineExpiration) { - auto client = service->makeClient("CurOpTest"); auto txn = client->makeOperationContext(); - txn->setMaxTimeMicros(durationCount<Microseconds>(Seconds{1})); + txn->setDeadlineAfterNowBy(Seconds{1}); mockClock->advance(Milliseconds{500}); ASSERT_OK(txn->checkForInterruptNoAssert()); + + // 1ms before relative deadline reports no interrupt mockClock->advance(Milliseconds{499}); ASSERT_OK(txn->checkForInterruptNoAssert()); + + // Exactly at deadline reports no interrupt, because setDeadlineAfterNowBy adds one clock + // precision unit to the deadline, to ensure that the deadline does not expire in less than the + // requested amount of time. + mockClock->advance(Milliseconds{1}); + ASSERT_OK(txn->checkForInterruptNoAssert()); + + // Since the mock clock's precision is 1ms, at test start + 1001 ms, we expect + // checkForInterruptNoAssert to return ExceededTimeLimit. mockClock->advance(Milliseconds{1}); ASSERT_EQ(ErrorCodes::ExceededTimeLimit, txn->checkForInterruptNoAssert()); + + // Also at times greater than start + 1001ms, we expect checkForInterruptNoAssert to keep + // returning ExceededTimeLimit. mockClock->advance(Milliseconds{1}); ASSERT_EQ(ErrorCodes::ExceededTimeLimit, txn->checkForInterruptNoAssert()); } +template <typename D> +void assertLargeRelativeDeadlineLikeInfinity(Client& client, D maxTime) { + auto txn = client.makeOperationContext(); + txn->setDeadlineAfterNowBy(maxTime); + ASSERT_FALSE(txn->hasDeadline()) << "Tried to set maxTime to " << maxTime; +} + +TEST_F(OperationDeadlineTests, VeryLargeRelativeDeadlinesHours) { + ASSERT_FALSE(client->makeOperationContext()->hasDeadline()); + assertLargeRelativeDeadlineLikeInfinity(*client, Hours::max()); +} + +TEST_F(OperationDeadlineTests, VeryLargeRelativeDeadlinesMinutes) { + assertLargeRelativeDeadlineLikeInfinity(*client, Minutes::max()); +} + +TEST_F(OperationDeadlineTests, VeryLargeRelativeDeadlinesSeconds) { + assertLargeRelativeDeadlineLikeInfinity(*client, Seconds::max()); +} + +TEST_F(OperationDeadlineTests, VeryLargeRelativeDeadlinesMilliseconds) { + assertLargeRelativeDeadlineLikeInfinity(*client, Milliseconds::max()); +} + +TEST_F(OperationDeadlineTests, VeryLargeRelativeDeadlinesMicroseconds) { + assertLargeRelativeDeadlineLikeInfinity(*client, Microseconds::max()); +} + +TEST_F(OperationDeadlineTests, VeryLargeRelativeDeadlinesNanoseconds) { + // Nanoseconds::max() is less than Microseconds::max(), so it is possible to set + // a deadline of that duration. + auto txn = client->makeOperationContext(); + txn->setDeadlineAfterNowBy(Nanoseconds::max()); + ASSERT_TRUE(txn->hasDeadline()); + ASSERT_EQ(mockClock->now() + mockClock->getPrecision() + + duration_cast<Milliseconds>(Nanoseconds::max()), + txn->getDeadline()); +} + } // namespace } // namespace mongo diff --git a/src/mongo/db/pipeline/document_source_test.cpp b/src/mongo/db/pipeline/document_source_test.cpp index 80423154a8d..841acf89b64 100644 --- a/src/mongo/db/pipeline/document_source_test.cpp +++ b/src/mongo/db/pipeline/document_source_test.cpp @@ -41,6 +41,7 @@ #include "mongo/db/storage/storage_options.h" #include "mongo/dbtests/dbtests.h" #include "mongo/util/clock_source_mock.h" +#include "mongo/util/tick_source_mock.h" #include "mongo/unittest/temp_dir.h" namespace mongo { @@ -51,6 +52,7 @@ bool isMongos() { std::unique_ptr<ServiceContextNoop> makeTestServiceContext() { auto service = stdx::make_unique<ServiceContextNoop>(); service->setFastClockSource(stdx::make_unique<ClockSourceMock>()); + service->setTickSource(stdx::make_unique<TickSourceMock>()); return service; } } diff --git a/src/mongo/db/query/find.cpp b/src/mongo/db/query/find.cpp index 7d979e4a891..0888b5e7085 100644 --- a/src/mongo/db/query/find.cpp +++ b/src/mongo/db/query/find.cpp @@ -323,7 +323,12 @@ QueryResult::View getMore(OperationContext* txn, // If the operation that spawned this cursor had a time limit set, apply leftover // time to this getmore. - txn->setMaxTimeMicros(cc->getLeftoverMaxTimeMicros()); + if (cc->getLeftoverMaxTimeMicros() < Microseconds::max()) { + uassert(40136, + "Illegal attempt to set operation deadline within DBDirectClient", + !txn->getClient()->isInDirectClient()); + txn->setDeadlineAfterNowBy(cc->getLeftoverMaxTimeMicros()); + } txn->checkForInterrupt(); // May trigger maxTimeAlwaysTimeOut fail point. // Ensure that the original query or command object is available in the slow query log, @@ -547,7 +552,12 @@ std::string runQuery(OperationContext* txn, } // Handle query option $maxTimeMS (not used with commands). - txn->setMaxTimeMicros(static_cast<unsigned long long>(pq.getMaxTimeMS()) * 1000); + if (pq.getMaxTimeMS() > 0) { + uassert(40116, + "Illegal attempt to set operation deadline within DBDirectClient", + !txn->getClient()->isInDirectClient()); + txn->setDeadlineAfterNowBy(Milliseconds{pq.getMaxTimeMS()}); + } txn->checkForInterrupt(); // May trigger maxTimeAlwaysTimeOut fail point. // uassert if we are not on a primary, and not a secondary with SlaveOk query parameter set. diff --git a/src/mongo/db/repl/operation_context_repl_mock.cpp b/src/mongo/db/repl/operation_context_repl_mock.cpp index b5d0660c1be..6770ea72ab6 100644 --- a/src/mongo/db/repl/operation_context_repl_mock.cpp +++ b/src/mongo/db/repl/operation_context_repl_mock.cpp @@ -49,14 +49,6 @@ OperationContextReplMock::OperationContextReplMock(Client* client, unsigned int OperationContextReplMock::~OperationContextReplMock() = default; -uint64_t OperationContextReplMock::getRemainingMaxTimeMicros() const { - return _maxTimeMicrosRemaining; -} - -void OperationContextReplMock::setRemainingMaxTimeMicros(uint64_t micros) { - _maxTimeMicrosRemaining = micros; -} - void OperationContextReplMock::setReplicatedWrites(bool writesAreReplicated) { _writesAreReplicated = writesAreReplicated; } diff --git a/src/mongo/db/repl/operation_context_repl_mock.h b/src/mongo/db/repl/operation_context_repl_mock.h index d28633e11dd..d3aee770588 100644 --- a/src/mongo/db/repl/operation_context_repl_mock.h +++ b/src/mongo/db/repl/operation_context_repl_mock.h @@ -49,10 +49,6 @@ public: OperationContextReplMock(Client* client, unsigned int opNum); virtual ~OperationContextReplMock(); - virtual uint64_t getRemainingMaxTimeMicros() const override; - - void setRemainingMaxTimeMicros(uint64_t micros); - void setReplicatedWrites(bool writesAreReplicated = true) override; bool writesAreReplicated() const override; diff --git a/src/mongo/db/repl/replication_coordinator_impl.cpp b/src/mongo/db/repl/replication_coordinator_impl.cpp index 01addb5cfac..14eba0564ed 100644 --- a/src/mongo/db/repl/replication_coordinator_impl.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl.cpp @@ -1095,14 +1095,11 @@ ReadConcernResponse ReplicationCoordinatorImpl::waitUntilOpTime(OperationContext // for a new snapshot. if (isMajorityReadConcern) { // Wait for a snapshot that meets our needs (< targetOpTime). - const auto waitTime = txn->isMaxTimeSet() - ? Microseconds(static_cast<int64_t>(txn->getRemainingMaxTimeMicros())) - : Microseconds{0}; - const auto waitForever = waitTime == Microseconds{0}; - LOG(2) << "waitUntilOpTime: waiting for a new snapshot to occur for micros: " - << waitTime; - if (!waitForever) { - _currentCommittedSnapshotCond.wait_for(lock, waitTime.toSystemDuration()); + if (txn->hasDeadline()) { + LOG(2) << "waitUntilOpTime: waiting for a new snapshot to occur until: " + << txn->getDeadline(); + _currentCommittedSnapshotCond.wait_until(lock, + txn->getDeadline().toSystemTimePoint()); } else { _currentCommittedSnapshotCond.wait(lock); } @@ -1116,10 +1113,8 @@ ReadConcernResponse ReplicationCoordinatorImpl::waitUntilOpTime(OperationContext WaiterInfo waitInfo(&_opTimeWaiterList, txn->getOpID(), &targetOpTime, nullptr, &condVar); LOG(3) << "Waiting for OpTime: " << waitInfo; - if (txn->isMaxTimeSet()) { - condVar.wait_for(lock, - Microseconds(static_cast<int64_t>(txn->getRemainingMaxTimeMicros())) - .toSystemDuration()); + if (txn->hasDeadline()) { + condVar.wait_until(lock, txn->getDeadline().toSystemTimePoint()); } else { condVar.wait(lock); } @@ -1545,12 +1540,7 @@ ReplicationCoordinator::StatusAndDuration ReplicationCoordinatorImpl::_awaitRepl Status(ErrorCodes::ShutdownInProgress, "Replication is being shut down"), elapsed); } - const Microseconds maxTimeMicrosRemaining{ - static_cast<int64_t>(txn->getRemainingMaxTimeMicros())}; - Microseconds waitTime = Microseconds::max(); - if (maxTimeMicrosRemaining != Microseconds::zero()) { - waitTime = maxTimeMicrosRemaining; - } + Microseconds waitTime = txn->getRemainingMaxTimeMicros(); if (writeConcern.wTimeout != WriteConcernOptions::kNoTimeout) { waitTime = std::min<Microseconds>(Milliseconds{writeConcern.wTimeout} - elapsed, waitTime); @@ -3340,11 +3330,10 @@ void ReplicationCoordinatorImpl::waitUntilSnapshotCommitted(OperationContext* tx stdx::unique_lock<stdx::mutex> lock(_mutex); while (!_currentCommittedSnapshot || _currentCommittedSnapshot->name < untilSnapshot) { - Microseconds waitTime(static_cast<int64_t>(txn->getRemainingMaxTimeMicros())); - if (waitTime == Microseconds(0)) { + if (!txn->hasDeadline()) { _currentCommittedSnapshotCond.wait(lock); } else { - _currentCommittedSnapshotCond.wait_for(lock, waitTime.toSystemDuration()); + _currentCommittedSnapshotCond.wait_until(lock, txn->getDeadline().toSystemTimePoint()); } txn->checkForInterrupt(); } diff --git a/src/mongo/db/s/operation_sharding_state.cpp b/src/mongo/db/s/operation_sharding_state.cpp index 01a0132df07..c6fe82ff6d0 100644 --- a/src/mongo/db/s/operation_sharding_state.cpp +++ b/src/mongo/db/s/operation_sharding_state.cpp @@ -40,7 +40,7 @@ const OperationContext::Decoration<OperationShardingState> shardingMetadataDecor OperationContext::declareDecoration<OperationShardingState>(); // Max time to wait for the migration critical section to complete -const Minutes kMaxWaitForMigrationCriticalSection(5); +const Microseconds kMaxWaitForMigrationCriticalSection = Minutes(5); } // namespace mongo @@ -101,12 +101,10 @@ bool OperationShardingState::waitForMigrationCriticalSection(OperationContext* t invariant(!txn->lockState()->isLocked()); if (_migrationCriticalSection) { - const Microseconds operationRemainingTime( - Microseconds(static_cast<int64_t>(txn->getRemainingMaxTimeMicros()))); _migrationCriticalSection->waitUntilOutOfCriticalSection( - durationCount<Microseconds>(operationRemainingTime) - ? operationRemainingTime - : Microseconds{kMaxWaitForMigrationCriticalSection}); + txn->hasDeadline() + ? std::min(txn->getRemainingMaxTimeMicros(), kMaxWaitForMigrationCriticalSection) + : kMaxWaitForMigrationCriticalSection); _migrationCriticalSection = nullptr; return true; } diff --git a/src/mongo/db/s/sharding_state.cpp b/src/mongo/db/s/sharding_state.cpp index 0d89f81f678..c69fe446a90 100644 --- a/src/mongo/db/s/sharding_state.cpp +++ b/src/mongo/db/s/sharding_state.cpp @@ -122,20 +122,6 @@ VersionChoice chooseNewestVersion(ChunkVersion prevLocalVersion, return VersionChoice::Remote; } -Date_t getDeadlineFromMaxTimeMS(OperationContext* txn) { - auto remainingTime = txn->getRemainingMaxTimeMicros(); - if (remainingTime == 0) { - return Date_t::max(); - } - - if (remainingTime == 1) { - // 1 means maxTimeMS has exceeded. - return Date_t::now(); - } - - return Date_t::now() + Microseconds(static_cast<int64_t>(remainingTime)); -} - /** * Updates the config server field of the shardIdentity document with the given connection string * if setName is equal to the config server replica set name. @@ -401,7 +387,7 @@ void ShardingState::initializeFromConfigConnString(OperationContext* txn, const } } - uassertStatusOK(_waitForInitialization(getDeadlineFromMaxTimeMS(txn))); + uassertStatusOK(_waitForInitialization(txn->getDeadline())); uassertStatusOK(reloadShardRegistryUntilSuccess(txn)); updateConfigServerOpTimeFromMetadata(txn); } @@ -432,8 +418,7 @@ Status ShardingState::initializeFromShardIdentity(OperationContext* txn) { return parseStatus.getStatus(); } - auto status = - initializeFromShardIdentity(parseStatus.getValue(), getDeadlineFromMaxTimeMS(txn)); + auto status = initializeFromShardIdentity(parseStatus.getValue(), txn->getDeadline()); if (!status.isOK()) { return status; } @@ -636,7 +621,7 @@ Status ShardingState::_refreshMetadata(OperationContext* txn, ChunkVersion* latestShardVersion) { invariant(!txn->lockState()->isLocked()); - Status status = _waitForInitialization(getDeadlineFromMaxTimeMS(txn)); + Status status = _waitForInitialization(txn->getDeadline()); if (!status.isOK()) return status; diff --git a/src/mongo/db/s/sharding_state_test.cpp b/src/mongo/db/s/sharding_state_test.cpp index 1f5eecf6f21..44d5cb7de24 100644 --- a/src/mongo/db/s/sharding_state_test.cpp +++ b/src/mongo/db/s/sharding_state_test.cpp @@ -52,6 +52,7 @@ #include "mongo/s/query/cluster_cursor_manager.h" #include "mongo/unittest/unittest.h" #include "mongo/util/clock_source_mock.h" +#include "mongo/util/tick_source_mock.h" namespace mongo { @@ -117,6 +118,7 @@ public: void setUp() override { _service.setFastClockSource(stdx::make_unique<ClockSourceMock>()); _service.setPreciseClockSource(stdx::make_unique<ClockSourceMock>()); + _service.setTickSource(stdx::make_unique<TickSourceMock>()); serverGlobalParams.clusterRole = ClusterRole::ShardServer; _client = _service.makeClient("ShardingStateTest"); diff --git a/src/mongo/s/balancer/balancer.cpp b/src/mongo/s/balancer/balancer.cpp index 203e3e5ce3d..c08ab24b900 100644 --- a/src/mongo/s/balancer/balancer.cpp +++ b/src/mongo/s/balancer/balancer.cpp @@ -58,6 +58,7 @@ #include "mongo/util/exit.h" #include "mongo/util/fail_point_service.h" #include "mongo/util/log.h" +#include "mongo/util/represent_as.h" #include "mongo/util/timer.h" #include "mongo/util/version.h" @@ -147,6 +148,25 @@ void warnOnMultiVersion(const vector<ClusterStatistics::ShardStatistics>& cluste warning() << sb.str(); } +void appendOperationDeadlineIfSet(OperationContext* txn, BSONObjBuilder* cmdBuilder) { + if (!txn->hasDeadline()) { + return; + } + // Treat a remaining max time less than 1ms as 1ms, since any smaller is treated as infinity + // or an error on the receiving node. + const auto remainingMicros = std::max(txn->getRemainingMaxTimeMicros(), Microseconds{1000}); + const auto maxTimeMsArg = representAs<int32_t>(durationCount<Milliseconds>(remainingMicros)); + + // We know that remainingMicros > 1000us, so if maxTimeMsArg is not engaged, it is because + // remainingMicros was too big to represent as a 32-bit signed integer number of + // milliseconds. In that case, we omit a maxTimeMs argument on the command, implying "no max + // time". + if (!maxTimeMsArg) { + return; + } + cmdBuilder->append(LiteParsedQuery::cmdOptionMaxTimeMS, *maxTimeMsArg); +} + /** * Blocking method, which requests a single chunk migration to run. */ @@ -178,9 +198,7 @@ Status executeSingleMigration(OperationContext* txn, maxChunkSizeBytes, secondaryThrottle, waitForDelete); - builder.append(LiteParsedQuery::cmdOptionMaxTimeMS, - durationCount<Milliseconds>( - Microseconds(static_cast<int64_t>(txn->getRemainingMaxTimeMicros())))); + appendOperationDeadlineIfSet(txn, &builder); BSONObj cmdObj = builder.obj(); diff --git a/src/mongo/s/catalog/replset/SConscript b/src/mongo/s/catalog/replset/SConscript index 3f01696eeb7..582b9a26aea 100644 --- a/src/mongo/s/catalog/replset/SConscript +++ b/src/mongo/s/catalog/replset/SConscript @@ -55,7 +55,7 @@ env.CppUnitTest( '$BUILD_DIR/mongo/s/coreshard', '$BUILD_DIR/mongo/s/mongoscore', '$BUILD_DIR/mongo/s/sharding_test_fixture', - '$BUILD_DIR/mongo/util/tick_source_mock', + '$BUILD_DIR/mongo/util/clock_source_mock', ] ) diff --git a/src/mongo/s/client/shard_remote.cpp b/src/mongo/s/client/shard_remote.cpp index a5e21e9becd..ef033ff718a 100644 --- a/src/mongo/s/client/shard_remote.cpp +++ b/src/mongo/s/client/shard_remote.cpp @@ -51,6 +51,7 @@ #include "mongo/s/grid.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" +#include "mongo/util/time_support.h" namespace mongo { @@ -91,27 +92,27 @@ const BSONObj kReplSecondaryOkMetadata{[] { * set on it that is the minimum of the maxTimeMS in 'cmdObj' (if present), 'maxTimeMicros', and * 30 seconds. */ -BSONObj appendMaxTimeToCmdObj(long long maxTimeMicros, const BSONObj& cmdObj) { - Milliseconds maxTime = duration_cast<Milliseconds>(kConfigCommandTimeout); +BSONObj appendMaxTimeToCmdObj(OperationContext* txn, const BSONObj& cmdObj) { + Milliseconds maxTime = kConfigCommandTimeout; - Milliseconds remainingTxnMaxTime = duration_cast<Milliseconds>(Microseconds(maxTimeMicros)); - bool hasTxnMaxTime(remainingTxnMaxTime != Microseconds::zero()); + bool hasTxnMaxTime = txn->hasDeadline(); bool hasUserMaxTime = !cmdObj[LiteParsedQuery::cmdOptionMaxTimeMS].eoo(); if (hasTxnMaxTime) { - if (remainingTxnMaxTime < maxTime) { - maxTime = remainingTxnMaxTime; + maxTime = std::min(maxTime, duration_cast<Milliseconds>(txn->getRemainingMaxTimeMicros())); + if (maxTime <= Milliseconds::zero()) { + // If there is less than 1ms remaining before the maxTime timeout expires, set the max + // time to 1ms, since setting maxTimeMs to 1ms in a command means "no max time". + + maxTime = Milliseconds{1}; } } if (hasUserMaxTime) { Milliseconds userMaxTime(cmdObj[LiteParsedQuery::cmdOptionMaxTimeMS].numberLong()); - if (userMaxTime == maxTime) { + if (userMaxTime <= maxTime) { return cmdObj; } - if (userMaxTime < maxTime) { - maxTime = userMaxTime; - } } BSONObjBuilder updatedCmdBuilder; @@ -199,8 +200,7 @@ StatusWith<Shard::CommandResponse> ShardRemote::_runCommand(OperationContext* tx const ReadPreferenceSetting& readPref, const string& dbName, const BSONObj& cmdObj) { - const BSONObj cmdWithMaxTimeMS = - (isConfig() ? appendMaxTimeToCmdObj(txn->getRemainingMaxTimeMicros(), cmdObj) : cmdObj); + const BSONObj cmdWithMaxTimeMS = (isConfig() ? appendMaxTimeToCmdObj(txn, cmdObj) : cmdObj); const auto host = _targeter->findHost(readPref, RemoteCommandTargeter::selectFindHostMaxWaitTime(txn)); @@ -332,10 +332,12 @@ StatusWith<Shard::QueryResponse> ShardRemote::_exhaustiveFindOnConfig( BSONObjBuilder findCmdBuilder; lpq->asFindCommand(&findCmdBuilder); - Milliseconds maxTime = kConfigCommandTimeout; - Microseconds remainingTxnMaxTime(static_cast<int64_t>(txn->getRemainingMaxTimeMicros())); - if (remainingTxnMaxTime != Microseconds::zero()) { - maxTime = duration_cast<Milliseconds>(remainingTxnMaxTime); + Microseconds maxTime = std::min(duration_cast<Microseconds>(kConfigCommandTimeout), + txn->getRemainingMaxTimeMicros()); + if (maxTime < Milliseconds{1}) { + // If there is less than 1ms remaining before the maxTime timeout expires, set the max time + // to 1ms, since setting maxTimeMs to 1ms in a find command means "no max time". + maxTime = Milliseconds{1}; } findCmdBuilder.append(LiteParsedQuery::cmdOptionMaxTimeMS, @@ -347,7 +349,7 @@ StatusWith<Shard::QueryResponse> ShardRemote::_exhaustiveFindOnConfig( findCmdBuilder.done(), fetcherCallback, _getMetadataForCommand(readPref), - maxTime); + duration_cast<Milliseconds>(maxTime)); Status scheduleStatus = fetcher.schedule(); if (!scheduleStatus.isOK()) { return scheduleStatus; diff --git a/src/mongo/s/sharding_test_fixture.cpp b/src/mongo/s/sharding_test_fixture.cpp index 86921c8c75b..76d1ff9322a 100644 --- a/src/mongo/s/sharding_test_fixture.cpp +++ b/src/mongo/s/sharding_test_fixture.cpp @@ -65,6 +65,7 @@ #include "mongo/s/write_ops/batched_command_response.h" #include "mongo/stdx/memory.h" #include "mongo/util/clock_source_mock.h" +#include "mongo/util/tick_source_mock.h" namespace mongo { @@ -89,6 +90,7 @@ void ShardingTestFixture::setUp() { _service = stdx::make_unique<ServiceContextNoop>(); _service->setFastClockSource(stdx::make_unique<ClockSourceMock>()); _service->setPreciseClockSource(stdx::make_unique<ClockSourceMock>()); + _service->setTickSource(stdx::make_unique<TickSourceMock>()); _messagePort = stdx::make_unique<MessagingPortMock>(); _client = _service->makeClient("ShardingTestFixture", _messagePort.get()); _opCtx = _client->makeOperationContext(); diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript index d1716c98713..8583220b9e7 100644 --- a/src/mongo/util/SConscript +++ b/src/mongo/util/SConscript @@ -129,16 +129,10 @@ env.Library( ) env.Library( - target='tick_source_mock', - source=[ - 'tick_source_mock.cpp', - ], -) - -env.Library( target='clock_source_mock', source=[ 'clock_source_mock.cpp', + 'tick_source_mock.cpp', ], ) @@ -492,9 +486,9 @@ env.CppUnitTest( env.CppUnitTest( target='duration_test', -source=[ - 'duration_test.cpp', -], -LIBDEPS=[ - '$BUILD_DIR/mongo/base', -]) + source=[ + 'duration_test.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + ]) diff --git a/src/mongo/util/timer.h b/src/mongo/util/timer.h index c16ac255206..05a5936c69e 100644 --- a/src/mongo/util/timer.h +++ b/src/mongo/util/timer.h @@ -29,6 +29,8 @@ #pragma once +#include "mongo/util/time_support.h" + namespace mongo { @@ -76,6 +78,10 @@ public: return static_cast<long long>((now() - _old) * _microsPerCount); } + Microseconds elapsed() const { + return Microseconds{micros()}; + } + inline void reset() { _old = now(); } |