1
0
Fork 0
arangodb/tests/Transaction/Manager.cpp

422 lines
16 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @brief test suite for transaction Manager
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2019 ArangoDB GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// @author Simon Grätzer
////////////////////////////////////////////////////////////////////////////////
#include "Aql/Query.h"
#include "Aql/OptimizerRulesFeature.h"
#include "RestServer/AqlFeature.h"
#include "RestServer/DatabaseFeature.h"
#include "RestServer/TraverserEngineRegistryFeature.h"
#include "RestServer/QueryRegistryFeature.h"
#include "RestServer/ViewTypesFeature.h"
#include "Sharding/ShardingFeature.h"
#include "StorageEngine/EngineSelectorFeature.h"
#include "Transaction/Manager.h"
#include "Transaction/ManagerFeature.h"
#include "Transaction/StandaloneContext.h"
#include "Transaction/SmartContext.h"
#include "Transaction/Status.h"
#include "Utils/ExecContext.h"
#include "Utils/SingleCollectionTransaction.h"
#include "VocBase/LogicalCollection.h"
#include "../Mocks/StorageEngineMock.h"
#include <velocypack/Parser.h>
#include <velocypack/velocypack-aliases.h>
#include "catch.hpp"
using namespace arangodb;
// -----------------------------------------------------------------------------
// --SECTION-- setup / tear-down
// -----------------------------------------------------------------------------
struct TransactionManagerSetup {
StorageEngineMock engine;
arangodb::application_features::ApplicationServer server;
std::vector<std::pair<arangodb::application_features::ApplicationFeature*, bool>> features;
TransactionManagerSetup(): engine(server), server(nullptr, nullptr) {
arangodb::EngineSelectorFeature::ENGINE = &engine;
// setup required application features
features.emplace_back(new arangodb::DatabaseFeature(server), false); // required for TRI_vocbase_t::dropCollection(...)
features.emplace_back(new arangodb::ShardingFeature(server), false);
features.emplace_back(new transaction::ManagerFeature(server), true);
features.emplace_back(new arangodb::QueryRegistryFeature(server), false); // must be first
arangodb::application_features::ApplicationServer::server->addFeature(features.back().first); // need QueryRegistryFeature feature to be added now in order to create the system database
features.emplace_back(new arangodb::TraverserEngineRegistryFeature(server), false); // must be before AqlFeature
features.emplace_back(new arangodb::AqlFeature(server), true);
features.emplace_back(new arangodb::aql::OptimizerRulesFeature(server), true);
for (auto& f: features) {
arangodb::application_features::ApplicationServer::server->addFeature(f.first);
}
for (auto& f: features) {
f.first->prepare();
}
for (auto& f: features) {
if (f.second) {
f.first->start();
}
}
}
~TransactionManagerSetup() {
arangodb::application_features::ApplicationServer::server = nullptr;
arangodb::EngineSelectorFeature::ENGINE = nullptr;
// destroy application features
for (auto& f: features) {
if (f.second) {
f.first->stop();
}
}
for (auto& f: features) {
f.first->unprepare();
}
}
};
arangodb::aql::QueryResult executeQuery(TRI_vocbase_t& vocbase,
std::string const& queryString,
std::shared_ptr<transaction::Context> ctx) {
auto options = std::make_shared<VPackBuilder>();
options->openObject();
options->close();
std::shared_ptr<arangodb::velocypack::Builder> bindVars;
arangodb::aql::Query query(false,
vocbase,
arangodb::aql::QueryString(queryString),
bindVars,
options,
arangodb::aql::PART_MAIN);
query.setTransactionContext(std::move(ctx));
std::shared_ptr<arangodb::aql::SharedQueryState> ss = query.sharedState();
arangodb::aql::QueryResult result;
while (true) {
auto state = query.execute(arangodb::QueryRegistryFeature::registry(), result);
if (state == arangodb::aql::ExecutionState::WAITING) {
ss->waitForAsyncResponse();
} else {
break;
}
}
return result;
}
// -----------------------------------------------------------------------------
// --SECTION-- test suite
// -----------------------------------------------------------------------------
/// @brief test transaction::Manager
TEST_CASE("TransactionManagerTest", "[transaction]") {
TransactionManagerSetup setup;
TRI_ASSERT(transaction::ManagerFeature::manager());
TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase");
auto* mgr = transaction::ManagerFeature::manager();
REQUIRE(mgr != nullptr);
scopeGuard([&] {
mgr->garbageCollect(true);
});
TRI_voc_tid_t tid = TRI_NewTickServer();
SECTION("Parsing errors") {
auto json = arangodb::velocypack::Parser::fromJson("{ \"write\": [33] }");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
CHECK(res.is(TRI_ERROR_BAD_PARAMETER));
json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"write\": \"33\"}, \"lockTimeout\": -1 }");
res = mgr->createManagedTrx(vocbase, tid, json->slice());
CHECK(res.is(TRI_ERROR_BAD_PARAMETER));
}
SECTION("Collection Not found") {
auto json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"read\": [\"33\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
CHECK(res.errorNumber() == TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND);
json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"write\": [\"33\"]}}");
res = mgr->createManagedTrx(vocbase, tid, json->slice());
CHECK(res.errorNumber() == TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND);
json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"exclusive\": [\"33\"]}}");
res = mgr->createManagedTrx(vocbase, tid, json->slice());
CHECK(res.errorNumber() == TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND);
}
std::shared_ptr<LogicalCollection> coll;
{
auto json = VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
REQUIRE(coll != nullptr);
SECTION("Transaction ID reuse") {
auto json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"read\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
REQUIRE(res.ok());
json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"write\": [\"33\"]}}");
res = mgr->createManagedTrx(vocbase, tid, json->slice());
CHECK(res.errorNumber() == TRI_ERROR_TRANSACTION_INTERNAL);
res = mgr->abortManagedTrx(tid);
REQUIRE(res.ok());
}
SECTION("Simple Transaction & Abort") {
auto json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
REQUIRE(res.ok());
auto doc = arangodb::velocypack::Parser::fromJson("{ \"_key\": \"1\"}");
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
REQUIRE(ctx.get() != nullptr);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::WRITE);
REQUIRE(trx.state()->isEmbeddedTransaction());
OperationOptions opts;
auto opRes = trx.insert(coll->name(), doc->slice(), opts);
REQUIRE(opRes.ok());
REQUIRE(trx.finish(opRes.result).ok());
}
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
{ // lease again
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
REQUIRE(ctx.get() != nullptr);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::READ);
REQUIRE(trx.state()->isEmbeddedTransaction());
OperationOptions opts;
auto opRes = trx.document(coll->name(), doc->slice(), opts);
REQUIRE(opRes.ok());
REQUIRE(trx.finish(opRes.result).ok());
}
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
REQUIRE(mgr->abortManagedTrx(tid).ok());
// perform same operation
REQUIRE(mgr->abortManagedTrx(tid).ok());
// cannot commit aborted transaction
REQUIRE(mgr->commitManagedTrx(tid).is(TRI_ERROR_TRANSACTION_DISALLOWED_OPERATION));
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::ABORTED));
}
SECTION("Simple Transaction & Commit") {
auto json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
REQUIRE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
REQUIRE(ctx.get() != nullptr);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::WRITE);
REQUIRE(trx.state()->isEmbeddedTransaction());
auto doc = arangodb::velocypack::Parser::fromJson("{ \"abc\": 1}");
OperationOptions opts;
auto opRes = trx.insert(coll->name(), doc->slice(), opts);
REQUIRE(opRes.ok());
REQUIRE(trx.finish(opRes.result).ok());
}
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
REQUIRE(mgr->commitManagedTrx(tid).ok());
// perform same operation
REQUIRE(mgr->commitManagedTrx(tid).ok());
// cannot commit aborted transaction
REQUIRE(mgr->abortManagedTrx(tid).is(TRI_ERROR_TRANSACTION_DISALLOWED_OPERATION));
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::COMMITTED));
}
SECTION("Simple Transaction & Commit while in use") {
auto json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
REQUIRE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
REQUIRE(ctx.get() != nullptr);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::WRITE);
REQUIRE(trx.state()->isEmbeddedTransaction());
auto doc = arangodb::velocypack::Parser::fromJson("{ \"abc\": 1}");
OperationOptions opts;
auto opRes = trx.insert(coll->name(), doc->slice(), opts);
REQUIRE(opRes.ok());
REQUIRE(mgr->commitManagedTrx(tid).is(TRI_ERROR_TRANSACTION_DISALLOWED_OPERATION));
REQUIRE(trx.finish(opRes.result).ok());
}
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
REQUIRE(mgr->commitManagedTrx(tid).ok());
// perform same operation
REQUIRE(mgr->commitManagedTrx(tid).ok());
// cannot abort committed transaction
REQUIRE(mgr->abortManagedTrx(tid).is(TRI_ERROR_TRANSACTION_DISALLOWED_OPERATION));
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::COMMITTED));
}
SECTION("Leasing multiple read-only transactions") {
auto json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"read\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
REQUIRE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::READ);
REQUIRE(ctx.get() != nullptr);
REQUIRE(ctx->getParentTransaction() != nullptr);
auto ctx2 = mgr->leaseManagedTrx(tid, AccessMode::Type::READ);
REQUIRE(ctx2.get() != nullptr);
CHECK(ctx->getParentTransaction() == ctx2->getParentTransaction());
auto ctx3 = mgr->leaseManagedTrx(tid, AccessMode::Type::READ);
REQUIRE(ctx3.get() != nullptr);
CHECK(ctx->getParentTransaction() == ctx3->getParentTransaction());
}
REQUIRE(mgr->abortManagedTrx(tid).ok());
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::ABORTED));
}
SECTION("Lock conflict") {
auto json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
REQUIRE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
REQUIRE(ctx.get() != nullptr);
REQUIRE(ctx->getParentTransaction() != nullptr);
REQUIRE_THROWS(mgr->leaseManagedTrx(tid, AccessMode::Type::READ));
}
REQUIRE(mgr->abortManagedTrx(tid).ok());
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::ABORTED));
}
SECTION("Garbage Collection shutdown") {
auto json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
REQUIRE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
REQUIRE(ctx.get() != nullptr);
REQUIRE(ctx->getParentTransaction() != nullptr);
}
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
REQUIRE(mgr->garbageCollect(/*abortAll*/true));
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::ABORTED));
}
SECTION("AQL standalone transaction") {
{
auto ctx = transaction::StandaloneContext::Create(vocbase);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::WRITE);
REQUIRE(trx.begin().ok());
auto doc = arangodb::velocypack::Parser::fromJson("{ \"abc\": 1}");
OperationOptions opts;
auto opRes = trx.insert(coll->name(), doc->slice(), opts);
REQUIRE(opRes.ok());
REQUIRE(trx.finish(opRes.result).ok());
}
auto ctx = std::make_shared<transaction::AQLStandaloneContext>(vocbase, tid);
auto qq = "FOR doc IN testCollection RETURN doc";
arangodb::aql::QueryResult qres = executeQuery(vocbase, qq, ctx);
REQUIRE(qres.ok());
}
SECTION("Abort transactions with matcher") {
auto json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
REQUIRE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
REQUIRE(ctx.get() != nullptr);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::WRITE);
REQUIRE(trx.state()->isEmbeddedTransaction());
auto doc = arangodb::velocypack::Parser::fromJson("{ \"abc\": 1}");
OperationOptions opts;
auto opRes = trx.insert(coll->name(), doc->slice(), opts);
REQUIRE(opRes.ok());
REQUIRE(trx.finish(opRes.result).ok());
}
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
//
mgr->abortManagedTrx([](TransactionState const& state) -> bool {
TransactionCollection* tcoll = state.collection(42, AccessMode::Type::NONE);
return tcoll != nullptr;
});
REQUIRE((mgr->getManagedTrxStatus(tid) == transaction::Status::ABORTED));
}
// SECTION("Permission denied") {
// ExecContext exe(ExecContext::Type, "dummy",
// vocbase.name(), auth::Level::NONE, auth::Level::NONE);
// ExecContextScope scope();
//
// auto json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"read\": [\"42\"]}}");
// Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
// REQUIRE(res.ok());
//
// json = arangodb::velocypack::Parser::fromJson("{ \"collections\":{\"write\": [\"33\"]}}");
// res = mgr->createManagedTrx(vocbase, tid, json->slice());
// REQUIRE(res.errorNumber() == TRI_ERROR_TRANSACTION_INTERNAL);
// }
//
// SECTION("Acquire transaction") {
// transaction::ManagedContext ctx();
//
// }
}