From 0d14b52b6933179cd0287441e664572c45b120ab Mon Sep 17 00:00:00 2001 From: jsteemann Date: Mon, 2 Jan 2017 09:16:23 +0100 Subject: [PATCH 1/4] fixed issue #2234 --- CHANGELOG | 16 +++++++++ arangod/Aql/AstNode.cpp | 4 +++ arangod/Aql/AstNode.h | 9 ++++- arangod/Aql/Condition.cpp | 66 ++++++++++++++++++---------------- arangod/Aql/OptimizerRules.cpp | 26 ++++++++------ 5 files changed, 78 insertions(+), 43 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0ff0cedfa3..787f300d81 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -49,6 +49,22 @@ edge attribute `label`. * process.stdout.isTTY now returns `true` in arangosh and when running arangod with the `--console` flag +v3.1.8 (XXXX-XX-XX) +------------------- + +* fixed issue #2238 + +* fixed issue #2234 + + +v3.1.7 (2016-12-29) +------------------- + +* fixed one too many elections in RAFT + +* new agency comm backported from devel + + v3.1.6 (2016-12-20) ------------------- diff --git a/arangod/Aql/AstNode.cpp b/arangod/Aql/AstNode.cpp index 5d15e8fe96..4b81c5f428 100644 --- a/arangod/Aql/AstNode.cpp +++ b/arangod/Aql/AstNode.cpp @@ -1925,6 +1925,8 @@ AstNode* AstNode::clone(Ast* ast) const { return ast->clone(this); } /// the string representation does not need to be JavaScript-compatible /// except for node types NODE_TYPE_VALUE, NODE_TYPE_ARRAY and NODE_TYPE_OBJECT /// (only for objects that do not contain dynamic attributes) +/// note that this may throw and that the caller is responsible for +/// catching the error void AstNode::stringify(arangodb::basics::StringBuffer* buffer, bool verbose, bool failIfLong) const { // any arrays/objects with more values than this will not be stringified if @@ -2199,6 +2201,8 @@ void AstNode::stringify(arangodb::basics::StringBuffer* buffer, bool verbose, THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, message); } +/// note that this may throw and that the caller is responsible for +/// catching the error std::string AstNode::toString() const { arangodb::basics::StringBuffer buffer(TRI_UNKNOWN_MEM_ZONE); stringify(&buffer, false, false); diff --git a/arangod/Aql/AstNode.h b/arangod/Aql/AstNode.h index e113767a40..53bd1eee2c 100644 --- a/arangod/Aql/AstNode.h +++ b/arangod/Aql/AstNode.h @@ -639,9 +639,16 @@ struct AstNode { /// @brief clone a node, recursively AstNode* clone(Ast*) const; - /// @brief append a JavaScript representation of the node into a string buffer + /// @brief append a string representation of the node into a string buffer + /// the string representation does not need to be JavaScript-compatible + /// except for node types NODE_TYPE_VALUE, NODE_TYPE_ARRAY and NODE_TYPE_OBJECT + /// (only for objects that do not contain dynamic attributes) + /// note that this may throw and that the caller is responsible for + /// catching the error void stringify(arangodb::basics::StringBuffer*, bool, bool) const; + /// note that this may throw and that the caller is responsible for + /// catching the error std::string toString() const; /// @brief stringify the value of a node into a string buffer diff --git a/arangod/Aql/Condition.cpp b/arangod/Aql/Condition.cpp index 7af12546d6..09725f77c1 100644 --- a/arangod/Aql/Condition.cpp +++ b/arangod/Aql/Condition.cpp @@ -991,54 +991,58 @@ bool Condition::canRemove(ExecutionPlan const* plan, ConditionPart const& me, return node->toString(); }; - for (size_t i = 0; i < n; ++i) { - auto operand = andNode->getMemberUnchecked(i); + try { + for (size_t i = 0; i < n; ++i) { + auto operand = andNode->getMemberUnchecked(i); - if (operand->isComparisonOperator()) { - auto lhs = operand->getMember(0); - auto rhs = operand->getMember(1); + if (operand->isComparisonOperator()) { + auto lhs = operand->getMember(0); + auto rhs = operand->getMember(1); - if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) { - clearAttributeAccess(result); + if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) { + clearAttributeAccess(result); - if (lhs->isAttributeAccessForVariable(result)) { - if (rhs->isConstant()) { - ConditionPart indexCondition(result.first, result.second, operand, - ATTRIBUTE_LEFT, nullptr); + if (lhs->isAttributeAccessForVariable(result)) { + if (rhs->isConstant()) { + ConditionPart indexCondition(result.first, result.second, operand, + ATTRIBUTE_LEFT, nullptr); - if (me.isCoveredBy(indexCondition, false)) { + if (me.isCoveredBy(indexCondition, false)) { + return true; + } + } + // non-constant condition + else if (me.operatorType == operand->type && + normalize(me.valueNode) == normalize(rhs)) { return true; } } - // non-constant condition - else if (me.operatorType == operand->type && - normalize(me.valueNode) == normalize(rhs)) { - return true; - } } - } - if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS || - rhs->type == NODE_TYPE_EXPANSION) { - clearAttributeAccess(result); + if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS || + rhs->type == NODE_TYPE_EXPANSION) { + clearAttributeAccess(result); - if (rhs->isAttributeAccessForVariable(result)) { - if (lhs->isConstant()) { - ConditionPart indexCondition(result.first, result.second, operand, - ATTRIBUTE_RIGHT, nullptr); + if (rhs->isAttributeAccessForVariable(result)) { + if (lhs->isConstant()) { + ConditionPart indexCondition(result.first, result.second, operand, + ATTRIBUTE_RIGHT, nullptr); - if (me.isCoveredBy(indexCondition, true)) { + if (me.isCoveredBy(indexCondition, true)) { + return true; + } + } + // non-constant condition + else if (me.operatorType == operand->type && + normalize(me.valueNode) == normalize(lhs)) { return true; } } - // non-constant condition - else if (me.operatorType == operand->type && - normalize(me.valueNode) == normalize(lhs)) { - return true; - } } } } + } catch (...) { + // simply ignore any errors and return false } return false; diff --git a/arangod/Aql/OptimizerRules.cpp b/arangod/Aql/OptimizerRules.cpp index 7e15163265..ab23a9ee77 100644 --- a/arangod/Aql/OptimizerRules.cpp +++ b/arangod/Aql/OptimizerRules.cpp @@ -3190,7 +3190,7 @@ struct OrSimplifier { } catch (...) { } - return ""; + return std::string(); } bool qualifies(AstNode const* node, std::string& attributeName) const { @@ -3399,6 +3399,18 @@ struct RemoveRedundantOr { CommonNodeFinder finder; AstNode const* commonNode = nullptr; std::string commonName; + + bool hasRedundantCondition(AstNode const* node) { + try { + if (finder.find(node, NODE_TYPE_OPERATOR_BINARY_LT, commonNode, + commonName)) { + return hasRedundantConditionWalker(node); + } + } catch (...) { + // ignore errors and simply return false + } + return false; + } AstNode* createReplacementNode(Ast* ast) { TRI_ASSERT(commonNode != nullptr); @@ -3408,6 +3420,7 @@ struct RemoveRedundantOr { bestValue); } + private: bool isInclusiveBound(AstNodeType type) { return (type == NODE_TYPE_OPERATOR_BINARY_GE || type == NODE_TYPE_OPERATOR_BINARY_LE); @@ -3429,8 +3442,7 @@ struct RemoveRedundantOr { } // returns false if the existing value is better and true if the input value - // is - // better + // is better bool compareBounds(AstNodeType type, AstNode const* value, int lowhigh) { int cmp = CompareAstNodes(bestValue, value, true); @@ -3440,14 +3452,6 @@ struct RemoveRedundantOr { return (cmp * lowhigh == 1); } - bool hasRedundantCondition(AstNode const* node) { - if (finder.find(node, NODE_TYPE_OPERATOR_BINARY_LT, commonNode, - commonName)) { - return hasRedundantConditionWalker(node); - } - return false; - } - bool hasRedundantConditionWalker(AstNode const* node) { AstNodeType type = node->type; From 9dce58e418f82d2dfdfafe0cd1e132ff80c44cc3 Mon Sep 17 00:00:00 2001 From: jsteemann Date: Mon, 2 Jan 2017 12:29:28 +0100 Subject: [PATCH 2/4] slightly speed up conditions optimizer --- arangod/Aql/Condition.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/arangod/Aql/Condition.cpp b/arangod/Aql/Condition.cpp index 09725f77c1..04ff4ea019 100644 --- a/arangod/Aql/Condition.cpp +++ b/arangod/Aql/Condition.cpp @@ -1129,6 +1129,7 @@ AstNode* Condition::transformNode(AstNode* node) { // create a new n-ary node node = _ast->createNode(Ast::NaryOperatorType(old->type)); + node->reserve(2); node->addMember(old->getMember(0)); node->addMember(old->getMember(1)); } @@ -1146,11 +1147,9 @@ AstNode* Condition::transformNode(AstNode* node) { auto sub = transformNode(node->getMemberUnchecked(i)); node->changeMember(i, sub); - if (sub->type == NODE_TYPE_OPERATOR_NARY_OR || - sub->type == NODE_TYPE_OPERATOR_BINARY_OR) { + if (sub->type == NODE_TYPE_OPERATOR_NARY_OR) { processChildren = true; - } else if (sub->type == NODE_TYPE_OPERATOR_NARY_AND || - sub->type == NODE_TYPE_OPERATOR_BINARY_AND) { + } else if (sub->type == NODE_TYPE_OPERATOR_NARY_AND) { mustCollapse = true; } } @@ -1169,14 +1168,15 @@ AstNode* Condition::transformNode(AstNode* node) { auto newOperator = _ast->createNode(NODE_TYPE_OPERATOR_NARY_OR); std::vector permutationStates; + permutationStates.reserve(n); + for (size_t i = 0; i < n; ++i) { auto sub = node->getMemberUnchecked(i); if (sub->type == NODE_TYPE_OPERATOR_NARY_OR) { - permutationStates.emplace_back( - PermutationState(sub, sub->numMembers())); + permutationStates.emplace_back(sub, sub->numMembers()); } else { - permutationStates.emplace_back(PermutationState(sub, 1)); + permutationStates.emplace_back(sub, 1); } } @@ -1186,9 +1186,10 @@ AstNode* Condition::transformNode(AstNode* node) { while (!done) { auto andOperator = _ast->createNode(NODE_TYPE_OPERATOR_NARY_AND); + andOperator->reserve(numPermutations); for (size_t i = 0; i < numPermutations; ++i) { - auto state = permutationStates[i]; + auto const& state = permutationStates[i]; andOperator->addMember(state.getValue()->clone(_ast)); } From 62aeddee6a6156f74d0fdc4d1c85bba177cd4252 Mon Sep 17 00:00:00 2001 From: jsteemann Date: Mon, 2 Jan 2017 13:39:27 +0100 Subject: [PATCH 3/4] speed up memory management --- arangod/Aql/QueryResources.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/arangod/Aql/QueryResources.cpp b/arangod/Aql/QueryResources.cpp index 90cb0cdb9a..c7551b54db 100644 --- a/arangod/Aql/QueryResources.cpp +++ b/arangod/Aql/QueryResources.cpp @@ -62,19 +62,25 @@ void QueryResources::addNode(AstNode* node) { if (_nodes.empty()) { // reserve some initial space for nodes - capacity = 16; + capacity = 64; } else { - capacity = (std::max)(_nodes.size() + 8, _nodes.capacity()); + capacity = _nodes.size() + 1; + if (capacity > _nodes.capacity()) { + capacity *= 2; + } } TRI_ASSERT(capacity > _nodes.size()); - TRI_ASSERT(capacity >= _nodes.capacity()); - // reserve space - _resourceMonitor->increaseMemoryUsage((capacity - _nodes.capacity()) * sizeof(AstNode*)); - _nodes.reserve(capacity); + // reserve space for pointers + if (capacity > _nodes.capacity()) { + _resourceMonitor->increaseMemoryUsage((capacity - _nodes.capacity()) * sizeof(AstNode*)); + _nodes.reserve(capacity); + } + // may throw _resourceMonitor->increaseMemoryUsage(sizeof(AstNode)); + // will not fail _nodes.emplace_back(node); } From 3c9d71fdcf535e8de3eb619da2cd74e697e495f7 Mon Sep 17 00:00:00 2001 From: jsteemann Date: Mon, 2 Jan 2017 15:36:43 +0100 Subject: [PATCH 4/4] performance fix for locks --- .../StorageEngine/MMFilesCompactorThread.cpp | 2 +- arangod/Utils/CollectionReadLocker.h | 3 +- arangod/Utils/CollectionWriteLocker.h | 2 +- arangod/VocBase/LogicalCollection.cpp | 59 ++++++++++--------- arangod/VocBase/LogicalCollection.h | 4 +- arangod/VocBase/transaction.cpp | 18 +++--- arangod/VocBase/transaction.h | 10 +--- 7 files changed, 46 insertions(+), 52 deletions(-) diff --git a/arangod/StorageEngine/MMFilesCompactorThread.cpp b/arangod/StorageEngine/MMFilesCompactorThread.cpp index b4ce57f146..2c8dda5171 100644 --- a/arangod/StorageEngine/MMFilesCompactorThread.cpp +++ b/arangod/StorageEngine/MMFilesCompactorThread.cpp @@ -307,7 +307,7 @@ MMFilesCompactorThread::CompactionInitialContext MMFilesCompactorThread::getComp bool ok; { bool const useDeadlockDetector = false; - int res = collection->beginReadTimed(useDeadlockDetector, 86400ULL * 1000ULL * 1000ULL, TRI_TRANSACTION_DEFAULT_SLEEP_DURATION); + int res = collection->beginReadTimed(useDeadlockDetector, 86400.0); if (res != TRI_ERROR_NO_ERROR) { ok = false; diff --git a/arangod/Utils/CollectionReadLocker.h b/arangod/Utils/CollectionReadLocker.h index 9f7742534e..44c2715bbb 100644 --- a/arangod/Utils/CollectionReadLocker.h +++ b/arangod/Utils/CollectionReadLocker.h @@ -40,8 +40,7 @@ class CollectionReadLocker { CollectionReadLocker(LogicalCollection* collection, bool useDeadlockDetector, bool doLock) : _collection(collection), _useDeadlockDetector(useDeadlockDetector), _doLock(false) { if (doLock) { - int res = _collection->beginReadTimed(_useDeadlockDetector, - 0, TRI_TRANSACTION_DEFAULT_SLEEP_DURATION); + int res = _collection->beginReadTimed(_useDeadlockDetector); if (res != TRI_ERROR_NO_ERROR) { THROW_ARANGO_EXCEPTION(res); diff --git a/arangod/Utils/CollectionWriteLocker.h b/arangod/Utils/CollectionWriteLocker.h index 700ad219d2..8bfd40fb1a 100644 --- a/arangod/Utils/CollectionWriteLocker.h +++ b/arangod/Utils/CollectionWriteLocker.h @@ -40,7 +40,7 @@ class CollectionWriteLocker { CollectionWriteLocker(arangodb::LogicalCollection* collection, bool useDeadlockDetector, bool doLock) : _collection(collection), _useDeadlockDetector(useDeadlockDetector), _doLock(false) { if (doLock) { - int res = _collection->beginWriteTimed(_useDeadlockDetector, 0, TRI_TRANSACTION_DEFAULT_SLEEP_DURATION); + int res = _collection->beginWriteTimed(_useDeadlockDetector); if (res != TRI_ERROR_NO_ERROR) { THROW_ARANGO_EXCEPTION(res); diff --git a/arangod/VocBase/LogicalCollection.cpp b/arangod/VocBase/LogicalCollection.cpp index 7f04d6c9b4..9789e36407 100644 --- a/arangod/VocBase/LogicalCollection.cpp +++ b/arangod/VocBase/LogicalCollection.cpp @@ -2922,7 +2922,7 @@ int LogicalCollection::endWrite(bool useDeadlockDetector) { } /// @brief read locks a collection, with a timeout (in µseconds) -int LogicalCollection::beginReadTimed(bool useDeadlockDetector, uint64_t timeout, uint64_t sleepPeriod) { +int LogicalCollection::beginReadTimed(bool useDeadlockDetector, double timeout) { if (arangodb::Transaction::_makeNolockHeaders != nullptr) { auto it = arangodb::Transaction::_makeNolockHeaders->find(name()); if (it != arangodb::Transaction::_makeNolockHeaders->end()) { @@ -2933,16 +2933,12 @@ int LogicalCollection::beginReadTimed(bool useDeadlockDetector, uint64_t timeout return TRI_ERROR_NO_ERROR; } } - uint64_t waited = 0; - if (timeout == 0) { - // we don't allow looping forever. limit waiting to 15 minutes max. - timeout = 15 * 60 * 1000 * 1000; - } // LOCKING-DEBUG // std::cout << "BeginReadTimed: " << _name << std::endl; int iterations = 0; bool wasBlocked = false; + double end = 0.0; while (true) { TRY_READ_LOCKER(locker, _idxLock); @@ -2970,6 +2966,7 @@ int LogicalCollection::beginReadTimed(bool useDeadlockDetector, uint64_t timeout return TRI_ERROR_DEADLOCK; } LOG(TRACE) << "waiting for read-lock on collection '" << name() << "'"; + // fall-through intentional } else if (++iterations >= 5) { // periodically check for deadlocks TRI_ASSERT(wasBlocked); @@ -2992,15 +2989,20 @@ int LogicalCollection::beginReadTimed(bool useDeadlockDetector, uint64_t timeout } } -#ifdef _WIN32 - usleep((unsigned long)sleepPeriod); -#else - usleep((useconds_t)sleepPeriod); -#endif + if (end == 0.0) { + // set end time for lock waiting + if (timeout <= 0.0) { + timeout = 15.0 * 60.0; + } + end = TRI_microtime() + timeout; + TRI_ASSERT(end > 0.0); + } - waited += sleepPeriod; + std::this_thread::yield(); + + TRI_ASSERT(end > 0.0); - if (waited > timeout) { + if (TRI_microtime() > end) { if (useDeadlockDetector) { _vocbase->_deadlockDetector.unsetReaderBlocked(this); } @@ -3011,7 +3013,7 @@ int LogicalCollection::beginReadTimed(bool useDeadlockDetector, uint64_t timeout } /// @brief write locks a collection, with a timeout -int LogicalCollection::beginWriteTimed(bool useDeadlockDetector, uint64_t timeout, uint64_t sleepPeriod) { +int LogicalCollection::beginWriteTimed(bool useDeadlockDetector, double timeout) { if (arangodb::Transaction::_makeNolockHeaders != nullptr) { auto it = arangodb::Transaction::_makeNolockHeaders->find(name()); if (it != arangodb::Transaction::_makeNolockHeaders->end()) { @@ -3022,16 +3024,12 @@ int LogicalCollection::beginWriteTimed(bool useDeadlockDetector, uint64_t timeou return TRI_ERROR_NO_ERROR; } } - uint64_t waited = 0; - if (timeout == 0) { - // we don't allow looping forever. limit waiting to 15 minutes max. - timeout = 15 * 60 * 1000 * 1000; - } // LOCKING-DEBUG // std::cout << "BeginWriteTimed: " << document->_info._name << std::endl; int iterations = 0; bool wasBlocked = false; + double end = 0.0; while (true) { TRY_WRITE_LOCKER(locker, _idxLock); @@ -3080,15 +3078,22 @@ int LogicalCollection::beginWriteTimed(bool useDeadlockDetector, uint64_t timeou } } -#ifdef _WIN32 - usleep((unsigned long)sleepPeriod); -#else - usleep((useconds_t)sleepPeriod); -#endif + std::this_thread::yield(); + + if (end == 0.0) { + // set end time for lock waiting + if (timeout <= 0.0) { + timeout = 15.0 * 60.0; + } + end = TRI_microtime() + timeout; + TRI_ASSERT(end > 0.0); + } - waited += sleepPeriod; - - if (waited > timeout) { + std::this_thread::yield(); + + TRI_ASSERT(end > 0.0); + + if (TRI_microtime() > end) { if (useDeadlockDetector) { _vocbase->_deadlockDetector.unsetWriterBlocked(this); } diff --git a/arangod/VocBase/LogicalCollection.h b/arangod/VocBase/LogicalCollection.h index 95a880b735..a223c46214 100644 --- a/arangod/VocBase/LogicalCollection.h +++ b/arangod/VocBase/LogicalCollection.h @@ -380,8 +380,8 @@ class LogicalCollection { int fillIndex(arangodb::Transaction*, arangodb::Index*, bool skipPersistent = true); - int beginReadTimed(bool useDeadlockDetector, uint64_t, uint64_t); - int beginWriteTimed(bool useDeadlockDetector, uint64_t, uint64_t); + int beginReadTimed(bool useDeadlockDetector, double timeout = 0.0); + int beginWriteTimed(bool useDeadlockDetector, double timeout = 0.0); int endRead(bool useDeadlockDetector); int endWrite(bool useDeadlockDetector); diff --git a/arangod/VocBase/transaction.cpp b/arangod/VocBase/transaction.cpp index dcd1184c7f..e2ce7d85e5 100644 --- a/arangod/VocBase/transaction.cpp +++ b/arangod/VocBase/transaction.cpp @@ -286,10 +286,10 @@ static int LockCollection(TRI_transaction_collection_t* trxCollection, LogicalCollection* collection = trxCollection->_collection; TRI_ASSERT(collection != nullptr); - uint64_t timeout = trx->_timeout; + double timeout = trx->_timeout; if (HasHint(trxCollection->_transaction, TRI_TRANSACTION_HINT_TRY_LOCK)) { - // give up if we cannot acquire the lock instantly - timeout = 1 * 100; + // give up early if we cannot acquire the lock instantly + timeout = 0.00000001; } bool const useDeadlockDetector = !IsSingleOperationTransaction(trx); @@ -297,13 +297,11 @@ static int LockCollection(TRI_transaction_collection_t* trxCollection, int res; if (type == TRI_TRANSACTION_READ) { LOG_TRX(trx, nestingLevel) << "read-locking collection " << trxCollection->_cid; - res = collection->beginReadTimed(useDeadlockDetector, timeout, - TRI_TRANSACTION_DEFAULT_SLEEP_DURATION); + res = collection->beginReadTimed(useDeadlockDetector, timeout); } else { LOG_TRX(trx, nestingLevel) << "write-locking collection " << trxCollection->_cid; - res = collection->beginWriteTimed(useDeadlockDetector, timeout, - TRI_TRANSACTION_DEFAULT_SLEEP_DURATION); + res = collection->beginWriteTimed(useDeadlockDetector, timeout); } if (res == TRI_ERROR_NO_ERROR) { @@ -1263,10 +1261,8 @@ TRI_transaction_t::TRI_transaction_t(TRI_vocbase_t* vocbase, double timeout, boo _timeout(TRI_TRANSACTION_DEFAULT_LOCK_TIMEOUT) { if (timeout > 0.0) { - _timeout = (uint64_t)(timeout * 1000000.0); - } else if (timeout == 0.0) { - _timeout = static_cast(0); - } + _timeout = timeout; + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/VocBase/transaction.h b/arangod/VocBase/transaction.h index 6f8cbd39a1..1c101c72c4 100644 --- a/arangod/VocBase/transaction.h +++ b/arangod/VocBase/transaction.h @@ -49,13 +49,7 @@ struct TRI_vocbase_t; /// @brief time (in µs) that is spent waiting for a lock //////////////////////////////////////////////////////////////////////////////// -#define TRI_TRANSACTION_DEFAULT_LOCK_TIMEOUT 30000000ULL - -//////////////////////////////////////////////////////////////////////////////// -/// @brief sleep time (in µs) while waiting for lock acquisition -//////////////////////////////////////////////////////////////////////////////// - -#define TRI_TRANSACTION_DEFAULT_SLEEP_DURATION 10000ULL +#define TRI_TRANSACTION_DEFAULT_LOCK_TIMEOUT 30.0 //////////////////////////////////////////////////////////////////////////////// /// @brief transaction type @@ -128,7 +122,7 @@ struct TRI_transaction_t { bool _hasOperations; bool _waitForSync; // whether or not the collection had a synchronous op bool _beginWritten; // whether or not the begin marker was already written - uint64_t _timeout; // timeout for lock acquisition + double _timeout; // timeout for lock acquisition }; ////////////////////////////////////////////////////////////////////////////////