diff options
author | Ben Caimano <ben.caimano@mongodb.com> | 2019-10-31 16:02:44 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-10-31 16:02:44 +0000 |
commit | 3a05e4ce75cd03b09b52ec5943e1801c6d91279b (patch) | |
tree | b4d892be3527e03c6ca73d4be182988bebccce02 /src/mongo | |
parent | 5b60211d31ccb75c02348b346272f3cd387c1dc6 (diff) | |
download | mongo-3a05e4ce75cd03b09b52ec5943e1801c6d91279b.tar.gz |
SERVER-41357 SERVER-41360 SERVER-43324 Add Mutex, BasicLockableAdaptor, and DiagnosticInfo
This commit backports Mutex, BasicLockableAdaptor, and DiagnosticInfo to
v4.2. These pieces came in completely in various interleaved commits.
The constituent commits partially included in this backport are listed
below. The majority of these commits are part of the "Improved
Diagnostics for Latches" epic.
Mutex:
- SERVER-41357
- SERVER-41362
- SERVER-42893
- SERVER-42595
- SERVER-42165
- SERVER-42895
BasicLockableAdaptor:
- SERVER-43324
- SERVER-43374
- SERVER-43800
DiagnosticInfo:
- SERVER-41360
- SERVER-41362
- SERVER-41358
- SERVER-41364
- SERVER-42448
- SERVER-42363
- SERVER-42363
- SERVER-42492
- SERVER-42595
- SERVER-44086
Note that while this commit adds these pieces and integrates them into
the mongo-server codebase, it does not convert existing stdx::mutex
construction. It also does not include the updates to Interruptible that
were done as part of "Improved Diagnostics for Latches".
Diffstat (limited to 'src/mongo')
34 files changed, 1134 insertions, 126 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript index 931c0bb4c67..e2863e13212 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -103,6 +103,7 @@ env.Library( 'logger/rotatable_file_manager.cpp', 'logger/rotatable_file_writer.cpp', 'platform/decimal128.cpp', + 'platform/mutex.cpp', 'platform/posix_fadvise.cpp', 'platform/process_id.cpp', 'platform/random.cpp', diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 086f866d667..c473d43c3d5 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -209,6 +209,7 @@ env.Library( '$BUILD_DIR/mongo/db/service_context', '$BUILD_DIR/mongo/db/query/command_request_response', '$BUILD_DIR/mongo/rpc/client_metadata', + '$BUILD_DIR/mongo/util/diagnostic_info', '$BUILD_DIR/mongo/util/fail_point', '$BUILD_DIR/mongo/util/net/network', '$BUILD_DIR/mongo/util/progress_meter', diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp index 551bed75d54..ebacc7fea0a 100644 --- a/src/mongo/db/curop.cpp +++ b/src/mongo/db/curop.cpp @@ -235,8 +235,10 @@ CurOp* CurOp::get(const OperationContext& opCtx) { void CurOp::reportCurrentOpForClient(OperationContext* opCtx, Client* client, bool truncateOps, + bool backtraceMode, BSONObjBuilder* infoBuilder) { invariant(client); + OperationContext* clientOpCtx = client->getOperationContext(); infoBuilder->append("type", "op"); @@ -301,6 +303,22 @@ void CurOp::reportCurrentOpForClient(OperationContext* opCtx, CurOp::get(clientOpCtx)->reportState(infoBuilder, truncateOps); } + + if (auto diagnostic = DiagnosticInfo::get(*client)) { + BSONObjBuilder waitingForLatchBuilder(infoBuilder->subobjStart("waitingForLatch")); + waitingForLatchBuilder.append("timestamp", diagnostic->getTimestamp()); + waitingForLatchBuilder.append("captureName", diagnostic->getCaptureName()); + if (backtraceMode) { + BSONArrayBuilder backtraceBuilder(waitingForLatchBuilder.subarrayStart("backtrace")); + /** This branch becomes useful again with SERVER-44091 + for (const auto& frame : diagnostic->makeStackTrace().frames) { + BSONObjBuilder backtraceObj(backtraceBuilder.subobjStart()); + backtraceObj.append("addr", integerToHex(frame.instructionOffset)); + backtraceObj.append("path", frame.objectPath); + } + */ + } + } } void CurOp::setGenericCursor_inlock(GenericCursor gc) { diff --git a/src/mongo/db/curop.h b/src/mongo/db/curop.h index 7444316ad33..37985a76865 100644 --- a/src/mongo/db/curop.h +++ b/src/mongo/db/curop.h @@ -36,6 +36,7 @@ #include "mongo/db/operation_context.h" #include "mongo/db/server_options.h" #include "mongo/platform/atomic_word.h" +#include "mongo/util/diagnostic_info.h" #include "mongo/util/progress_meter.h" #include "mongo/util/time_support.h" @@ -275,6 +276,7 @@ public: static void reportCurrentOpForClient(OperationContext* opCtx, Client* client, bool truncateOps, + bool backtraceMode, BSONObjBuilder* infoBuilder); /** diff --git a/src/mongo/db/operation_context.cpp b/src/mongo/db/operation_context.cpp index 4690f4e38a2..c7f50fe40bd 100644 --- a/src/mongo/db/operation_context.cpp +++ b/src/mongo/db/operation_context.cpp @@ -261,7 +261,7 @@ Status OperationContext::checkForInterruptNoAssert() noexcept { // - _baton is notified (someone's queuing work for the baton) // - _baton::run returns (timeout fired / networking is ready / socket disconnected) StatusWith<stdx::cv_status> OperationContext::waitForConditionOrInterruptNoAssertUntil( - stdx::condition_variable& cv, stdx::unique_lock<stdx::mutex>& m, Date_t deadline) noexcept { + stdx::condition_variable& cv, BasicLockableAdapter m, Date_t deadline) noexcept { invariant(getClient()); if (auto status = checkForInterruptNoAssert(); !status.isOK()) { diff --git a/src/mongo/db/operation_context.h b/src/mongo/db/operation_context.h index 26dc7a7723c..025e8f7c1f3 100644 --- a/src/mongo/db/operation_context.h +++ b/src/mongo/db/operation_context.h @@ -46,6 +46,7 @@ #include "mongo/transport/session.h" #include "mongo/util/decorable.h" #include "mongo/util/interruptible.h" +#include "mongo/util/lockable_adapter.h" #include "mongo/util/time_support.h" #include "mongo/util/timer.h" @@ -352,9 +353,7 @@ public: Microseconds getRemainingMaxTimeMicros() const; StatusWith<stdx::cv_status> waitForConditionOrInterruptNoAssertUntil( - stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, - Date_t deadline) noexcept override; + stdx::condition_variable& cv, BasicLockableAdapter m, Date_t deadline) noexcept override; bool isIgnoringInterrupts() const; diff --git a/src/mongo/db/pipeline/document_source_current_op.cpp b/src/mongo/db/pipeline/document_source_current_op.cpp index cbdf5eae988..3160a988c65 100644 --- a/src/mongo/db/pipeline/document_source_current_op.cpp +++ b/src/mongo/db/pipeline/document_source_current_op.cpp @@ -42,6 +42,7 @@ const StringData kIdleSessionsFieldName = "idleSessions"_sd; const StringData kLocalOpsFieldName = "localOps"_sd; const StringData kTruncateOpsFieldName = "truncateOps"_sd; const StringData kIdleCursorsFieldName = "idleCursors"_sd; +const StringData kBacktraceFieldName = "backtrace"_sd; const StringData kOpIdFieldName = "opid"_sd; const StringData kClientFieldName = "client"_sd; @@ -116,7 +117,8 @@ DocumentSource::GetNextResult DocumentSourceCurrentOp::getNext() { _includeIdleSessions, _includeOpsFromAllUsers, _truncateOps, - _idleCursors); + _idleCursors, + _backtrace); _opsIter = _ops.begin(); @@ -190,6 +192,7 @@ intrusive_ptr<DocumentSource> DocumentSourceCurrentOp::createFromBson( LocalOpsMode showLocalOpsOnMongoS = LocalOpsMode::kRemoteShardOps; TruncationMode truncateOps = TruncationMode::kNoTruncation; CursorMode idleCursors = CursorMode::kExcludeCursors; + BacktraceMode backtrace = BacktraceMode::kExcludeBacktrace; for (auto&& elem : spec.embeddedObject()) { const auto fieldName = elem.fieldNameStringData(); @@ -243,6 +246,14 @@ intrusive_ptr<DocumentSource> DocumentSourceCurrentOp::createFromBson( elem.type() == BSONType::Bool); idleCursors = (elem.boolean() ? CursorMode::kIncludeCursors : CursorMode::kExcludeCursors); + } else if (fieldName == kBacktraceFieldName) { + uassert(ErrorCodes::FailedToParse, + str::stream() << "The 'backtrace' parameter of the $currentOp stage must be " + "a boolean value, but found: " + << typeName(elem.type()), + elem.type() == BSONType::Bool); + backtrace = (elem.boolean() ? BacktraceMode::kIncludeBacktrace + : BacktraceMode::kExcludeBacktrace); } else { uasserted(ErrorCodes::FailedToParse, str::stream() @@ -256,7 +267,8 @@ intrusive_ptr<DocumentSource> DocumentSourceCurrentOp::createFromBson( includeOpsFromAllUsers, showLocalOpsOnMongoS, truncateOps, - idleCursors); + idleCursors, + backtrace); } intrusive_ptr<DocumentSourceCurrentOp> DocumentSourceCurrentOp::create( @@ -266,14 +278,16 @@ intrusive_ptr<DocumentSourceCurrentOp> DocumentSourceCurrentOp::create( UserMode includeOpsFromAllUsers, LocalOpsMode showLocalOpsOnMongoS, TruncationMode truncateOps, - CursorMode idleCursors) { + CursorMode idleCursors, + BacktraceMode backtrace) { return new DocumentSourceCurrentOp(pExpCtx, includeIdleConnections, includeIdleSessions, includeOpsFromAllUsers, showLocalOpsOnMongoS, truncateOps, - idleCursors); + idleCursors, + backtrace); } Value DocumentSourceCurrentOp::serialize(boost::optional<ExplainOptions::Verbosity> explain) const { @@ -290,6 +304,8 @@ Value DocumentSourceCurrentOp::serialize(boost::optional<ExplainOptions::Verbosi {kTruncateOpsFieldName, _truncateOps == TruncationMode::kTruncateOps ? Value(true) : Value()}, {kIdleCursorsFieldName, - _idleCursors == CursorMode::kIncludeCursors ? Value(true) : Value()}}}}); + _idleCursors == CursorMode::kIncludeCursors ? Value(true) : Value()}, + {kBacktraceFieldName, + _backtrace == BacktraceMode::kIncludeBacktrace ? Value(true) : Value()}}}}); } } // namespace mongo diff --git a/src/mongo/db/pipeline/document_source_current_op.h b/src/mongo/db/pipeline/document_source_current_op.h index 0aa281f56c6..0e86973a009 100644 --- a/src/mongo/db/pipeline/document_source_current_op.h +++ b/src/mongo/db/pipeline/document_source_current_op.h @@ -41,6 +41,7 @@ public: using SessionMode = MongoProcessInterface::CurrentOpSessionsMode; using UserMode = MongoProcessInterface::CurrentOpUserMode; using CursorMode = MongoProcessInterface::CurrentOpCursorMode; + using BacktraceMode = MongoProcessInterface::CurrentOpBacktraceMode; static constexpr StringData kStageName = "$currentOp"_sd; @@ -98,7 +99,8 @@ public: UserMode includeOpsFromAllUsers = UserMode::kExcludeOthers, LocalOpsMode showLocalOpsOnMongoS = LocalOpsMode::kRemoteShardOps, TruncationMode truncateOps = TruncationMode::kNoTruncation, - CursorMode idleCursors = CursorMode::kExcludeCursors); + CursorMode idleCursors = CursorMode::kExcludeCursors, + BacktraceMode backtrace = BacktraceMode::kExcludeBacktrace); GetNextResult getNext() final; @@ -136,14 +138,16 @@ private: UserMode includeOpsFromAllUsers, LocalOpsMode showLocalOpsOnMongoS, TruncationMode truncateOps, - CursorMode idleCursors) + CursorMode idleCursors, + BacktraceMode backtrace) : DocumentSource(pExpCtx), _includeIdleConnections(includeIdleConnections), _includeIdleSessions(includeIdleSessions), _includeOpsFromAllUsers(includeOpsFromAllUsers), _showLocalOpsOnMongoS(showLocalOpsOnMongoS), _truncateOps(truncateOps), - _idleCursors(idleCursors) {} + _idleCursors(idleCursors), + _backtrace(backtrace) {} ConnMode _includeIdleConnections = ConnMode::kExcludeIdle; SessionMode _includeIdleSessions = SessionMode::kIncludeIdle; @@ -151,6 +155,7 @@ private: LocalOpsMode _showLocalOpsOnMongoS = LocalOpsMode::kRemoteShardOps; TruncationMode _truncateOps = TruncationMode::kNoTruncation; CursorMode _idleCursors = CursorMode::kExcludeCursors; + BacktraceMode _backtrace = BacktraceMode::kExcludeBacktrace; std::string _shardName; diff --git a/src/mongo/db/pipeline/document_source_current_op_test.cpp b/src/mongo/db/pipeline/document_source_current_op_test.cpp index 7f72327a51c..f9cd2a91b14 100644 --- a/src/mongo/db/pipeline/document_source_current_op_test.cpp +++ b/src/mongo/db/pipeline/document_source_current_op_test.cpp @@ -71,7 +71,8 @@ public: CurrentOpSessionsMode sessionMode, CurrentOpUserMode userMode, CurrentOpTruncateMode truncateMode, - CurrentOpCursorMode cursorMode) const { + CurrentOpCursorMode cursorMode, + CurrentOpBacktraceMode backtraceMode) const { return _ops; } diff --git a/src/mongo/db/pipeline/mongo_process_common.cpp b/src/mongo/db/pipeline/mongo_process_common.cpp index 054848c045d..3d20d646258 100644 --- a/src/mongo/db/pipeline/mongo_process_common.cpp +++ b/src/mongo/db/pipeline/mongo_process_common.cpp @@ -27,6 +27,8 @@ * it in the license file. */ +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault + #include "mongo/platform/basic.h" #include "mongo/db/pipeline/mongo_process_common.h" @@ -39,8 +41,12 @@ #include "mongo/db/operation_context.h" #include "mongo/db/pipeline/expression_context.h" #include "mongo/db/service_context.h" +#include "mongo/platform/atomic_word.h" +#include "mongo/platform/mutex.h" #include "mongo/s/catalog_cache.h" #include "mongo/s/grid.h" +#include "mongo/util/diagnostic_info.h" +#include "mongo/util/log.h" #include "mongo/util/net/socket_utils.h" namespace mongo { @@ -51,12 +57,15 @@ std::vector<BSONObj> MongoProcessCommon::getCurrentOps( CurrentOpSessionsMode sessionMode, CurrentOpUserMode userMode, CurrentOpTruncateMode truncateMode, - CurrentOpCursorMode cursorMode) const { + CurrentOpCursorMode cursorMode, + CurrentOpBacktraceMode backtraceMode) const { OperationContext* opCtx = expCtx->opCtx; AuthorizationSession* ctxAuth = AuthorizationSession::get(opCtx->getClient()); std::vector<BSONObj> ops; + auto blockedOpGuard = DiagnosticInfo::maybeMakeBlockedOpForTest(opCtx->getClient()); + for (ServiceContext::LockedClientsCursor cursor(opCtx->getClient()->getServiceContext()); Client* client = cursor.next();) { invariant(client); @@ -76,7 +85,7 @@ std::vector<BSONObj> MongoProcessCommon::getCurrentOps( } // Delegate to the mongoD- or mongoS-specific implementation of _reportCurrentOpForClient. - ops.emplace_back(_reportCurrentOpForClient(opCtx, client, truncateMode)); + ops.emplace_back(_reportCurrentOpForClient(opCtx, client, truncateMode, backtraceMode)); } // If 'cursorMode' is set to include idle cursors, retrieve them and add them to ops. diff --git a/src/mongo/db/pipeline/mongo_process_common.h b/src/mongo/db/pipeline/mongo_process_common.h index 2ebe835d4ab..ff32827cba1 100644 --- a/src/mongo/db/pipeline/mongo_process_common.h +++ b/src/mongo/db/pipeline/mongo_process_common.h @@ -57,7 +57,8 @@ public: CurrentOpSessionsMode sessionMode, CurrentOpUserMode userMode, CurrentOpTruncateMode truncateMode, - CurrentOpCursorMode cursorMode) const final; + CurrentOpCursorMode cursorMode, + CurrentOpBacktraceMode backtraceMode) const final; virtual std::vector<FieldPath> collectDocumentKeyFieldsActingAsRouter( OperationContext*, const NamespaceString&) const override; @@ -81,7 +82,8 @@ protected: */ virtual BSONObj _reportCurrentOpForClient(OperationContext* opCtx, Client* client, - CurrentOpTruncateMode truncateOps) const = 0; + CurrentOpTruncateMode truncateOps, + CurrentOpBacktraceMode backtraceMode) const = 0; /** * Iterates through all entries in the local SessionCatalog, and adds an entry to the 'ops' diff --git a/src/mongo/db/pipeline/mongo_process_interface.h b/src/mongo/db/pipeline/mongo_process_interface.h index ae7e707e3e9..be897699a6f 100644 --- a/src/mongo/db/pipeline/mongo_process_interface.h +++ b/src/mongo/db/pipeline/mongo_process_interface.h @@ -91,6 +91,7 @@ public: enum class CurrentOpLocalOpsMode { kLocalMongosOps, kRemoteShardOps }; enum class CurrentOpSessionsMode { kIncludeIdle, kExcludeIdle }; enum class CurrentOpCursorMode { kIncludeCursors, kExcludeCursors }; + enum class CurrentOpBacktraceMode { kIncludeBacktrace, kExcludeBacktrace }; /** * Factory function to create MongoProcessInterface of the right type. The implementation will @@ -277,7 +278,8 @@ public: CurrentOpSessionsMode sessionMode, CurrentOpUserMode userMode, CurrentOpTruncateMode, - CurrentOpCursorMode) const = 0; + CurrentOpCursorMode, + CurrentOpBacktraceMode) const = 0; /** * Returns the name of the local shard if sharding is enabled, or an empty string. diff --git a/src/mongo/db/pipeline/mongos_process_interface.cpp b/src/mongo/db/pipeline/mongos_process_interface.cpp index 7c5539088c9..cec0279587e 100644 --- a/src/mongo/db/pipeline/mongos_process_interface.cpp +++ b/src/mongo/db/pipeline/mongos_process_interface.cpp @@ -241,11 +241,15 @@ boost::optional<Document> MongoSInterface::lookupSingleDocument( BSONObj MongoSInterface::_reportCurrentOpForClient(OperationContext* opCtx, Client* client, - CurrentOpTruncateMode truncateOps) const { + CurrentOpTruncateMode truncateOps, + CurrentOpBacktraceMode backtraceMode) const { BSONObjBuilder builder; - CurOp::reportCurrentOpForClient( - opCtx, client, (truncateOps == CurrentOpTruncateMode::kTruncateOps), &builder); + CurOp::reportCurrentOpForClient(opCtx, + client, + (truncateOps == CurrentOpTruncateMode::kTruncateOps), + (backtraceMode == CurrentOpBacktraceMode::kIncludeBacktrace), + &builder); OperationContext* clientOpCtx = client->getOperationContext(); diff --git a/src/mongo/db/pipeline/mongos_process_interface.h b/src/mongo/db/pipeline/mongos_process_interface.h index 172890cf742..0e5583a85a4 100644 --- a/src/mongo/db/pipeline/mongos_process_interface.h +++ b/src/mongo/db/pipeline/mongos_process_interface.h @@ -236,7 +236,8 @@ public: protected: BSONObj _reportCurrentOpForClient(OperationContext* opCtx, Client* client, - CurrentOpTruncateMode truncateOps) const final; + CurrentOpTruncateMode truncateOps, + CurrentOpBacktraceMode backtraceMode) const final; void _reportCurrentOpsForIdleSessions(OperationContext* opCtx, CurrentOpUserMode userMode, diff --git a/src/mongo/db/pipeline/process_interface_standalone.cpp b/src/mongo/db/pipeline/process_interface_standalone.cpp index bf34f8ce84e..fed8b7076e8 100644 --- a/src/mongo/db/pipeline/process_interface_standalone.cpp +++ b/src/mongo/db/pipeline/process_interface_standalone.cpp @@ -536,11 +536,17 @@ bool MongoInterfaceStandalone::fieldsHaveSupportingUniqueIndex( } BSONObj MongoInterfaceStandalone::_reportCurrentOpForClient( - OperationContext* opCtx, Client* client, CurrentOpTruncateMode truncateOps) const { + OperationContext* opCtx, + Client* client, + CurrentOpTruncateMode truncateOps, + CurrentOpBacktraceMode backtraceMode) const { BSONObjBuilder builder; - CurOp::reportCurrentOpForClient( - opCtx, client, (truncateOps == CurrentOpTruncateMode::kTruncateOps), &builder); + CurOp::reportCurrentOpForClient(opCtx, + client, + (truncateOps == CurrentOpTruncateMode::kTruncateOps), + (backtraceMode == CurrentOpBacktraceMode::kIncludeBacktrace), + &builder); OperationContext* clientOpCtx = client->getOperationContext(); diff --git a/src/mongo/db/pipeline/process_interface_standalone.h b/src/mongo/db/pipeline/process_interface_standalone.h index ecdb5a2c0d1..e306fca6436 100644 --- a/src/mongo/db/pipeline/process_interface_standalone.h +++ b/src/mongo/db/pipeline/process_interface_standalone.h @@ -153,7 +153,8 @@ public: protected: BSONObj _reportCurrentOpForClient(OperationContext* opCtx, Client* client, - CurrentOpTruncateMode truncateOps) const final; + CurrentOpTruncateMode truncateOps, + CurrentOpBacktraceMode backtraceMode) const final; void _reportCurrentOpsForIdleSessions(OperationContext* opCtx, CurrentOpUserMode userMode, diff --git a/src/mongo/db/pipeline/stub_mongo_process_interface.h b/src/mongo/db/pipeline/stub_mongo_process_interface.h index a8a4a02e1e4..e32048e0a80 100644 --- a/src/mongo/db/pipeline/stub_mongo_process_interface.h +++ b/src/mongo/db/pipeline/stub_mongo_process_interface.h @@ -146,7 +146,8 @@ public: CurrentOpSessionsMode sessionMode, CurrentOpUserMode userMode, CurrentOpTruncateMode truncateMode, - CurrentOpCursorMode cursorMode) const override { + CurrentOpCursorMode cursorMode, + CurrentOpBacktraceMode backtraceMode) const override { MONGO_UNREACHABLE; } diff --git a/src/mongo/platform/SConscript b/src/mongo/platform/SConscript index a1b1e9ae134..62805813b64 100644 --- a/src/mongo/platform/SConscript +++ b/src/mongo/platform/SConscript @@ -8,6 +8,7 @@ env.CppUnitTest('atomic_proxy_test', 'atomic_proxy_test.cpp') env.CppUnitTest('atomic_word_test', 'atomic_word_test.cpp') env.CppUnitTest('bits_test', 'bits_test.cpp') env.CppUnitTest('endian_test', 'endian_test.cpp') +env.CppUnitTest('mutex_test', 'mutex_test.cpp') env.CppUnitTest('process_id_test', 'process_id_test.cpp') env.CppUnitTest('random_test', 'random_test.cpp') env.CppUnitTest('stack_locator_test', 'stack_locator_test.cpp') diff --git a/src/mongo/platform/mutex.cpp b/src/mongo/platform/mutex.cpp new file mode 100644 index 00000000000..3f2a18fe58a --- /dev/null +++ b/src/mongo/platform/mutex.cpp @@ -0,0 +1,91 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/mutex.h" + +namespace mongo { + +void Mutex::lock() { + if (_mutex.try_lock()) { + _onQuickLock(_name); + return; + } + + _onContendedLock(_name); + _mutex.lock(); + _onSlowLock(_name); +} +void Mutex::unlock() { + _onUnlock(_name); + _mutex.unlock(); +} +bool Mutex::try_lock() { + if (!_mutex.try_lock()) { + return false; + } + + _onQuickLock(_name); + return true; +} + +void Mutex::addLockListener(LockListener* listener) { + auto& state = _getListenerState(); + + state.list.push_back(listener); +} + +void Mutex::_onContendedLock(const StringData& name) noexcept { + auto& state = _getListenerState(); + for (auto listener : state.list) { + listener->onContendedLock(name); + } +} + +void Mutex::_onQuickLock(const StringData& name) noexcept { + auto& state = _getListenerState(); + for (auto listener : state.list) { + listener->onQuickLock(name); + } +} + +void Mutex::_onSlowLock(const StringData& name) noexcept { + auto& state = _getListenerState(); + for (auto listener : state.list) { + listener->onSlowLock(name); + } +} + +void Mutex::_onUnlock(const StringData& name) noexcept { + auto& state = _getListenerState(); + for (auto listener : state.list) { + listener->onUnlock(name); + } +} + +} // namespace mongo diff --git a/src/mongo/platform/mutex.h b/src/mongo/platform/mutex.h new file mode 100644 index 00000000000..dd6bd4996a0 --- /dev/null +++ b/src/mongo/platform/mutex.h @@ -0,0 +1,143 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <type_traits> + +#include "mongo/base/error_codes.h" +#include "mongo/base/string_data.h" +#include "mongo/platform/atomic_word.h" +#include "mongo/stdx/mutex.h" +#include "mongo/util/duration.h" + +namespace mongo { + +class Latch { +public: + virtual ~Latch() = default; + + virtual void lock() = 0; + virtual void unlock() = 0; + virtual bool try_lock() = 0; + + virtual StringData getName() const { + return "AnonymousLatch"_sd; + } +}; + +class Mutex : public Latch { + class LockNotifier; + +public: + class LockListener; + + static constexpr auto kAnonymousMutexStr = "AnonymousMutex"_sd; + + Mutex() : Mutex(kAnonymousMutexStr) {} + // Note that StringData is a view type, thus the underlying string for _name must outlive any + // given Mutex + explicit Mutex(const StringData& name) : _name(name) {} + + void lock() override; + void unlock() override; + bool try_lock() override; + StringData getName() const override { + return _name; + } + + /** + * This function adds a LockListener subclass to the triggers for certain actions. + * + * LockListeners can only be added and not removed. If you wish to deactivate a LockListeners + * subclass, please provide the switch on that subclass to noop its functions. It is only safe + * to add a LockListener during a MONGO_INITIALIZER. + */ + static void addLockListener(LockListener* listener); + +private: + static auto& _getListenerState() noexcept { + struct State { + std::vector<LockListener*> list; + }; + + // Note that state should no longer be mutated after init-time (ala MONGO_INITIALIZERS). If + // this changes, than this state needs to be synchronized. + static State state; + return state; + } + + static void _onContendedLock(const StringData& name) noexcept; + static void _onQuickLock(const StringData& name) noexcept; + static void _onSlowLock(const StringData& name) noexcept; + static void _onUnlock(const StringData& name) noexcept; + + const StringData _name; + stdx::mutex _mutex; // NOLINT +}; + +/** + * A set of actions to happen upon notable events on a Lockable-conceptualized type + */ +class Mutex::LockListener { + friend class Mutex; + +public: + virtual ~LockListener() = default; + + /** + * Action to do when a lock cannot be immediately acquired + */ + virtual void onContendedLock(const StringData& name) = 0; + + /** + * Action to do when a lock was acquired without blocking + */ + virtual void onQuickLock(const StringData& name) = 0; + + /** + * Action to do when a lock was acquired after blocking + */ + virtual void onSlowLock(const StringData& name) = 0; + + /** + * Action to do when a lock is unlocked + */ + virtual void onUnlock(const StringData& name) = 0; +}; + +} // namespace mongo + +/** + * Define a mongo::Mutex with all arguments passed through to the ctor + */ +#define MONGO_MAKE_LATCH(...) \ + mongo::Mutex { \ + __VA_ARGS__ \ + } diff --git a/src/mongo/platform/mutex_test.cpp b/src/mongo/platform/mutex_test.cpp new file mode 100644 index 00000000000..739734c0e46 --- /dev/null +++ b/src/mongo/platform/mutex_test.cpp @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/unittest/unittest.h" + +#include "mongo/platform/mutex.h" + +namespace mongo { +TEST(MutexTest, BasicSingleThread) { + Mutex m; + m.lock(); + ASSERT(!m.try_lock()); + m.unlock(); + ASSERT(m.try_lock()); + m.unlock(); +} + +namespace { +// Since this MONGO_MAKE_LATCH has no arguments, the mutex is anonymous +auto gMutex = MONGO_MAKE_LATCH(); +} // anonymous namespace + +TEST(MutexTest, Macros) { + // Verify a pure inline latch + auto constexpr kLatchName = "inlineLatchForTest"; + auto m1 = MONGO_MAKE_LATCH(kLatchName); + static_assert(std::is_same_v<decltype(m1), Mutex>); + ASSERT_EQ(m1.getName(), kLatchName); + + // Verify a class member latch + struct MutexHolder { + Mutex m2 = MONGO_MAKE_LATCH("MutexHolder"); + } holder; + static_assert(std::is_same_v<decltype(MutexHolder::m2), Mutex>); + ASSERT_NE(holder.m2.getName().find("MutexHolder"_sd), std::string::npos); + ASSERT_EQ(holder.m2.getName().find("m2"_sd), std::string::npos) << holder.m2.getName(); + + // Verify the global named latch + static_assert(std::is_same_v<decltype(gMutex), Mutex>); + ASSERT_EQ(gMutex.getName(), Mutex::kAnonymousMutexStr); +} +} // namespace mongo diff --git a/src/mongo/stdx/condition_variable.h b/src/mongo/stdx/condition_variable.h index 2f8a0ca6484..a27567ee13b 100644 --- a/src/mongo/stdx/condition_variable.h +++ b/src/mongo/stdx/condition_variable.h @@ -88,18 +88,17 @@ class Waitable; namespace stdx { -using condition_variable_any = ::std::condition_variable_any; // NOLINT -using cv_status = ::std::cv_status; // NOLINT -using ::std::notify_all_at_thread_exit; // NOLINT +using cv_status = ::std::cv_status; // NOLINT +using ::std::notify_all_at_thread_exit; // NOLINT /** - * We wrap std::condition_variable to allow us to register Notifyables which can "wait" on the - * condvar without actually waiting on the std::condition_variable. This allows us to possibly do - * productive work in those types, rather than sleeping in the os. + * We wrap std::condition_variable_any to allow us to register Notifyables which can "wait" on the + * condvar without actually waiting on the std::condition_variable_any. This allows us to possibly + * do productive work in those types, rather than sleeping in the os. */ -class condition_variable : private std::condition_variable { // NOLINT +class condition_variable : private std::condition_variable_any { // NOLINT public: - using std::condition_variable::condition_variable; // NOLINT + using std::condition_variable_any::condition_variable_any; // NOLINT void notify_one() noexcept { if (_notifyableCount.load()) { @@ -110,7 +109,7 @@ public: } } - std::condition_variable::notify_one(); // NOLINT + std::condition_variable_any::notify_one(); // NOLINT } void notify_all() noexcept { @@ -121,13 +120,12 @@ public: } } - std::condition_variable::notify_all(); // NOLINT + std::condition_variable_any::notify_all(); // NOLINT } - using std::condition_variable::native_handle; // NOLINT - using std::condition_variable::wait; // NOLINT - using std::condition_variable::wait_for; // NOLINT - using std::condition_variable::wait_until; // NOLINT + using std::condition_variable_any::wait; // NOLINT + using std::condition_variable_any::wait_for; // NOLINT + using std::condition_variable_any::wait_until; // NOLINT private: friend class ::mongo::Waitable; @@ -212,5 +210,6 @@ private: std::list<Notifyable*> _notifyables; }; +using condition_variable_any = stdx::condition_variable; } // namespace stdx } // namespace mongo diff --git a/src/mongo/transport/baton_asio_linux.h b/src/mongo/transport/baton_asio_linux.h index 3536bc16ab4..0db7fda5230 100644 --- a/src/mongo/transport/baton_asio_linux.h +++ b/src/mongo/transport/baton_asio_linux.h @@ -305,7 +305,8 @@ public: // If we don't have a timeout, or we have a timeout that's unexpired, run poll. if (!deadline || (*deadline > now)) { if (deadline && !clkSource->tracksSystemClock()) { - invariant(clkSource->setAlarm(*deadline, [this] { notify(); })); + invariant(clkSource->setAlarm(*deadline, + [this, anchor = shared_from_this()] { notify(); })); deadline.reset(); } diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript index 302f78794a6..0ec84ecf8d1 100644 --- a/src/mongo/util/SConscript +++ b/src/mongo/util/SConscript @@ -274,6 +274,17 @@ env.Library( ], ) +env.Library( + target='diagnostic_info', + source= [ + 'diagnostic_info.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + "$BUILD_DIR/mongo/db/service_context", + ], +) + env.Benchmark( target='clock_source_bm', source=[ @@ -514,6 +525,7 @@ icuEnv.CppUnitTest( 'container_size_helper_test.cpp', 'decimal_counter_test.cpp', 'decorable_test.cpp', + 'diagnostic_info_test.cpp', 'dns_name_test.cpp', 'dns_query_test.cpp', 'duration_test.cpp', @@ -530,6 +542,7 @@ icuEnv.CppUnitTest( 'icu_test.cpp', 'invalidating_lru_cache_test.cpp', 'itoa_test.cpp', + 'lockable_adapter_test.cpp', 'lru_cache_test.cpp', 'md5_test.cpp', 'md5main.cpp', @@ -560,6 +573,7 @@ icuEnv.CppUnitTest( 'background_job', 'clock_source_mock', 'clock_sources', + 'diagnostic_info', 'dns_query', 'fail_point', 'icu', diff --git a/src/mongo/util/clock_source.cpp b/src/mongo/util/clock_source.cpp index 8be344ba282..a3fb3aabeb1 100644 --- a/src/mongo/util/clock_source.cpp +++ b/src/mongo/util/clock_source.cpp @@ -35,61 +35,59 @@ namespace mongo { stdx::cv_status ClockSource::waitForConditionUntil(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, + BasicLockableAdapter bla, Date_t deadline, Waitable* waitable) { if (_tracksSystemClock) { if (deadline == Date_t::max()) { - Waitable::wait(waitable, this, cv, m); + Waitable::wait(waitable, this, cv, bla); return stdx::cv_status::no_timeout; } - return Waitable::wait_until(waitable, this, cv, m, deadline.toSystemTimePoint()); + return Waitable::wait_until(waitable, this, cv, bla, deadline.toSystemTimePoint()); } // The rest of this function only runs during testing, when the clock source is virtualized and // does not track the system clock. - if (deadline <= now()) { + auto currentTime = now(); + if (deadline <= currentTime) { return stdx::cv_status::timeout; } struct AlarmInfo { - stdx::mutex controlMutex; - stdx::mutex* waitMutex; - stdx::condition_variable* waitCV; - stdx::cv_status cvWaitResult = stdx::cv_status::no_timeout; + stdx::mutex mutex; // NOLINT + + stdx::condition_variable* cv; + stdx::cv_status result = stdx::cv_status::no_timeout; }; auto alarmInfo = std::make_shared<AlarmInfo>(); - alarmInfo->waitCV = &cv; - alarmInfo->waitMutex = m.mutex(); - const auto waiterThreadId = stdx::this_thread::get_id(); - bool invokedAlarmInline = false; - invariant(setAlarm(deadline, [alarmInfo, waiterThreadId, &invokedAlarmInline] { - stdx::lock_guard<stdx::mutex> controlLk(alarmInfo->controlMutex); - alarmInfo->cvWaitResult = stdx::cv_status::timeout; - if (!alarmInfo->waitMutex) { - return; - } - if (stdx::this_thread::get_id() == waiterThreadId) { - // In NetworkInterfaceMock, setAlarm may invoke its callback immediately if the deadline - // has expired, so we detect that case and avoid self-deadlock by returning early, here. - // It is safe to set invokedAlarmInline without synchronization in this case, because it - // is exactly the case where the same thread is writing and consulting the value. - invokedAlarmInline = true; + alarmInfo->cv = &cv; + + invariant(setAlarm(deadline, [alarmInfo] { + // Set an alarm to hit our virtualized deadline + stdx::lock_guard infoLk(alarmInfo->mutex); + auto cv = std::exchange(alarmInfo->cv, nullptr); + if (!cv) { return; } - stdx::lock_guard<stdx::mutex> waitLk(*alarmInfo->waitMutex); - alarmInfo->waitCV->notify_all(); + + alarmInfo->result = stdx::cv_status::timeout; + cv->notify_all(); })); - if (!invokedAlarmInline) { - Waitable::wait(waitable, this, cv, m); + + if (stdx::lock_guard infoLk(alarmInfo->mutex); !alarmInfo->cv) { + // If setAlarm() ran inline, then we've timed out + return alarmInfo->result; } - m.unlock(); - stdx::lock_guard<stdx::mutex> controlLk(alarmInfo->controlMutex); - m.lock(); - alarmInfo->waitMutex = nullptr; - alarmInfo->waitCV = nullptr; - return alarmInfo->cvWaitResult; + + // This is a wait_until because theoretically setAlarm could run out of line before this cv + // joins the wait list. Then it could completely miss the notification and block until a lucky + // renotify or spurious wakeup. + Waitable::wait_until(waitable, this, cv, bla, currentTime + kMaxTimeoutForArtificialClocks); + + stdx::lock_guard infoLk(alarmInfo->mutex); + alarmInfo->cv = nullptr; + return alarmInfo->result; } } // namespace mongo diff --git a/src/mongo/util/clock_source.h b/src/mongo/util/clock_source.h index 0ce72e09be7..b51776ceaeb 100644 --- a/src/mongo/util/clock_source.h +++ b/src/mongo/util/clock_source.h @@ -34,6 +34,7 @@ #include "mongo/stdx/condition_variable.h" #include "mongo/stdx/mutex.h" #include "mongo/util/functional.h" +#include "mongo/util/lockable_adapter.h" #include "mongo/util/time_support.h" namespace mongo { @@ -47,13 +48,15 @@ class ClockSource { // We need a type trait to differentiate waitable ptr args from predicates. // // This returns true for non-pointers and function pointers - template <typename Pred> - struct CouldBePredicate - : public std::integral_constant<bool, - !std::is_pointer<Pred>::value || - std::is_function<std::remove_pointer_t<Pred>>::value> { + template <typename PredicateT> + struct CouldBePredicate : public std::integral_constant< + bool, + !std::is_pointer<PredicateT>::value || + std::is_function<std::remove_pointer_t<PredicateT>>::value> { }; + static constexpr auto kMaxTimeoutForArtificialClocks = Seconds(1); + public: virtual ~ClockSource() = default; @@ -89,9 +92,13 @@ public: /** * Like cv.wait_until(m, deadline), but uses this ClockSource instead of * stdx::chrono::system_clock to measure the passage of time. + * + * Note that this can suffer spurious wakeups like cw.wait_until() and, when used with a mocked + * clock source, may sleep in system time for kMaxTimeoutForArtificialClocks due to unfortunate + * implementation details. */ stdx::cv_status waitForConditionUntil(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, + BasicLockableAdapter m, Date_t deadline, Waitable* waitable = nullptr); @@ -99,11 +106,13 @@ public: * Like cv.wait_until(m, deadline, pred), but uses this ClockSource instead of * stdx::chrono::system_clock to measure the passage of time. */ - template <typename Pred, std::enable_if_t<CouldBePredicate<Pred>::value, int> = 0> + template <typename LockT, + typename PredicateT, + std::enable_if_t<CouldBePredicate<PredicateT>::value, int> = 0> bool waitForConditionUntil(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, + LockT& m, Date_t deadline, - const Pred& pred, + const PredicateT& pred, Waitable* waitable = nullptr) { while (!pred()) { if (waitForConditionUntil(cv, m, deadline, waitable) == stdx::cv_status::timeout) { @@ -117,13 +126,14 @@ public: * Like cv.wait_for(m, duration, pred), but uses this ClockSource instead of * stdx::chrono::system_clock to measure the passage of time. */ - template <typename Duration, - typename Pred, - std::enable_if_t<CouldBePredicate<Pred>::value, int> = 0> + template <typename LockT, + typename Duration, + typename PredicateT, + std::enable_if_t<CouldBePredicate<PredicateT>::value, int> = 0> bool waitForConditionFor(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, + LockT& m, Duration duration, - const Pred& pred, + const PredicateT& pred, Waitable* waitable = nullptr) { return waitForConditionUntil(cv, m, now() + duration, pred, waitable); } diff --git a/src/mongo/util/diagnostic_info.cpp b/src/mongo/util/diagnostic_info.cpp new file mode 100644 index 00000000000..9a6710f8a10 --- /dev/null +++ b/src/mongo/util/diagnostic_info.cpp @@ -0,0 +1,222 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault + +#include "mongo/platform/basic.h" + +#include "mongo/util/diagnostic_info.h" + +#include <forward_list> + +#include <fmt/format.h> +#include <fmt/ostream.h> + +#include "mongo/base/init.h" +#include "mongo/db/client.h" +#include "mongo/platform/mutex.h" +#include "mongo/util/clock_source.h" +#include "mongo/util/fail_point_service.h" +#include "mongo/util/interruptible.h" +#include "mongo/util/log.h" + +using namespace fmt::literals; + +namespace mongo { + +namespace { +MONGO_FAIL_POINT_DEFINE(currentOpSpawnsThreadWaitingForLatch); + +constexpr auto kBlockedOpMutexName = "BlockedOpForTestLatch"_sd; +constexpr auto kBlockedOpInterruptibleName = "BlockedOpForTestInterruptible"_sd; + +class BlockedOp { +public: + void start(ServiceContext* serviceContext); + void join(); + void setIsContended(bool value); + void setIsWaiting(bool value); + +private: + stdx::condition_variable _cv; + stdx::mutex _m; // NOLINT + + struct LatchState { + bool isContended = false; + boost::optional<stdx::thread> thread{boost::none}; + + Mutex mutex = MONGO_MAKE_LATCH(kBlockedOpMutexName); + }; + LatchState _latchState; +} gBlockedOp; + +// This function causes us to make an additional thread with a self-contended lock so that +// $currentOp can observe its DiagnosticInfo. Note that we track each thread that called us so that +// we can join the thread when they are gone. +void BlockedOp::start(ServiceContext* serviceContext) { + stdx::unique_lock<stdx::mutex> lk(_m); + + invariant(!_latchState.thread); + + _latchState.mutex.lock(); + _latchState.thread = stdx::thread([this, serviceContext]() mutable { + ThreadClient tc("DiagnosticCaptureTestLatch", serviceContext); + + log() << "Entered currentOpSpawnsThreadWaitingForLatch thread"; + + stdx::lock_guard testLock(_latchState.mutex); + + log() << "Joining currentOpSpawnsThreadWaitingForLatch thread"; + }); + + _cv.wait(lk, [this] { return _latchState.isContended; }); + log() << "Started threads for currentOpSpawnsThreadWaitingForLatch"; +} + +// This function unlocks testMutex and joins if there are no more callers of BlockedOp::start() +// remaining +void BlockedOp::join() { + decltype(_latchState.thread) latchThread; + { + stdx::lock_guard<stdx::mutex> lk(_m); + + invariant(_latchState.thread); + + _latchState.mutex.unlock(); + _latchState.isContended = false; + + std::swap(_latchState.thread, latchThread); + } + + latchThread->join(); +} + +void BlockedOp::setIsContended(bool value) { + log() << "Setting isContended to " << (value ? "true" : "false"); + stdx::lock_guard lk(_m); + _latchState.isContended = value; + _cv.notify_one(); +} + +struct DiagnosticInfoHandle { + stdx::mutex mutex; // NOLINT + std::forward_list<DiagnosticInfo> list; +}; +const auto getDiagnosticInfoHandle = Client::declareDecoration<DiagnosticInfoHandle>(); + +MONGO_INITIALIZER(LockListener)(InitializerContext* context) { + class LockListener : public Mutex::LockListener { + void onContendedLock(const StringData& name) override { + if (auto client = Client::getCurrent()) { + auto& handle = getDiagnosticInfoHandle(client); + stdx::lock_guard<stdx::mutex> lk(handle.mutex); + handle.list.emplace_front(DiagnosticInfo::capture(name)); + + if (currentOpSpawnsThreadWaitingForLatch.shouldFail() && + (name == kBlockedOpMutexName)) { + gBlockedOp.setIsContended(true); + } + } + } + + void onQuickLock(const StringData&) override { + // Do nothing + } + + void onSlowLock(const StringData& name) override { + if (auto client = Client::getCurrent()) { + auto& handle = getDiagnosticInfoHandle(client); + stdx::lock_guard<stdx::mutex> lk(handle.mutex); + + invariant(!handle.list.empty()); + handle.list.pop_front(); + } + } + + void onUnlock(const StringData&) override { + // Do nothing + } + }; + + // Intentionally leaked, people use Latches in detached threads + static auto& listener = *new LockListener; + Mutex::addLockListener(&listener); + + return Status::OK(); +} + +} // namespace + +bool operator==(const DiagnosticInfo& info1, const DiagnosticInfo& info2) { + return info1._captureName == info2._captureName && info1._timestamp == info2._timestamp && + info1._backtrace.data == info2._backtrace.data; +} + +std::string DiagnosticInfo::toString() const { + return "{{ \"name\": \"{}\", \"time\": \"{}\", \"backtraceSize\": {} }}"_format( + _captureName.toString(), _timestamp.toString(), _backtrace.data.size()); +} + +DiagnosticInfo DiagnosticInfo::capture(const StringData& captureName, Options options) { + // Since we don't have a fast enough backtrace implementation at the moment, the Backtrace is + // always empty. If SERVER-44091 happens, this should branch on options.shouldTakeBacktrace + auto backtrace = Backtrace{}; + auto currentTime = getGlobalServiceContext()->getFastClockSource()->now(); + + return DiagnosticInfo(currentTime, captureName, std::move(backtrace)); +} + +DiagnosticInfo::BlockedOpGuard::~BlockedOpGuard() { + gBlockedOp.join(); +} + +auto DiagnosticInfo::maybeMakeBlockedOpForTest(Client* client) -> std::unique_ptr<BlockedOpGuard> { + std::unique_ptr<BlockedOpGuard> guard; + MONGO_FAIL_POINT_BLOCK_IF(currentOpSpawnsThreadWaitingForLatch, data, [&](const BSONObj& data) { + return data.hasField("clientName") && (data.getStringField("clientName") == client->desc()); + }) { + gBlockedOp.start(client->getServiceContext()); + guard = std::make_unique<BlockedOpGuard>(); + } + + return guard; +} + +boost::optional<DiagnosticInfo> DiagnosticInfo::get(Client& client) { + auto& handle = getDiagnosticInfoHandle(client); + stdx::lock_guard<stdx::mutex> lk(handle.mutex); + + if (handle.list.empty()) { + return boost::none; + } + + return handle.list.front(); +} + +} // namespace mongo diff --git a/src/mongo/util/diagnostic_info.h b/src/mongo/util/diagnostic_info.h new file mode 100644 index 00000000000..5bc30202562 --- /dev/null +++ b/src/mongo/util/diagnostic_info.h @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/base/string_data.h" +#include "mongo/db/client.h" +#include "mongo/platform/mutex.h" +#include "mongo/stdx/condition_variable.h" +#include "mongo/util/time_support.h" + +namespace mongo { +/** + * DiagnosticInfo keeps track of diagnostic information such as a developer provided + * name, the time when a lock was first acquired, and a partial caller call stack. + */ +class DiagnosticInfo { +public: + // Maximum number of stack frames to appear in a DiagnosticInfo::Backtrace. + static constexpr size_t kMaxBackTraceFrames = 100ull; + struct Backtrace { + std::vector<void*> data = std::vector<void*>(kMaxBackTraceFrames, nullptr); + }; + + /** + * A simple RAII guard to attempt to join a blocked op once it is no longer needed + * + * This type is used in tests in conjunction with maybeMakeBlockedOpForTest + */ + class BlockedOpGuard { + public: + ~BlockedOpGuard(); + }; + + static boost::optional<DiagnosticInfo> get(Client& client); + + virtual ~DiagnosticInfo() = default; + + Date_t getTimestamp() const { + return _timestamp; + } + + StringData getCaptureName() const { + return _captureName; + } + + std::string toString() const; + + /** + * Simple options struct to go with takeDiagnosticInfo + */ + struct Options { + Options() : shouldTakeBacktrace{false} {} + + bool shouldTakeBacktrace; + }; + + /** + * Captures the diagnostic information based on the caller's context. + */ + static DiagnosticInfo capture(const StringData& captureName, Options options = Options{}); + + /** + * This function checks the FailPoint currentOpSpawnsThreadWaitingForLatch and potentially + * launches a blocked operation to populate waitingForLatch for $currentOp. + */ + static std::unique_ptr<BlockedOpGuard> maybeMakeBlockedOpForTest(Client* client); + + friend std::ostream& operator<<(std::ostream& s, const DiagnosticInfo& info) { + return s << info.toString(); + } + +private: + friend bool operator==(const DiagnosticInfo& info1, const DiagnosticInfo& info2); + friend bool operator!=(const DiagnosticInfo& info1, const DiagnosticInfo& info2) { + return !(info1 == info2); + } + friend std::ostream& operator<<(std::ostream& s, const DiagnosticInfo& info); + + Date_t _timestamp; + StringData _captureName; + Backtrace _backtrace; + + DiagnosticInfo(const Date_t& timestamp, const StringData& captureName, Backtrace backtrace) + : _timestamp(timestamp), _captureName(captureName), _backtrace(std::move(backtrace)) {} +}; + + +} // namespace mongo diff --git a/src/mongo/util/diagnostic_info_test.cpp b/src/mongo/util/diagnostic_info_test.cpp new file mode 100644 index 00000000000..3c054d685dc --- /dev/null +++ b/src/mongo/util/diagnostic_info_test.cpp @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault + +#include "mongo/util/diagnostic_info.h" + +#include <string> + +#include "mongo/platform/atomic_word.h" +#include "mongo/platform/compiler.h" +#include "mongo/unittest/unittest.h" +#include "mongo/util/clock_source_mock.h" +#include "mongo/util/log.h" + +namespace mongo { +TEST(DiagnosticInfo, BasicSingleThread) { + // set up serviceContext and clock source + auto serviceContext = ServiceContext::make(); + auto clockSource = std::make_unique<ClockSourceMock>(); + auto clockSourcePointer = clockSource.get(); + serviceContext->setFastClockSource(std::move(clockSource)); + setGlobalServiceContext(std::move(serviceContext)); + + // take the initial diagnostic info + DiagnosticInfo capture1 = DiagnosticInfo::capture("capture1"_sd); + ASSERT_EQ(capture1.getCaptureName(), "capture1"); + + // mock time advancing and check that the current time is greater than capture1's timestamp + clockSourcePointer->advance(Seconds(1)); + ASSERT_LT(capture1.getTimestamp(), clockSourcePointer->now()); + + // take a second diagnostic capture and compare its fields to the first + DiagnosticInfo capture2 = DiagnosticInfo::capture("capture2"_sd); + ASSERT_LT(capture1.getTimestamp(), capture2.getTimestamp()); + ASSERT_EQ(capture2.getCaptureName(), "capture2"); + ASSERT_NE(capture2, capture1); + + clockSourcePointer->advance(Seconds(1)); + ASSERT_LT(capture2.getTimestamp(), clockSourcePointer->now()); +} +} // namespace mongo diff --git a/src/mongo/util/future_test_utils.h b/src/mongo/util/future_test_utils.h index d4189f28efc..98ce1984660 100644 --- a/src/mongo/util/future_test_utils.h +++ b/src/mongo/util/future_test_utils.h @@ -79,9 +79,7 @@ public: class DummyInterruptable final : public Interruptible { StatusWith<stdx::cv_status> waitForConditionOrInterruptNoAssertUntil( - stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, - Date_t deadline) noexcept override { + stdx::condition_variable& cv, BasicLockableAdapter m, Date_t deadline) noexcept override { return Status(ErrorCodes::Interrupted, ""); } Date_t getDeadline() const override { diff --git a/src/mongo/util/interruptible.h b/src/mongo/util/interruptible.h index 61d19b271d1..446e61849cc 100644 --- a/src/mongo/util/interruptible.h +++ b/src/mongo/util/interruptible.h @@ -31,6 +31,7 @@ #include "mongo/stdx/condition_variable.h" #include "mongo/stdx/mutex.h" +#include "mongo/util/lockable_adapter.h" #include "mongo/util/time_support.h" #include "mongo/util/waitable.h" @@ -201,8 +202,8 @@ public: * deadline on this operation to expire. In the event of interruption or operation deadline * expiration, raises a AssertionException with an error code indicating the interruption type. */ - void waitForConditionOrInterrupt(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m) { + template <typename LockT> + void waitForConditionOrInterrupt(stdx::condition_variable& cv, LockT& m) { uassertStatusOK(waitForConditionOrInterruptNoAssert(cv, m)); } @@ -211,10 +212,8 @@ public: * is interrupted or its deadline expires. Throws a DBException for interruption and * deadline expiration. */ - template <typename Pred> - void waitForConditionOrInterrupt(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, - Pred pred) { + template <typename LockT, typename PredicateT> + void waitForConditionOrInterrupt(stdx::condition_variable& cv, LockT& m, PredicateT pred) { while (!pred()) { waitForConditionOrInterrupt(cv, m); } @@ -224,8 +223,8 @@ public: * Same as waitForConditionOrInterrupt, except returns a Status instead of throwing * a DBException to report interruption. */ - Status waitForConditionOrInterruptNoAssert(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m) noexcept { + template <typename LockT> + Status waitForConditionOrInterruptNoAssert(stdx::condition_variable& cv, LockT& m) noexcept { auto status = waitForConditionOrInterruptNoAssertUntil(cv, m, Date_t::max()); if (!status.isOK()) { return status.getStatus(); @@ -239,10 +238,10 @@ public: * Same as the predicate form of waitForConditionOrInterrupt, except that it returns a not okay * status instead of throwing on interruption. */ - template <typename Pred> + template <typename LockT, typename PredicateT> Status waitForConditionOrInterruptNoAssert(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, - Pred pred) noexcept { + LockT& m, + PredicateT pred) noexcept { while (!pred()) { auto status = waitForConditionOrInterruptNoAssert(cv, m); @@ -262,8 +261,9 @@ public: * the given "deadline" expires, returns cv_status::timeout. Otherwise, returns * cv_status::no_timeout. */ + template <typename LockT> stdx::cv_status waitForConditionOrInterruptUntil(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, + LockT& m, Date_t deadline) { return uassertStatusOK(waitForConditionOrInterruptNoAssertUntil(cv, m, deadline)); } @@ -277,11 +277,11 @@ public: * the given "deadline" expires, returns cv_status::timeout. Otherwise, returns * cv_status::no_timeout indicating that "pred" finally returned true. */ - template <typename Pred> + template <typename LockT, typename PredicateT> bool waitForConditionOrInterruptUntil(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, + LockT& m, Date_t deadline, - Pred pred) { + PredicateT pred) { while (!pred()) { if (stdx::cv_status::timeout == waitForConditionOrInterruptUntil(cv, m, deadline)) { return pred(); @@ -294,8 +294,9 @@ public: * Same as the non-predicate form of waitForConditionOrInterruptUntil, but takes a relative * amount of time to wait instead of an absolute time point. */ + template <typename LockT> stdx::cv_status waitForConditionOrInterruptFor(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, + LockT& m, Milliseconds ms) { return uassertStatusOK( waitForConditionOrInterruptNoAssertUntil(cv, m, getExpirationDateForWaitForValue(ms))); @@ -305,11 +306,11 @@ public: * Same as the predicate form of waitForConditionOrInterruptUntil, but takes a relative * amount of time to wait instead of an absolute time point. */ - template <typename Pred> + template <typename LockT, typename PredicateT> bool waitForConditionOrInterruptFor(stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, + LockT& m, Milliseconds ms, - Pred pred) { + PredicateT pred) { const auto deadline = getExpirationDateForWaitForValue(ms); while (!pred()) { if (stdx::cv_status::timeout == waitForConditionOrInterruptUntil(cv, m, deadline)) { @@ -324,9 +325,7 @@ public: * non-ok status indicates the error instead of a DBException. */ virtual StatusWith<stdx::cv_status> waitForConditionOrInterruptNoAssertUntil( - stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, - Date_t deadline) noexcept = 0; + stdx::condition_variable& cv, BasicLockableAdapter m, Date_t deadline) noexcept = 0; /** * Sleeps until "deadline"; throws an exception if the interruptible is interrupted before then. @@ -399,9 +398,7 @@ protected: */ class Interruptible::NotInterruptible final : public Interruptible { StatusWith<stdx::cv_status> waitForConditionOrInterruptNoAssertUntil( - stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& m, - Date_t deadline) noexcept override { + stdx::condition_variable& cv, BasicLockableAdapter m, Date_t deadline) noexcept override { if (deadline == Date_t::max()) { cv.wait(m); diff --git a/src/mongo/util/lockable_adapter.h b/src/mongo/util/lockable_adapter.h new file mode 100644 index 00000000000..83999b34c3f --- /dev/null +++ b/src/mongo/util/lockable_adapter.h @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +namespace mongo { + +// BasicLockableAdapter allows non-template functions to take any lockable type. This can be useful +// when you have a custom lockable type and don't want to make the lockable parameter concrete for a +// function but can't make the function use a template parameter for the lock type. +// +// This type should NOT be used to store a lockable type! +// +// Example: +// void wait(BasicLockableAdapter lock) { +// stdx::lock_guard lg(lock); +// } +// +// mongo::ResourceMutex mut; +// wait(mut); +class BasicLockableAdapter { +public: + template <typename T> + BasicLockableAdapter(T& lock) : _underlyingLock(&lock), _vtable(&forT<std::decay_t<T>>) {} + + void lock() { + _vtable->lock(_underlyingLock); + } + + void unlock() { + _vtable->unlock(_underlyingLock); + } + +private: + struct VTable { + void (*lock)(void*); + void (*unlock)(void*); + }; + + template <typename T> + static inline VTable forT = VTable{+[](void* t) { static_cast<T*>(t)->lock(); }, + +[](void* t) { static_cast<T*>(t)->unlock(); }}; + + void* _underlyingLock; + const VTable* _vtable; +}; + +} // namespace mongo diff --git a/src/mongo/util/lockable_adapter_test.cpp b/src/mongo/util/lockable_adapter_test.cpp new file mode 100644 index 00000000000..f0431d19c0e --- /dev/null +++ b/src/mongo/util/lockable_adapter_test.cpp @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/unittest/unittest.h" + +#include "mongo/stdx/condition_variable.h" +#include "mongo/stdx/future.h" +#include "mongo/stdx/mutex.h" +#include "mongo/util/lockable_adapter.h" + +namespace mongo { + +namespace { + +template <typename Pred> +void waitForCondition(stdx::condition_variable_any& cv, BasicLockableAdapter lock, Pred pred) { + cv.wait(lock, pred); +} + +void callUnderLock(BasicLockableAdapter adaptedLock) { + stdx::lock_guard lock(adaptedLock); + ASSERT_TRUE(true); // got here +} + +class TestLockable { +public: + TestLockable() {} + + void lock() { + _mutex.lock(); + ++lockCalls; + } + + void unlock() { + ++unlockCalls; + _mutex.unlock(); + } + + int lockCalls{0}; + int unlockCalls{0}; + +private: + stdx::mutex _mutex; // NOLINT +}; + +} // namespace + +TEST(BasicLockableAdapter, TestWithConditionVariable) { + bool ready = false; + stdx::condition_variable_any cv; + stdx::mutex mut; // NOLINT + + auto result = stdx::async(stdx::launch::async, [&ready, &mut, &cv] { + stdx::lock_guard lock(mut); + ASSERT_FALSE(ready); + ready = true; + cv.notify_all(); + }); + + stdx::unique_lock lock(mut); + waitForCondition(cv, lock, [&ready] { return ready; }); + ASSERT_TRUE(ready); +} + +TEST(BasicLockableAdapter, TestWithMutexTypes) { + + { + stdx::mutex mut; // NOLINT + callUnderLock(mut); + } + + { + stdx::timed_mutex mut; + callUnderLock(mut); + } + + { + TestLockable mut; + callUnderLock(mut); + ASSERT_EQ(mut.lockCalls, 1); + ASSERT_EQ(mut.unlockCalls, 1); + } +} + +TEST(BasicLockableAdapter, TestWithCustomLockableType) { + bool ready = false; + stdx::condition_variable_any cv; + TestLockable mut; + + auto result = stdx::async(stdx::launch::async, [&ready, &mut, &cv] { + stdx::lock_guard lock(mut); + ASSERT_FALSE(ready); + ready = true; + cv.notify_all(); + }); + + { + stdx::unique_lock lock(mut); + waitForCondition(cv, lock, [&ready] { return ready; }); + } + + ASSERT_TRUE(ready); + ASSERT_GT(mut.lockCalls, 0); + ASSERT_EQ(mut.lockCalls, mut.unlockCalls); +} + +} // namespace mongo diff --git a/src/mongo/util/waitable.h b/src/mongo/util/waitable.h index f2985b5e963..b831d422b23 100644 --- a/src/mongo/util/waitable.h +++ b/src/mongo/util/waitable.h @@ -46,13 +46,17 @@ namespace mongo { * * The current implementer of Waitable is the transport layer baton type, which performs delayed IO * when it would otherwise block. + * + * Note that every Waitable should be level-triggered like its base class, Notifyable. See + * mongo/stdx/condition_variable.h for more details. */ class Waitable : public Notifyable { public: + template <typename LockT> static void wait(Waitable* waitable, ClockSource* clkSource, stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& lk) { + LockT& lk) { if (waitable) { cv._runWithNotifyable(*waitable, [&]() noexcept { lk.unlock(); @@ -64,22 +68,23 @@ public: } } - template <typename Predicate> + template <typename LockT, typename PredicateT> static void wait(Waitable* waitable, ClockSource* clkSource, stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& lk, - Predicate pred) { + LockT& lk, + PredicateT pred) { while (!pred()) { wait(waitable, clkSource, cv, lk); } } + template <typename LockT> static stdx::cv_status wait_until( Waitable* waitable, ClockSource* clkSource, stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& lk, + LockT& lk, const stdx::chrono::time_point<stdx::chrono::system_clock>& timeout_time) { if (waitable) { auto rval = stdx::cv_status::no_timeout; @@ -98,13 +103,13 @@ public: } } - template <typename Predicate> + template <typename LockT, typename PredicateT> static bool wait_until(Waitable* waitable, ClockSource* clkSource, stdx::condition_variable& cv, - stdx::unique_lock<stdx::mutex>& lk, + LockT& lk, const stdx::chrono::time_point<stdx::chrono::system_clock>& timeout_time, - Predicate pred) { + PredicateT pred) { while (!pred()) { if (wait_until(waitable, clkSource, cv, lk, timeout_time) == stdx::cv_status::timeout) { return pred(); |