summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndy Schwerin <schwerin@mongodb.com>2016-05-02 10:55:01 -0400
committerAndy Schwerin <schwerin@mongodb.com>2016-05-23 10:28:31 -0400
commit2e627487ef0475c46143b5f57d3e7c3d3027d5dc (patch)
tree7ad552be9a3cae113bc3cfd9df0faea78aa50e24 /src
parentc9aac9d6eaba6ef2eb8903f07e997b594e88addc (diff)
downloadmongo-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')
-rw-r--r--src/mongo/db/clientcursor.cpp2
-rw-r--r--src/mongo/db/clientcursor.h21
-rw-r--r--src/mongo/db/commands/getmore_cmd.cpp17
-rw-r--r--src/mongo/db/dbcommands.cpp7
-rw-r--r--src/mongo/db/ftdc/ftdc_test.cpp2
-rw-r--r--src/mongo/db/operation_context.cpp102
-rw-r--r--src/mongo/db/operation_context.h66
-rw-r--r--src/mongo/db/operation_context_test.cpp61
-rw-r--r--src/mongo/db/pipeline/document_source_test.cpp2
-rw-r--r--src/mongo/db/query/find.cpp14
-rw-r--r--src/mongo/db/repl/operation_context_repl_mock.cpp8
-rw-r--r--src/mongo/db/repl/operation_context_repl_mock.h4
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl.cpp31
-rw-r--r--src/mongo/db/s/operation_sharding_state.cpp10
-rw-r--r--src/mongo/db/s/sharding_state.cpp21
-rw-r--r--src/mongo/db/s/sharding_state_test.cpp2
-rw-r--r--src/mongo/s/balancer/balancer.cpp24
-rw-r--r--src/mongo/s/catalog/replset/SConscript2
-rw-r--r--src/mongo/s/client/shard_remote.cpp36
-rw-r--r--src/mongo/s/sharding_test_fixture.cpp2
-rw-r--r--src/mongo/util/SConscript20
-rw-r--r--src/mongo/util/timer.h6
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();
}