diff --git a/arangod/Cluster/ClusterTrxMethods.cpp b/arangod/Cluster/ClusterTrxMethods.cpp index 4743e43bb88f..e349bcbe95c9 100644 --- a/arangod/Cluster/ClusterTrxMethods.cpp +++ b/arangod/Cluster/ClusterTrxMethods.cpp @@ -271,7 +271,7 @@ Future commitAbortTransaction(arangodb::TransactionState* state, Result res; for (Try const& tryRes : responses) { network::Response const& resp = tryRes.get(); // throws exceptions upwards - Result res = ::checkTransactionResult(tidPlus, status, resp); + res = ::checkTransactionResult(tidPlus, status, resp); if (res.fail()) { break; } @@ -345,8 +345,7 @@ Future commitAbortTransaction(transaction::Methods& trx, transaction::St } // namespace -namespace arangodb { -namespace ClusterTrxMethods { +namespace arangodb::ClusterTrxMethods { using namespace arangodb::futures; bool IsServerIdLessThan::operator()(ServerID const& lhs, ServerID const& rhs) const noexcept { @@ -575,5 +574,4 @@ bool isElCheapo(TransactionState const& state) { state.hasHint(transaction::Hints::Hint::FROM_TOPLEVEL_AQL)); } -} // namespace ClusterTrxMethods -} // namespace arangodb +} // namespace arangodb::ClusterTrxMethods diff --git a/arangod/Cluster/ClusterTypes.cpp b/arangod/Cluster/ClusterTypes.cpp index aefd09338ba7..d8b1f3bd59dd 100644 --- a/arangod/Cluster/ClusterTypes.cpp +++ b/arangod/Cluster/ClusterTypes.cpp @@ -182,4 +182,10 @@ AnalyzersRevision::Ptr AnalyzersRevision::fromVelocyPack(VPackSlice const& slice buildingRevisionSlice.getNumber(), std::move(coordinatorID), rebootID)); } + +auto isShardName(std::string_view name) -> bool { + return name.size() > 1 && name[0] == 's' && + std::all_of(name.cbegin() + 1, name.cend(), static_cast(std::isdigit)); +} + } // namespace arangodb diff --git a/arangod/Cluster/ServerState.cpp b/arangod/Cluster/ServerState.cpp index f9dbf8594281..2c1b5d41253a 100644 --- a/arangod/Cluster/ServerState.cpp +++ b/arangod/Cluster/ServerState.cpp @@ -1190,3 +1190,11 @@ Result ServerState::propagateClusterReadOnly(bool mode) { setReadOnly(mode ? API_TRUE : API_FALSE); return Result(); } + +#ifdef ARANGODB_USE_GOOGLE_TESTS +bool ServerState::isGoogleTest() const noexcept { return _isGoogleTests; } + +void ServerState::setGoogleTest(bool isGoogleTests) noexcept { + _isGoogleTests = isGoogleTests; +} +#endif diff --git a/arangod/Cluster/ServerState.h b/arangod/Cluster/ServerState.h index 37c5341c334c..4deede69d638 100644 --- a/arangod/Cluster/ServerState.h +++ b/arangod/Cluster/ServerState.h @@ -282,6 +282,13 @@ class ServerState { /// file where the server persists its UUID std::string getUuidFilename() const; +#ifdef ARANGODB_USE_GOOGLE_TESTS + [[nodiscard]] bool isGoogleTest() const noexcept; + void setGoogleTest(bool isGoogleTests) noexcept; +#else + [[nodiscard]] constexpr bool isGoogleTest() const noexcept { return false; } +#endif + private: /// @brief atomically fetches the server role inline RoleEnum loadRole() const noexcept { @@ -374,6 +381,10 @@ class ServerState { TRI_voc_tick_t _foxxmasterSince; bool _foxxmasterQueueupdate; + +#ifdef ARANGODB_USE_GOOGLE_TESTS + bool _isGoogleTests = false; +#endif }; } // namespace arangodb diff --git a/arangod/ClusterEngine/ClusterTransactionState.cpp b/arangod/ClusterEngine/ClusterTransactionState.cpp index 8f35e8390c9a..0212816949e6 100644 --- a/arangod/ClusterEngine/ClusterTransactionState.cpp +++ b/arangod/ClusterEngine/ClusterTransactionState.cpp @@ -155,6 +155,10 @@ uint64_t ClusterTransactionState::numCommits() const { return _status == transaction::Status::COMMITTED ? 1 : 0; } +TRI_voc_tick_t ClusterTransactionState::lastOperationTick() const noexcept { + return 0; +} + std::unique_ptr ClusterTransactionState::createTransactionCollection( DataSourceId cid, AccessMode::Type accessType) { return std::make_unique(this, cid, accessType); diff --git a/arangod/ClusterEngine/ClusterTransactionState.h b/arangod/ClusterEngine/ClusterTransactionState.h index 1052f4556099..0deca731e17c 100644 --- a/arangod/ClusterEngine/ClusterTransactionState.h +++ b/arangod/ClusterEngine/ClusterTransactionState.h @@ -24,29 +24,39 @@ #pragma once #include "StorageEngine/TransactionState.h" +#include "VocBase/Identifiers/TransactionId.h" + +struct TRI_vocbase_t; namespace arangodb { +class Result; + +namespace transaction { +struct Options; +} /// @brief transaction type class ClusterTransactionState final : public TransactionState { public: ClusterTransactionState(TRI_vocbase_t& vocbase, TransactionId tid, transaction::Options const& options); - ~ClusterTransactionState() = default; + ~ClusterTransactionState() override = default; /// @brief begin a transaction - Result beginTransaction(transaction::Hints hints) override; + [[nodiscard]] Result beginTransaction(transaction::Hints hints) override; /// @brief commit a transaction - Result commitTransaction(transaction::Methods* trx) override; + [[nodiscard]] Result commitTransaction(transaction::Methods* trx) override; /// @brief abort a transaction - Result abortTransaction(transaction::Methods* trx) override; - + [[nodiscard]] Result abortTransaction(transaction::Methods* trx) override; + /// @brief return number of commits, including intermediate commits - uint64_t numCommits() const override; + [[nodiscard]] uint64_t numCommits() const override; - bool hasFailedOperations() const override { return false; } + [[nodiscard]] bool hasFailedOperations() const override { return false; } + + [[nodiscard]] TRI_voc_tick_t lastOperationTick() const noexcept override; protected: std::unique_ptr createTransactionCollection( @@ -54,4 +64,3 @@ class ClusterTransactionState final : public TransactionState { }; } // namespace arangodb - diff --git a/arangod/IResearch/IResearchAqlAnalyzer.cpp b/arangod/IResearch/IResearchAqlAnalyzer.cpp index 2808edf9e0c3..3c560075ceb0 100644 --- a/arangod/IResearch/IResearchAqlAnalyzer.cpp +++ b/arangod/IResearch/IResearchAqlAnalyzer.cpp @@ -185,36 +185,41 @@ bool normalize_slice(VPackSlice const& slice, VPackBuilder& builder) { class CalculationTransactionState final : public arangodb::TransactionState { public: explicit CalculationTransactionState(TRI_vocbase_t& vocbase) - : TransactionState(vocbase, arangodb::TransactionId(0), arangodb::transaction::Options()) { + : TransactionState(vocbase, arangodb::TransactionId(0), + arangodb::transaction::Options()) { updateStatus(arangodb::transaction::Status::RUNNING); // always running to make ASSERTS happy } - ~CalculationTransactionState() { + ~CalculationTransactionState() override { if (status() == arangodb::transaction::Status::RUNNING) { updateStatus(arangodb::transaction::Status::ABORTED); // simulate state changes to make ASSERTS happy } } /// @brief begin a transaction - arangodb::Result beginTransaction(arangodb::transaction::Hints) override { + [[nodiscard]] arangodb::Result beginTransaction(arangodb::transaction::Hints) override { return {}; } /// @brief commit a transaction - arangodb::Result commitTransaction(arangodb::transaction::Methods*) override { + [[nodiscard]] arangodb::Result commitTransaction(arangodb::transaction::Methods*) override { updateStatus(arangodb::transaction::Status::COMMITTED); // simulate state changes to make ASSERTS happy return {}; } /// @brief abort a transaction - arangodb::Result abortTransaction(arangodb::transaction::Methods*) override { + [[nodiscard]] arangodb::Result abortTransaction(arangodb::transaction::Methods*) override { updateStatus(arangodb::transaction::Status::ABORTED); // simulate state changes to make ASSERTS happy return {}; } - bool hasFailedOperations() const override { return false; } + [[nodiscard]] bool hasFailedOperations() const override { return false; } /// @brief number of commits, including intermediate commits - uint64_t numCommits() const override { return 0; } + [[nodiscard]] uint64_t numCommits() const override { return 0; } + + [[nodiscard]] TRI_voc_tick_t lastOperationTick() const noexcept override { + return 0; + } std::unique_ptr createTransactionCollection( arangodb::DataSourceId cid, arangodb::AccessMode::Type accessType) override { diff --git a/arangod/RocksDBEngine/Methods/RocksDBReadOnlyBaseMethods.cpp b/arangod/RocksDBEngine/Methods/RocksDBReadOnlyBaseMethods.cpp index 0e8bafec8051..eaba6d9fb8be 100644 --- a/arangod/RocksDBEngine/Methods/RocksDBReadOnlyBaseMethods.cpp +++ b/arangod/RocksDBEngine/Methods/RocksDBReadOnlyBaseMethods.cpp @@ -29,9 +29,6 @@ #include using namespace arangodb; - -RocksDBReadOnlyBaseMethods::RocksDBReadOnlyBaseMethods(RocksDBTransactionState* state) - : RocksDBTransactionMethods(state) {} void RocksDBReadOnlyBaseMethods::prepareOperation(DataSourceId cid, RevisionId rid, TRI_voc_document_operation_e operationType) { diff --git a/arangod/RocksDBEngine/Methods/RocksDBReadOnlyBaseMethods.h b/arangod/RocksDBEngine/Methods/RocksDBReadOnlyBaseMethods.h index 82c6f7285c15..274bc0ac8562 100644 --- a/arangod/RocksDBEngine/Methods/RocksDBReadOnlyBaseMethods.h +++ b/arangod/RocksDBEngine/Methods/RocksDBReadOnlyBaseMethods.h @@ -33,7 +33,7 @@ namespace arangodb { class RocksDBReadOnlyBaseMethods : public RocksDBTransactionMethods { public: - explicit RocksDBReadOnlyBaseMethods(RocksDBTransactionState* state); + using RocksDBTransactionMethods::RocksDBTransactionMethods; TRI_voc_tick_t lastOperationTick() const noexcept override { return 0; } diff --git a/arangod/RocksDBEngine/RocksDBTransactionState.cpp b/arangod/RocksDBEngine/RocksDBTransactionState.cpp index 55635d41306c..af4513a365b8 100644 --- a/arangod/RocksDBEngine/RocksDBTransactionState.cpp +++ b/arangod/RocksDBEngine/RocksDBTransactionState.cpp @@ -215,7 +215,8 @@ Result RocksDBTransactionState::commitTransaction(transaction::Methods* activeTr cleanupTransaction(); // deletes trx ++statistics()._transactionsCommitted; } else { - abortTransaction(activeTrx); // deletes trx + // what if this fails? + std::ignore = abortTransaction(activeTrx); // deletes trx } TRI_ASSERT(!_cacheTx); @@ -330,18 +331,36 @@ void RocksDBTransactionState::trackIndexRemove(DataSourceId cid, IndexId idxId, } } -bool RocksDBTransactionState::isOnlyExclusiveTransaction() const { +bool RocksDBTransactionState::isOnlyExclusiveTransaction() const noexcept { if (!AccessMode::isWriteOrExclusive(_type)) { return false; } - for (TransactionCollection* coll : _collections) { - if (AccessMode::isWrite(coll->accessType())) { - return false; - } - } - return true; + return std::none_of(_collections.cbegin(), _collections.cend(), [](auto* coll) { + return AccessMode::isWrite(coll->accessType()); + }); } +bool RocksDBTransactionState::hasFailedOperations() const { + return (_status == transaction::Status::ABORTED) && hasOperations(); +} + +RocksDBTransactionState* RocksDBTransactionState::toState(transaction::Methods* trx) { + TRI_ASSERT(trx != nullptr); + TransactionState* state = trx->state(); + TRI_ASSERT(state != nullptr); + return static_cast(state); +} + +RocksDBTransactionMethods* RocksDBTransactionState::toMethods(transaction::Methods* trx, DataSourceId collectionId) { + TRI_ASSERT(trx != nullptr); + TransactionState* state = trx->state(); + TRI_ASSERT(state != nullptr); + return static_cast(state)->rocksdbMethods(collectionId); +} + +void RocksDBTransactionState::prepareForParallelReads() { _parallel = true; } +bool RocksDBTransactionState::inParallelMode() const { return _parallel; } + #ifdef ARANGODB_ENABLE_MAINTAINER_MODE RocksDBTransactionStateGuard::RocksDBTransactionStateGuard(RocksDBTransactionState* state) noexcept : _state(state) { diff --git a/arangod/RocksDBEngine/RocksDBTransactionState.h b/arangod/RocksDBEngine/RocksDBTransactionState.h index 5153ab587928..7e539c97552a 100644 --- a/arangod/RocksDBEngine/RocksDBTransactionState.h +++ b/arangod/RocksDBEngine/RocksDBTransactionState.h @@ -66,26 +66,24 @@ class RocksDBTransactionState : public TransactionState { public: RocksDBTransactionState(TRI_vocbase_t& vocbase, TransactionId tid, transaction::Options const& options); - ~RocksDBTransactionState(); + ~RocksDBTransactionState() override; /// @brief begin a transaction - Result beginTransaction(transaction::Hints hints) override; + [[nodiscard]] Result beginTransaction(transaction::Hints hints) override; /// @brief commit a transaction - Result commitTransaction(transaction::Methods* trx) override; + [[nodiscard]] Result commitTransaction(transaction::Methods* trx) override; /// @brief abort a transaction - Result abortTransaction(transaction::Methods* trx) override; + [[nodiscard]] Result abortTransaction(transaction::Methods* trx) override; - virtual bool hasOperations() const noexcept = 0; + [[nodiscard]] virtual bool hasOperations() const noexcept = 0; - virtual uint64_t numOperations() const noexcept = 0; + [[nodiscard]] virtual uint64_t numOperations() const noexcept = 0; - bool hasFailedOperations() const override { - return (_status == transaction::Status::ABORTED) && hasOperations(); - } + [[nodiscard]] bool hasFailedOperations() const override; - bool iteratorMustCheckBounds(DataSourceId cid, ReadOwnWrites readOwnWrites) const; + [[nodiscard]] bool iteratorMustCheckBounds(DataSourceId cid, ReadOwnWrites readOwnWrites) const; void prepareOperation(DataSourceId cid, RevisionId rid, TRI_voc_document_operation_e operationType); @@ -93,38 +91,28 @@ class RocksDBTransactionState : public TransactionState { /// @brief add an operation for a transaction collection /// sets hasPerformedIntermediateCommit to true if an intermediate commit was /// performed - Result addOperation(DataSourceId collectionId, RevisionId revisionId, + [[nodiscard]] Result addOperation(DataSourceId collectionId, RevisionId revisionId, TRI_voc_document_operation_e opType, bool& hasPerformedIntermediateCommit); /// @brief return wrapper around rocksdb transaction - virtual RocksDBTransactionMethods* rocksdbMethods(DataSourceId collectionId) const = 0; - + [[nodiscard]] virtual RocksDBTransactionMethods* rocksdbMethods(DataSourceId collectionId) const = 0; + /// @brief acquire a database snapshot if we do not yet have one. /// Returns true if a snapshot was acquired, otherwise false (i.e., if we already had a snapshot) - virtual bool ensureSnapshot() = 0; - - static RocksDBTransactionState* toState(transaction::Methods* trx) { - TRI_ASSERT(trx != nullptr); - TransactionState* state = trx->state(); - TRI_ASSERT(state != nullptr); - return static_cast(state); - } - - static RocksDBTransactionMethods* toMethods(transaction::Methods* trx, DataSourceId collectionId) { - TRI_ASSERT(trx != nullptr); - TransactionState* state = trx->state(); - TRI_ASSERT(state != nullptr); - return static_cast(state)->rocksdbMethods(collectionId); - } + [[nodiscard]] virtual bool ensureSnapshot() = 0; + + [[nodiscard]] static RocksDBTransactionState* toState(transaction::Methods* trx); + + [[nodiscard]] static RocksDBTransactionMethods* toMethods(transaction::Methods* trx, DataSourceId collectionId); /// @brief make some internal preparations for accessing this state in /// parallel from multiple threads. READ-ONLY transactions - void prepareForParallelReads() { _parallel = true; } + void prepareForParallelReads(); /// @brief in parallel mode. READ-ONLY transactions - bool inParallelMode() const { return _parallel; } + [[nodiscard]] bool inParallelMode() const; - RocksDBTransactionCollection::TrackedOperations& trackedOperations(DataSourceId cid); + [[nodiscard]] RocksDBTransactionCollection::TrackedOperations& trackedOperations(DataSourceId cid); /// @brief Track documents inserted to the collection /// Used to update the revision tree for replication after commit @@ -142,9 +130,10 @@ class RocksDBTransactionState : public TransactionState { /// Used to update the estimate after the trx committed void trackIndexRemove(DataSourceId cid, IndexId idxObjectId, uint64_t hash); - bool isOnlyExclusiveTransaction() const; + /// @brief whether or not a transaction only has exclusive or read accesses + bool isOnlyExclusiveTransaction() const noexcept; - virtual rocksdb::SequenceNumber beginSeq() const = 0; + [[nodiscard]] virtual rocksdb::SequenceNumber beginSeq() const = 0; #ifdef ARANGODB_ENABLE_MAINTAINER_MODE /// @brief only needed for RocksDBTransactionStateGuard diff --git a/arangod/StorageEngine/TransactionCollection.h b/arangod/StorageEngine/TransactionCollection.h index e1e0b920fb57..08d0c99f23ac 100644 --- a/arangod/StorageEngine/TransactionCollection.h +++ b/arangod/StorageEngine/TransactionCollection.h @@ -60,7 +60,7 @@ class TransactionCollection { std::string const& collectionName() const; - AccessMode::Type accessType() const { return _accessType; } + AccessMode::Type accessType() const noexcept { return _accessType; } Result updateUsage(AccessMode::Type accessType); diff --git a/arangod/StorageEngine/TransactionState.cpp b/arangod/StorageEngine/TransactionState.cpp index 9d5905c942fa..77c9212190dc 100644 --- a/arangod/StorageEngine/TransactionState.cpp +++ b/arangod/StorageEngine/TransactionState.cpp @@ -28,6 +28,7 @@ #include "Basics/DebugRaceController.h" #include "Basics/Exceptions.h" #include "Basics/StringUtils.h" +#include "Basics/overload.h" #include "Logger/LogMacros.h" #include "Logger/Logger.h" #include "Logger/LoggerStream.h" @@ -51,15 +52,10 @@ using namespace arangodb; TransactionState::TransactionState(TRI_vocbase_t& vocbase, TransactionId tid, transaction::Options const& options) : _vocbase(vocbase), - _type(AccessMode::Type::READ), - _status(transaction::Status::CREATED), - _arena(), _collections{_arena}, // assign arena to vector - _hints(), _serverRole(ServerState::instance()->getRole()), _options(options), - _id(tid), - _registeredTransaction(false) { + _id(tid) { // patch intermediateCommitCount for testing #ifdef ARANGODB_ENABLE_FAILURE_TESTS @@ -84,15 +80,18 @@ TransactionCollection* TransactionState::collection(DataSourceId cid, TRI_ASSERT(_status == transaction::Status::CREATED || _status == transaction::Status::RUNNING); - size_t unused; - TransactionCollection* trxCollection = findCollection(cid, unused); - - if (trxCollection == nullptr || !trxCollection->canAccess(accessType)) { - // not found or not accessible in the requested mode - return nullptr; - } - - return trxCollection; + auto collectionOrPos = findCollectionOrPos(cid); + + return std::visit(overload{ + [](CollectionNotFound const&) -> TransactionCollection* { + return nullptr; + }, + [&](CollectionFound const& colFound) -> TransactionCollection* { + auto* const col = colFound.collection; + return col->canAccess(accessType) ? col : nullptr; + }, + }, + collectionOrPos); } /// @brief return the collection from a transaction @@ -114,7 +113,7 @@ TransactionCollection* TransactionState::collection(std::string const& name, return (*it); } -TransactionState::Cookie* TransactionState::cookie(void const* key) noexcept { +TransactionState::Cookie* TransactionState::cookie(void const* key) const noexcept { auto itr = _cookies.find(key); return itr == _cookies.end() ? nullptr : itr->second.get(); @@ -194,10 +193,9 @@ Result TransactionState::addCollectionInternal(DataSourceId cid, std::string con Result res; // check if we already got this collection in the _collections vector - size_t position = 0; - TransactionCollection* trxColl = findCollection(cid, position); - - if (trxColl != nullptr) { + auto colOrPos = findCollectionOrPos(cid); + if (std::holds_alternative(colOrPos)) { + auto* const trxColl = std::get(colOrPos).collection; LOG_TRX("ad6d0", TRACE, this) << "updating collection usage " << cid << ": '" << cname << "'"; @@ -212,6 +210,9 @@ Result TransactionState::addCollectionInternal(DataSourceId cid, std::string con // collection is already contained in vector return res.reset(trxColl->updateUsage(accessType)); } + TRI_ASSERT(std::holds_alternative(colOrPos)); + auto const position = std::get(colOrPos).lowerBound; + // collection not found. @@ -242,9 +243,8 @@ Result TransactionState::addCollectionInternal(DataSourceId cid, std::string con } // collection was not contained. now create and insert it - TRI_ASSERT(trxColl == nullptr); - trxColl = createTransactionCollection(cid, accessType).release(); + auto* const trxColl = createTransactionCollection(cid, accessType).release(); TRI_ASSERT(trxColl != nullptr); @@ -300,21 +300,22 @@ TransactionCollection* TransactionState::findCollection(DataSourceId cid) const /// The idea is if a collection is found it will be returned. /// In this case the position is not used. /// In case the collection is not found. It will return a -/// nullptr and the position will be set. The position +/// lower bound of its position. The position /// defines where the collection should be inserted, /// so whenever we want to insert the collection we /// have to use this position for insert. -TransactionCollection* TransactionState::findCollection(DataSourceId cid, - size_t& position) const { +auto TransactionState::findCollectionOrPos(DataSourceId cid) const + -> std::variant { size_t const n = _collections.size(); size_t i; + // TODO We could do a binary search here. for (i = 0; i < n; ++i) { - auto trxCollection = _collections[i]; + auto* trxCollection = _collections[i]; if (cid == trxCollection->id()) { // found - return trxCollection; + return CollectionFound{trxCollection}; } if (cid < trxCollection->id()) { @@ -324,10 +325,8 @@ TransactionCollection* TransactionState::findCollection(DataSourceId cid, // next } - // update the insert position if required - position = i; - - return nullptr; + // return the insert position if required + return CollectionNotFound{i}; } void TransactionState::setExclusiveAccessType() { diff --git a/arangod/StorageEngine/TransactionState.h b/arangod/StorageEngine/TransactionState.h index 9ab5501ab661..526988534769 100644 --- a/arangod/StorageEngine/TransactionState.h +++ b/arangod/StorageEngine/TransactionState.h @@ -38,6 +38,7 @@ #include "VocBase/voc-types.h" #include +#include #ifdef ARANGODB_ENABLE_MAINTAINER_MODE @@ -73,7 +74,7 @@ class TransactionState { virtual ~Cookie() = default; }; - static bool ServerIdLessThan(ServerID const& lhs, ServerID const& rhs) { + [[nodiscard]] static bool ServerIdLessThan(ServerID const& lhs, ServerID const& rhs) { return lhs < rhs; } @@ -88,51 +89,59 @@ class TransactionState { virtual ~TransactionState(); /// @return a cookie associated with the specified key, nullptr if none - Cookie* cookie(void const* key) noexcept; + [[nodiscard]] Cookie* cookie(void const* key) const noexcept; /// @brief associate the specified cookie with the specified key /// @return the previously associated cookie, if any Cookie::ptr cookie(void const* key, Cookie::ptr&& cookie); - bool isRunningInCluster() const { + [[nodiscard]] bool isRunningInCluster() const { return ServerState::isRunningInCluster(_serverRole); } - bool isDBServer() const { return ServerState::isDBServer(_serverRole); } - bool isCoordinator() const { return ServerState::isCoordinator(_serverRole); } - ServerState::RoleEnum serverRole() const { return _serverRole; } - - inline transaction::Options& options() { return _options; } - inline transaction::Options const& options() const { return _options; } - inline TRI_vocbase_t& vocbase() const { return _vocbase; } - inline TransactionId id() const { return _id; } - inline transaction::Status status() const { return _status; } - inline bool isRunning() const { + [[nodiscard]] bool isDBServer() const { + return ServerState::isDBServer(_serverRole); + } + [[nodiscard]] bool isCoordinator() const { + return ServerState::isCoordinator(_serverRole); + } + [[nodiscard]] ServerState::RoleEnum serverRole() const { return _serverRole; } + + [[nodiscard]] transaction::Options& options() { return _options; } + [[nodiscard]] transaction::Options const& options() const { + return _options; + } + [[nodiscard]] TRI_vocbase_t& vocbase() const { return _vocbase; } + [[nodiscard]] TransactionId id() const { return _id; } + [[nodiscard]] transaction::Status status() const noexcept { return _status; } + [[nodiscard]] bool isRunning() const { return _status == transaction::Status::RUNNING; } void setRegistered() noexcept { _registeredTransaction = true; } - bool wasRegistered() const noexcept { return _registeredTransaction; } + [[nodiscard]] bool wasRegistered() const noexcept { + return _registeredTransaction; + } /// @brief returns the name of the actor the transaction runs on: /// - leader /// - follower /// - coordinator /// - single - char const* actorName() const noexcept; + [[nodiscard]] char const* actorName() const noexcept; /// @brief return a reference to the global transaction statistics/counters TransactionStatistics& statistics() noexcept; - double lockTimeout() const { return _options.lockTimeout; } + [[nodiscard]] double lockTimeout() const { return _options.lockTimeout; } void lockTimeout(double value) { if (value > 0.0) { _options.lockTimeout = value; } } - bool waitForSync() const { return _options.waitForSync; } + [[nodiscard]] bool waitForSync() const { return _options.waitForSync; } void waitForSync(bool value) { _options.waitForSync = value; } - bool allowImplicitCollectionsForRead() const { + [[nodiscard]] bool allowImplicitCollectionsForRead() const { return _options.allowImplicitCollectionsForRead; } void allowImplicitCollectionsForRead(bool value) { @@ -140,17 +149,19 @@ class TransactionState { } /// @brief return the collection from a transaction - TransactionCollection* collection(DataSourceId cid, AccessMode::Type accessType) const; + [[nodiscard]] TransactionCollection* collection(DataSourceId cid, + AccessMode::Type accessType) const; /// @brief return the collection from a transaction - TransactionCollection* collection(std::string const& name, AccessMode::Type accessType) const; + [[nodiscard]] TransactionCollection* collection(std::string const& name, + AccessMode::Type accessType) const; /// @brief add a collection to a transaction - Result addCollection(DataSourceId cid, std::string const& cname, - AccessMode::Type accessType, bool lockUsage); + [[nodiscard]] Result addCollection(DataSourceId cid, std::string const& cname, + AccessMode::Type accessType, bool lockUsage); /// @brief use all participating collections of a transaction - Result useCollections(); + [[nodiscard]] Result useCollections(); /// @brief run a callback on all collections of the transaction template @@ -164,10 +175,10 @@ class TransactionState { } /// @brief return the number of collections in the transaction - size_t numCollections() const { return _collections.size(); } + [[nodiscard]] size_t numCollections() const { return _collections.size(); } /// @brief whether or not a transaction consists of a single operation - bool isSingleOperation() const { + [[nodiscard]] bool isSingleOperation() const { return hasHint(transaction::Hints::Hint::SINGLE_OPERATION); } @@ -175,7 +186,9 @@ class TransactionState { void updateStatus(transaction::Status status) noexcept; /// @brief whether or not a specific hint is set for the transaction - bool hasHint(transaction::Hints::Hint hint) const { return _hints.has(hint); } + [[nodiscard]] bool hasHint(transaction::Hints::Hint hint) const { + return _hints.has(hint); + } /// @brief begin a transaction virtual arangodb::Result beginTransaction(transaction::Hints hints) = 0; @@ -197,30 +210,27 @@ class TransactionState { virtual void beginQuery(bool /*isModificationQuery*/) {} virtual void endQuery(bool /*isModificationQuery*/) noexcept {} - TransactionCollection* findCollection(DataSourceId cid) const; + [[nodiscard]] TransactionCollection* findCollection(DataSourceId cid) const; /// @brief make a exclusive transaction, only valid before begin void setExclusiveAccessType(); /// @brief whether or not a transaction is read-only - bool isReadOnlyTransaction() const { - return (_type == AccessMode::Type::READ); + [[nodiscard]] bool isReadOnlyTransaction() const noexcept { + return _type == AccessMode::Type::READ; } /// @brief whether or not a transaction is a follower transaction - bool isFollowerTransaction() const { + [[nodiscard]] bool isFollowerTransaction() const { return hasHint(transaction::Hints::Hint::IS_FOLLOWER_TRX); } - /// @brief whether or not a transaction only has exculsive or read accesses - bool isOnlyExclusiveTransaction() const; - /// @brief servers already contacted - ::arangodb::containers::HashSet const& knownServers() const { + [[nodiscard]] ::arangodb::containers::HashSet const& knownServers() const { return _knownServers; } - bool knowsServer(std::string const& uuid) const { + [[nodiscard]] bool knowsServer(std::string const& uuid) const { return _knownServers.find(uuid) != _knownServers.end(); } @@ -235,18 +245,18 @@ class TransactionState { /// @returns tick of last operation in a transaction /// @note the value is guaranteed to be valid only after /// transaction is committed - virtual TRI_voc_tick_t lastOperationTick() const noexcept { return 0; } + [[nodiscard]] virtual TRI_voc_tick_t lastOperationTick() const noexcept = 0; void acceptAnalyzersRevision(QueryAnalyzerRevisions const& analyzersRevsion) noexcept; - const QueryAnalyzerRevisions& analyzersRevision() const noexcept { + [[nodiscard]] QueryAnalyzerRevisions const& analyzersRevision() const noexcept { return _analyzersRevision; } #ifdef USE_ENTERPRISE void addInaccessibleCollection(DataSourceId cid, std::string const& cname); - bool isInaccessibleCollection(DataSourceId cid); - bool isInaccessibleCollection(std::string const& cname); + [[nodiscard]] bool isInaccessibleCollection(DataSourceId cid); + [[nodiscard]] bool isInaccessibleCollection(std::string const& cname); #endif /// @brief roll a new transaction ID on the coordintor. Use this method @@ -260,7 +270,14 @@ class TransactionState { DataSourceId cid, AccessMode::Type accessType) = 0; /// @brief find a collection in the transaction's list of collections - TransactionCollection* findCollection(DataSourceId cid, size_t& position) const; + struct CollectionNotFound { + std::size_t lowerBound; + }; + struct CollectionFound { + TransactionCollection* collection; + }; + [[nodiscard]] auto findCollectionOrPos(DataSourceId cid) const + -> std::variant; /// @brief clear the query cache for all collections that were modified by /// the transaction @@ -285,15 +302,15 @@ class TransactionState { TRI_vocbase_t& _vocbase; /// @brief vocbase for this transaction /// @brief access type (read|write) - AccessMode::Type _type; + AccessMode::Type _type = AccessMode::Type::READ; /// @brief current status - transaction::Status _status; + transaction::Status _status = transaction::Status::CREATED; using ListType = arangodb::containers::SmallVector; - ListType::allocator_type::arena_type _arena; // memory for collections + ListType::allocator_type::arena_type _arena{}; // memory for collections ListType _collections; // list of participating collections - transaction::Hints _hints; // hints; set on _nestingLevel == 0 + transaction::Hints _hints{}; // hints; set on _nestingLevel == 0 ServerState::RoleEnum const _serverRole; /// role of the server @@ -309,7 +326,7 @@ class TransactionState { ::arangodb::containers::HashSet _knownServers; QueryAnalyzerRevisions _analyzersRevision; - bool _registeredTransaction; + bool _registeredTransaction = false; }; } // namespace arangodb diff --git a/arangod/Transaction/Hints.h b/arangod/Transaction/Hints.h index 5d4320cb9a6c..0a09d4af5ad9 100644 --- a/arangod/Transaction/Hints.h +++ b/arangod/Transaction/Hints.h @@ -23,14 +23,13 @@ #pragma once -#include "Basics/Common.h" +#include -namespace arangodb { -namespace transaction { +namespace arangodb::transaction { class Hints { public: - typedef uint32_t ValueType; + typedef std::uint32_t ValueType; /// @brief individual hint flags that can be used for transactions enum class Hint : ValueType { @@ -76,6 +75,5 @@ class Hints { ValueType _value; }; -} // namespace transaction -} // namespace arangodb +} // namespace arangodb::transaction diff --git a/arangod/Transaction/Manager.cpp b/arangod/Transaction/Manager.cpp index 4c863e7f1c87..9ec627b4e3f3 100644 --- a/arangod/Transaction/Manager.cpp +++ b/arangod/Transaction/Manager.cpp @@ -359,6 +359,8 @@ ResultT Manager::createManagedTrx(TRI_vocbase_t& vocbase, VPackSl Result Manager::ensureManagedTrx(TRI_vocbase_t& vocbase, TransactionId tid, VPackSlice trxOpts, bool isFollowerTransaction) { + TRI_ASSERT((ServerState::instance()->isSingleServer() && !isFollowerTransaction) || + tid.isFollowerTransactionId() == isFollowerTransaction); transaction::Options options; std::vector reads, writes, exclusives; @@ -367,10 +369,6 @@ Result Manager::ensureManagedTrx(TRI_vocbase_t& vocbase, TransactionId tid, return res; } - if (isFollowerTransaction) { - options.isFollowerTransaction = true; - } - return ensureManagedTrx(vocbase, tid, reads, writes, exclusives, std::move(options)); } @@ -562,20 +560,23 @@ ResultT Manager::createManagedTrx( if (res.fail()) { return res; } - std::shared_ptr state; ServerState::RoleEnum role = ServerState::instance()->getRole(); TRI_ASSERT(ServerState::isSingleServerOrCoordinator(role)); TransactionId tid = ServerState::isSingleServer(role) ? TransactionId::createSingleServer() : TransactionId::createCoordinator(); - try { - // now start our own transaction + + auto maybeState = basics::catchToResultT([&] { StorageEngine& engine = vocbase.server().getFeature().engine(); - state = engine.createTransactionState(vocbase, tid, options); - } catch (basics::Exception const& e) { - return res.reset(e.code(), e.message()); + // now start our own transaction + return engine.createTransactionState(vocbase, tid, options); + }); + if (!maybeState.ok()) { + return std::move(maybeState).result(); } + auto& state = maybeState.get(); + TRI_ASSERT(state != nullptr); TRI_ASSERT(state->id() == tid); @@ -627,9 +628,17 @@ Result Manager::ensureManagedTrx(TRI_vocbase_t& vocbase, TransactionId tid, return res.reset(TRI_ERROR_SHUTTING_DOWN); } - if (tid.isFollowerTransactionId()) { - options.isFollowerTransaction = true; - } + // This method should not be used in a single server. Note that single-server + // transaction IDs will randomly be identified as follower transactions, + // leader transactions, legacy transactions or coordinator transactions; + // context is important. + TRI_ASSERT(!ServerState::instance()->isSingleServer() || + ServerState::instance()->isGoogleTest()); + // We should never have `options.isFollowerTransaction == true`, but + // `tid.isFollowerTransactionId() == false`. + TRI_ASSERT(options.isFollowerTransaction == tid.isFollowerTransactionId() || + !options.isFollowerTransaction); + options.isFollowerTransaction = tid.isFollowerTransactionId(); LOG_TOPIC("7bd2d", DEBUG, Logger::TRANSACTIONS) << "managed trx creating: " << tid.id(); @@ -661,14 +670,16 @@ Result Manager::ensureManagedTrx(TRI_vocbase_t& vocbase, TransactionId tid, return res; } - std::shared_ptr state; - try { - // now start our own transaction + auto maybeState = basics::catchToResultT([&] { StorageEngine& engine = vocbase.server().getFeature().engine(); - state = engine.createTransactionState(vocbase, tid, options); - } catch (basics::Exception const& e) { - return res.reset(e.code(), e.message()); + // now start our own transaction + return engine.createTransactionState(vocbase, tid, options); + }); + if (!maybeState.ok()) { + return std::move(maybeState).result(); } + auto& state = maybeState.get(); + TRI_ASSERT(state != nullptr); TRI_ASSERT(state->id() == tid); diff --git a/arangod/Transaction/Options.cpp b/arangod/Transaction/Options.cpp index 0a7893203fc5..457a9e6ca153 100644 --- a/arangod/Transaction/Options.cpp +++ b/arangod/Transaction/Options.cpp @@ -32,25 +32,15 @@ using namespace arangodb::transaction; -uint64_t Options::defaultMaxTransactionSize = UINT64_MAX; -uint64_t Options::defaultIntermediateCommitSize = 512 * 1024 * 1024; -uint64_t Options::defaultIntermediateCommitCount = 1 * 1000 * 1000; - -Options::Options() - : lockTimeout(defaultLockTimeout), - maxTransactionSize(defaultMaxTransactionSize), - intermediateCommitSize(defaultIntermediateCommitSize), - intermediateCommitCount(defaultIntermediateCommitCount), - allowImplicitCollectionsForRead(true), - allowImplicitCollectionsForWrite(false), -#ifdef USE_ENTERPRISE - skipInaccessibleCollections(false), -#endif - waitForSync(false), - fillBlockCache(true), - isFollowerTransaction(false), - origin("", arangodb::RebootId(0)) { - +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +uint64_t Options::defaultMaxTransactionSize = + std::numeric_limits::max(); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +uint64_t Options::defaultIntermediateCommitSize = std::uint64_t{512} * 1024 * 1024; // 1 << 29 +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +uint64_t Options::defaultIntermediateCommitCount = 1'000'000; + +Options::Options() { // if we are a coordinator, fill in our own server id/reboot id. // the data is passed to DB servers when the transaction is started // there. the DB servers use this data to abort the transaction diff --git a/arangod/Transaction/Options.h b/arangod/Transaction/Options.h index 7d221148c79c..e7ff7f9a1440 100644 --- a/arangod/Transaction/Options.h +++ b/arangod/Transaction/Options.h @@ -60,23 +60,23 @@ struct Options { bool isIntermediateCommitEnabled() const noexcept; static constexpr double defaultLockTimeout = 900.0; - static uint64_t defaultMaxTransactionSize; - static uint64_t defaultIntermediateCommitSize; - static uint64_t defaultIntermediateCommitCount; + static std::uint64_t defaultMaxTransactionSize; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + static std::uint64_t defaultIntermediateCommitSize; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + static std::uint64_t defaultIntermediateCommitCount; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) /// @brief time (in seconds) that is spent waiting for a lock - double lockTimeout; - uint64_t maxTransactionSize; - uint64_t intermediateCommitSize; - uint64_t intermediateCommitCount; - bool allowImplicitCollectionsForRead; - bool allowImplicitCollectionsForWrite; // replication only! + double lockTimeout = defaultLockTimeout; + std::uint64_t maxTransactionSize = defaultMaxTransactionSize; + std::uint64_t intermediateCommitSize = defaultIntermediateCommitSize; + std::uint64_t intermediateCommitCount = defaultIntermediateCommitCount; + bool allowImplicitCollectionsForRead = true; + bool allowImplicitCollectionsForWrite = false; // replication only! #ifdef USE_ENTERPRISE - bool skipInaccessibleCollections; + bool skipInaccessibleCollections = false; #endif - bool waitForSync; - bool fillBlockCache; - bool isFollowerTransaction; + bool waitForSync = false; + bool fillBlockCache = true; + bool isFollowerTransaction = false; /// @brief originating server of this transaction. will be populated /// only in the cluster, and with a coordinator id/coordinator reboot id @@ -86,12 +86,12 @@ struct Options { /// abort the transaction should the coordinator die or be rebooted. /// the server id and reboot id are intentionally empty in single server /// case. - arangodb::cluster::RebootTracker::PeerState origin; + arangodb::cluster::RebootTracker::PeerState origin = {"", arangodb::RebootId(0)}; }; struct AllowImplicitCollectionsSwitcher { AllowImplicitCollectionsSwitcher(Options& options, bool allow) noexcept - : _options(options), + : _options(options), _oldValue(options.allowImplicitCollectionsForRead) { // previous value has been saved, now override value in options with disallow options.allowImplicitCollectionsForRead = allow; diff --git a/arangod/VocBase/AccessMode.h b/arangod/VocBase/AccessMode.h index cc34cba00935..ee77a6ea7fcf 100644 --- a/arangod/VocBase/AccessMode.h +++ b/arangod/VocBase/AccessMode.h @@ -40,16 +40,16 @@ struct AccessMode { AccessMode::Type::WRITE < AccessMode::Type::EXCLUSIVE, "AccessMode::Type total order fail"); - static bool isNone(Type type) { return (type == Type::NONE); } + static bool isNone(Type type) noexcept { return type == Type::NONE; } - static bool isRead(Type type) { return (type == Type::READ); } + static bool isRead(Type type) noexcept { return type == Type::READ; } - static bool isWrite(Type type) { return (type == Type::WRITE); } + static bool isWrite(Type type) noexcept { return type == Type::WRITE; } - static bool isExclusive(Type type) { return (type == Type::EXCLUSIVE); } + static bool isExclusive(Type type) noexcept { return type == Type::EXCLUSIVE; } - static bool isWriteOrExclusive(Type type) { - return (isWrite(type) || isExclusive(type)); + static bool isWriteOrExclusive(Type type) noexcept { + return isWrite(type) || isExclusive(type); } /// @brief checks if the type of the two modes is different diff --git a/arangod/VocBase/Identifiers/TransactionId.cpp b/arangod/VocBase/Identifiers/TransactionId.cpp index c74ac240d62a..a61a6d4097b5 100644 --- a/arangod/VocBase/Identifiers/TransactionId.cpp +++ b/arangod/VocBase/Identifiers/TransactionId.cpp @@ -62,4 +62,12 @@ TransactionId TransactionId::createLegacy() { return TransactionId(TRI_NewServerSpecificTickMod4() + 3); } +TransactionId TransactionId::createLeader() { + return TransactionId(TRI_NewServerSpecificTickMod4() + 1); +} + +TransactionId TransactionId::createFollower() { + return TransactionId(TRI_NewServerSpecificTickMod4() + 2); +} + } // namespace arangodb diff --git a/arangod/VocBase/Identifiers/TransactionId.h b/arangod/VocBase/Identifiers/TransactionId.h index c61392005aa3..8e67e0e74a0d 100644 --- a/arangod/VocBase/Identifiers/TransactionId.h +++ b/arangod/VocBase/Identifiers/TransactionId.h @@ -64,6 +64,10 @@ class TransactionId : public basics::Identifier { /// @brief create a legacy id static TransactionId createLegacy(); + + static TransactionId createLeader(); + + static TransactionId createFollower(); }; // TransactionId should not be bigger than the BaseType diff --git a/lib/Basics/Exceptions.h b/lib/Basics/Exceptions.h index c10f7d29435f..411eb79cde03 100644 --- a/lib/Basics/Exceptions.h +++ b/lib/Basics/Exceptions.h @@ -30,6 +30,7 @@ #include #include "Basics/Result.h" +#include "Basics/ResultT.h" #include "Basics/SourceLocation.h" #include "Basics/StringUtils.h" #include "Basics/error.h" @@ -127,39 +128,62 @@ namespace helper { } // namespace helper template -Result catchToResult(F&& fn, ErrorCode defaultError = TRI_ERROR_INTERNAL) noexcept { - Result result{TRI_ERROR_NO_ERROR}; +[[nodiscard]] auto catchToResult(F&& fn) noexcept -> Result { // The outer try/catch catches possible exceptions thrown by result.reset(), // due to allocation failure. If we don't have enough memory to allocate an // error, let's just give up. try { try { - result = std::forward(fn)(); + return std::forward(fn)(); // TODO check whether there are other specific exceptions we should catch } catch (arangodb::basics::Exception const& e) { - result.reset(e.code(), e.message()); + return Result(e.code(), e.message()); } catch (std::bad_alloc const&) { - result.reset(TRI_ERROR_OUT_OF_MEMORY); + return Result(TRI_ERROR_OUT_OF_MEMORY); } catch (std::exception const& e) { - result.reset(defaultError, e.what()); + return Result(TRI_ERROR_INTERNAL, e.what()); } catch (...) { - result.reset(defaultError); + return Result(TRI_ERROR_INTERNAL); + } + } catch (std::exception const& e) { + helper::dieWithLogMessage(e.what()); + } catch (...) { + helper::dieWithLogMessage(nullptr); + } +} + +template > +[[nodiscard]] auto catchToResultT(F&& fn) noexcept -> ResultT { + // The outer try/catch catches possible exceptions thrown by result.reset(), + // due to allocation failure. If we don't have enough memory to allocate an + // error, let's just give up. + try { + try { + return std::forward(fn)(); + // TODO check whether there are other specific exceptions we should catch + } catch (arangodb::basics::Exception const& e) { + return ResultT::error(e.code(), e.message()); + } catch (std::bad_alloc const&) { + return ResultT::error(TRI_ERROR_OUT_OF_MEMORY); + } catch (std::exception const& e) { + return ResultT::error(TRI_ERROR_INTERNAL, e.what()); + } catch (...) { + return ResultT::error(TRI_ERROR_INTERNAL); } } catch (std::exception const& e) { helper::dieWithLogMessage(e.what()); } catch (...) { helper::dieWithLogMessage(nullptr); } - return result; } template -Result catchVoidToResult(F&& fn, ErrorCode defaultError = TRI_ERROR_INTERNAL) noexcept { +[[nodiscard]] auto catchVoidToResult(F&& fn) noexcept -> Result { auto wrapped = [&fn]() -> Result { std::forward(fn)(); return Result{TRI_ERROR_NO_ERROR}; }; - return catchToResult(wrapped, defaultError); + return catchToResult(wrapped); } namespace helper { diff --git a/tests/Mocks/StorageEngineMock.cpp b/tests/Mocks/StorageEngineMock.cpp index 874520677cbd..f711bc7be0ae 100644 --- a/tests/Mocks/StorageEngineMock.cpp +++ b/tests/Mocks/StorageEngineMock.cpp @@ -1970,6 +1970,10 @@ bool TransactionStateMock::hasFailedOperations() const { return false; // assume no failed operations } +TRI_voc_tick_t TransactionStateMock::lastOperationTick() const noexcept { + return 0; +} + std::unique_ptr TransactionStateMock::createTransactionCollection( arangodb::DataSourceId cid, arangodb::AccessMode::Type accessType) { return std::make_unique(this, cid, accessType); diff --git a/tests/Mocks/StorageEngineMock.h b/tests/Mocks/StorageEngineMock.h index 0d38f5ad9c24..4f93d3b52654 100644 --- a/tests/Mocks/StorageEngineMock.h +++ b/tests/Mocks/StorageEngineMock.h @@ -179,6 +179,7 @@ class TransactionStateMock : public arangodb::TransactionState { virtual arangodb::Result commitTransaction(arangodb::transaction::Methods* trx) override; virtual uint64_t numCommits() const override; virtual bool hasFailedOperations() const override; + TRI_voc_tick_t lastOperationTick() const noexcept override; std::unique_ptr createTransactionCollection( arangodb::DataSourceId cid, arangodb::AccessMode::Type accessType) override; diff --git a/tests/Transaction/Manager-test.cpp b/tests/Transaction/Manager-test.cpp index c6f9e9e8da60..72ec510b2582 100644 --- a/tests/Transaction/Manager-test.cpp +++ b/tests/Transaction/Manager-test.cpp @@ -76,7 +76,7 @@ class TransactionManagerTest : public ::testing::Test { TransactionManagerTest() : vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, testDBInfo(setup.server.server())), mgr(transaction::ManagerFeature::manager()), - tid(TRI_NewTickServer()) {} + tid(TransactionId::createLeader()) {} ~TransactionManagerTest() { mgr->garbageCollect(true); } }; @@ -230,6 +230,7 @@ TEST_F(TransactionManagerTest, simple_transaction_and_commit) { } TEST_F(TransactionManagerTest, simple_transaction_and_commit_is_follower) { + tid = TransactionId::createFollower(); auto beforeRole = arangodb::ServerState::instance()->getRole(); auto roleGuard = scopeGuard([&]() noexcept { arangodb::ServerState::instance()->setRole(beforeRole); diff --git a/tests/main.cpp b/tests/main.cpp index b3fdc71cd265..f743dca8051b 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -132,6 +132,7 @@ int main(int argc, char* argv[]) { // many other places rely on the reboot id being initialized, // so we do it here in a central place arangodb::ServerState::instance()->setRebootId(arangodb::RebootId{1}); + arangodb::ServerState::instance()->setGoogleTest(true); IcuInitializer::setup(ARGV0); // enable mocking globally - not awesome, but helps to prevent runtime