memgraph/tests/unit/transaction_engine_distributed.cpp
florijan 9721ccf61c Cleanup per-transaction caches in distributed
Summary:
On the master cleanups are hooked directly into the transaction engine.
This is beneficial because the master might have bigger caches and we
want to clear them as soon as possible.

On the workers there is a periodic RPC call to the master about living
transactions, which takes care of releasing local caches. This is
suboptimal because long transactions will prevent cache GC (like with
data GC). It is however fairly simple.

Note that all cleanup is not done automatically and `RemotePull` has
been reduced accordingly. @msantl, please verify correctness and
consider if the code can be additionally simplified.

Reviewers: teon.banek, msantl

Reviewed By: msantl

Subscribers: pullbot, msantl

Differential Revision: https://phabricator.memgraph.io/D1202
2018-02-16 15:30:05 +01:00

161 lines
4.4 KiB
C++

#include <algorithm>
#include <mutex>
#include <unordered_set>
#include <vector>
#include "gtest/gtest.h"
#include "communication/rpc/server.hpp"
#include "io/network/endpoint.hpp"
#include "transactions/engine_master.hpp"
#include "transactions/engine_rpc_messages.hpp"
#include "transactions/engine_worker.hpp"
#include "transactions/tx_end_listener.hpp"
using namespace tx;
using namespace communication::rpc;
class WorkerEngineTest : public testing::Test {
protected:
const std::string local{"127.0.0.1"};
System master_system_{{local, 0}};
MasterEngine master_{master_system_};
WorkerEngine worker_{master_system_.endpoint()};
};
TEST_F(WorkerEngineTest, BeginOnWorker) {
worker_.Begin();
auto second = worker_.Begin();
EXPECT_EQ(master_.RunningTransaction(second->id_)->snapshot().size(), 1);
}
TEST_F(WorkerEngineTest, AdvanceOnWorker) {
auto tx = worker_.Begin();
auto cid = tx->cid();
EXPECT_EQ(worker_.Advance(tx->id_), cid + 1);
}
TEST_F(WorkerEngineTest, CommitOnWorker) {
auto tx = worker_.Begin();
auto tx_id = tx->id_;
worker_.Commit(*tx);
EXPECT_TRUE(master_.Info(tx_id).is_committed());
}
TEST_F(WorkerEngineTest, AbortOnWorker) {
auto tx = worker_.Begin();
auto tx_id = tx->id_;
worker_.Abort(*tx);
EXPECT_TRUE(master_.Info(tx_id).is_aborted());
}
TEST_F(WorkerEngineTest, RunningTransaction) {
master_.Begin();
master_.Begin();
worker_.RunningTransaction(1);
worker_.RunningTransaction(2);
int count = 0;
worker_.LocalForEachActiveTransaction([&count](Transaction &t) {
++count;
if (t.id_ == 1) {
EXPECT_EQ(t.snapshot(),
tx::Snapshot(std::vector<tx::transaction_id_t>{}));
} else {
EXPECT_EQ(t.snapshot(), tx::Snapshot({1}));
}
});
EXPECT_EQ(count, 2);
}
TEST_F(WorkerEngineTest, Info) {
auto *tx_1 = master_.Begin();
auto *tx_2 = master_.Begin();
// We can't check active transactions in the worker (see comments there for
// info).
master_.Commit(*tx_1);
EXPECT_TRUE(master_.Info(1).is_committed());
EXPECT_TRUE(worker_.Info(1).is_committed());
master_.Abort(*tx_2);
EXPECT_TRUE(master_.Info(2).is_aborted());
EXPECT_TRUE(worker_.Info(2).is_aborted());
}
TEST_F(WorkerEngineTest, GlobalGcSnapshot) {
auto *tx_1 = master_.Begin();
master_.Begin();
master_.Commit(*tx_1);
EXPECT_EQ(master_.GlobalGcSnapshot(), tx::Snapshot({1, 2}));
EXPECT_EQ(worker_.GlobalGcSnapshot(), master_.GlobalGcSnapshot());
}
TEST_F(WorkerEngineTest, GlobalActiveTransactions) {
auto *tx_1 = master_.Begin();
master_.Begin();
auto *tx_3 = master_.Begin();
master_.Begin();
master_.Commit(*tx_1);
master_.Abort(*tx_3);
EXPECT_EQ(worker_.GlobalActiveTransactions(), tx::Snapshot({2, 4}));
}
TEST_F(WorkerEngineTest, GlobalIsActive) {
auto *tx_1 = master_.Begin();
master_.Begin();
auto *tx_3 = master_.Begin();
master_.Begin();
master_.Commit(*tx_1);
master_.Abort(*tx_3);
EXPECT_FALSE(worker_.GlobalIsActive(1));
EXPECT_TRUE(worker_.GlobalIsActive(2));
EXPECT_FALSE(worker_.GlobalIsActive(3));
EXPECT_TRUE(worker_.GlobalIsActive(4));
}
TEST_F(WorkerEngineTest, LocalLast) {
master_.Begin();
EXPECT_EQ(worker_.LocalLast(), 0);
worker_.RunningTransaction(1);
EXPECT_EQ(worker_.LocalLast(), 1);
master_.Begin();
EXPECT_EQ(worker_.LocalLast(), 1);
master_.Begin();
EXPECT_EQ(worker_.LocalLast(), 1);
master_.Begin();
worker_.RunningTransaction(4);
EXPECT_EQ(worker_.LocalLast(), 4);
}
TEST_F(WorkerEngineTest, LocalForEachActiveTransaction) {
master_.Begin();
worker_.RunningTransaction(1);
master_.Begin();
master_.Begin();
master_.Begin();
worker_.RunningTransaction(4);
std::unordered_set<tx::transaction_id_t> local;
worker_.LocalForEachActiveTransaction(
[&local](Transaction &t) { local.insert(t.id_); });
EXPECT_EQ(local, std::unordered_set<tx::transaction_id_t>({1, 4}));
}
TEST_F(WorkerEngineTest, TxEndListener) {
std::atomic<int> has_expired{0};
TxEndListener worker_end_listner{
worker_, [&has_expired](transaction_id_t tid) {
std::cout << "asdasdadas: " << tid << std::endl;
++has_expired; }};
auto sleep_period =
WorkerEngine::kCacheReleasePeriod + std::chrono::milliseconds(200);
auto t1 = master_.Begin();
auto t2 = master_.Begin();
std::this_thread::sleep_for(sleep_period);
EXPECT_EQ(has_expired.load(), 0);
master_.Commit(*t1);
master_.Abort(*t2);
std::this_thread::sleep_for(sleep_period);
EXPECT_EQ(has_expired.load(), 2);
}