Small cleanup of some mvcc classes

Reviewers: florijan, buda

Reviewed By: florijan

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D835
This commit is contained in:
Mislav Bradac 2017-09-27 14:45:50 +02:00
parent e70f4de208
commit afff458afa
14 changed files with 161 additions and 175 deletions

View File

@ -207,7 +207,7 @@ bool GraphDbAccessor::RemoveVertex(VertexAccessor &vertex_accessor) {
// it's possible the vertex was removed already in this transaction
// due to it getting matched multiple times by some patterns
// we can only delete it once, so check if it's already deleted
if (vertex_accessor.current_->is_deleted_by(*transaction_)) return true;
if (vertex_accessor.current_->is_expired_by(*transaction_)) return true;
if (vertex_accessor.out_degree() > 0 || vertex_accessor.in_degree() > 0)
return false;
@ -228,7 +228,7 @@ void GraphDbAccessor::DetachRemoveVertex(VertexAccessor &vertex_accessor) {
// it's possible the vertex was removed already in this transaction
// due to it getting matched multiple times by some patterns
// we can only delete it once, so check if it's already deleted
if (!vertex_accessor.current_->is_deleted_by(*transaction_))
if (!vertex_accessor.current_->is_expired_by(*transaction_))
vertex_accessor.vlist_->remove(vertex_accessor.current_, *transaction_);
}
@ -289,7 +289,7 @@ void GraphDbAccessor::RemoveEdge(EdgeAccessor &edge_accessor,
// due to it getting matched multiple times by some patterns
// we can only delete it once, so check if it's already deleted
edge_accessor.SwitchNew();
if (edge_accessor.current_->is_deleted_by(*transaction_)) return;
if (edge_accessor.current_->is_expired_by(*transaction_)) return;
if (remove_from_from)
edge_accessor.from().update().out_.RemoveEdge(edge_accessor.vlist_);
if (remove_from_to)

View File

@ -118,9 +118,9 @@ class GraphDbAccessor {
[this, current_state](const VertexAccessor &accessor) {
return (accessor.old_ &&
!(current_state &&
accessor.old_->is_deleted_by(*transaction_))) ||
accessor.old_->is_expired_by(*transaction_))) ||
(current_state && accessor.new_ &&
!accessor.new_->is_deleted_by(*transaction_));
!accessor.new_->is_expired_by(*transaction_));
},
std::move(accessors));
}
@ -286,9 +286,9 @@ class GraphDbAccessor {
[this, current_state](const EdgeAccessor &accessor) {
return (accessor.old_ &&
!(current_state &&
accessor.old_->is_deleted_by(*transaction_))) ||
accessor.old_->is_expired_by(*transaction_))) ||
(current_state && accessor.new_ &&
!accessor.new_->is_deleted_by(*transaction_));
!accessor.new_->is_expired_by(*transaction_));
},
std::move(accessors));
}
@ -548,11 +548,11 @@ class GraphDbAccessor {
// - we have new_ and it hasn't been deleted
if (!accessor.new_) {
debug_assert(
!accessor.old_->is_deleted_by(*transaction_),
!accessor.old_->is_expired_by(*transaction_),
"Can't update a record deleted in the current transaction+command");
} else {
debug_assert(
!accessor.new_->is_deleted_by(*transaction_),
!accessor.new_->is_expired_by(*transaction_),
"Can't update a record deleted in the current transaction+command");
}

View File

@ -98,8 +98,8 @@ static auto GetVlists(
// transaction+command
// taking into account the current_state flag
bool visible =
(old_record && !(current_state && old_record->is_deleted_by(t))) ||
(current_state && new_record && !new_record->is_deleted_by(t));
(old_record && !(current_state && old_record->is_expired_by(t))) ||
(current_state && new_record && !new_record->is_expired_by(t));
if (!visible) return false;
// if current_state is true and we have the new record, then that's
// the reference value, and that needs to be compared with the index
@ -167,8 +167,8 @@ static void Refresh(
// because it's creator transaction could still be modifying it,
// and modify+read is not thread-safe. for that reason we need to
// first see if the the transaction that created it has ended
// (tx.cre() < oldest active trancsation).
else if (indices_entry.record_->tx.cre() < snapshot.back() &&
// (tx().cre < oldest active trancsation).
else if (indices_entry.record_->tx().cre < snapshot.back() &&
!exists(key_indices_pair.first, indices_entry)) {
indices_entries_accessor.remove(indices_entry);
}

View File

@ -1,32 +0,0 @@
#pragma once
#include <atomic>
namespace mvcc {
template <class T>
class CreExp {
public:
CreExp() = default;
CreExp(T cre, T exp) : cre_(cre), exp_(exp) {}
T cre(std::memory_order order = std::memory_order_seq_cst) const {
return cre_.load(order);
}
void cre(T value, std::memory_order order = std::memory_order_seq_cst) {
cre_.store(value, order);
}
T exp(std::memory_order order = std::memory_order_seq_cst) const {
return exp_.load(order);
}
void exp(T value, std::memory_order order = std::memory_order_seq_cst) {
exp_.store(value, order);
}
private:
std::atomic<T> cre_{0}, exp_{0};
};
}

View File

@ -7,7 +7,6 @@
#include "transactions/engine.hpp"
#include "transactions/transaction.hpp"
#include "mvcc/cre_exp.hpp"
#include "mvcc/hints.hpp"
#include "mvcc/version.hpp"
#include "storage/locking/record_lock.hpp"
@ -21,29 +20,35 @@ template <class T>
class Record : public Version<T> {
public:
Record() = default;
Record(const Record &) = delete;
Record &operator=(const Record &) = delete;
Record(Record &&) = delete;
Record &operator=(Record &&) = delete;
// The copy constructor ignores tx, cmd, hints and super because
// they contain atomic variables that can't be copied
// it's still useful to have this copy constructor so that subclass
// data can easily be copied
// TODO maybe disable the copy-constructor and instead use a
// data variable in the version_list update() function (and similar)
// like it was in Dominik's implementation
Record(const Record &) {}
private:
template <typename TId>
struct CreExp {
std::atomic<TId> cre{0};
std::atomic<TId> exp{0};
};
// tx.cre is the id of the transaction that created the record
// and tx.exp is the id of the transaction that deleted the record
// these values are used to determine the visibility of the record
// to the current transaction
CreExp<tx::transaction_id_t> tx;
// These values are used to determine the visibility of the record
// to the current transaction.
CreExp<tx::transaction_id_t> tx_;
// cmd.cre is the id of the command in this transaction that created the
// record and cmd.exp is the id of the command in this transaction that
// deleted the record. these values are used to determine the visibility
// of the record to the current command in the running transaction
CreExp<tx::command_id_t> cmd;
// deleted the record. These values are used to determine the visibility
// of the record to the current command in the running transaction.
CreExp<tx::command_id_t> cmd_;
Hints hints;
Hints hints_;
public:
inline const auto &tx() const { return tx_; }
inline const auto &cmd() const { return cmd_; }
// NOTE: Wasn't used.
// this lock is used by write queries when they update or delete records
@ -60,15 +65,15 @@ class Record : public Version<T> {
tx::command_id_t cmd_exp;
std::tie(tx_exp, cmd_exp) = fetch_exp();
return ((tx.cre() == t.id_ && // inserted by the current transaction
cmd.cre() < t.cid() && // before this command, and
return ((tx_.cre == t.id_ && // inserted by the current transaction
cmd_.cre < t.cid() && // before this command, and
(tx_exp == 0 || // the row has not been deleted, or
(tx_exp == t.id_ && // it was deleted by the current
// transaction
cmd_exp >= t.cid()))) // but not before this command,
|| // or
(cre_committed(tx.cre(), t) && // the record was inserted by a
// committed transaction, and
(cre_committed(tx_.cre, t) && // the record was inserted by a
// committed transaction, and
(tx_exp == 0 || // the record has not been deleted, or
(tx_exp == t.id_ && // the row is being deleted by this
// transaction
@ -80,27 +85,27 @@ class Record : public Version<T> {
}
void mark_created(const tx::Transaction &t) {
debug_assert(tx.cre() == 0, "Marking node as created twice.");
tx.cre(t.id_);
cmd.cre(t.cid());
debug_assert(tx_.cre == 0, "Marking node as created twice.");
tx_.cre = t.id_;
cmd_.cre = t.cid();
}
void mark_deleted(const tx::Transaction &t) {
if (tx.exp() != 0) hints.exp.clear();
tx.exp(t.id_);
cmd.exp(t.cid());
void mark_expired(const tx::Transaction &t) {
if (tx_.exp != 0) hints_.exp.clear();
tx_.exp = t.id_;
cmd_.exp = t.cid();
}
bool exp_committed(tx::transaction_id_t id, const tx::Transaction &t) {
return committed(hints.exp, id, t);
return committed(hints_.exp, id, t);
}
bool exp_committed(tx::Engine &engine) {
return committed(hints.exp, tx.exp(), engine);
return committed(hints_.exp, tx_.exp, engine);
}
bool cre_committed(tx::transaction_id_t id, const tx::Transaction &t) {
return committed(hints.cre, id, t);
return committed(hints_.cre, id, t);
}
/**
@ -114,7 +119,7 @@ class Record : public Version<T> {
tx::Engine &engine) const {
// first get tx.exp so that all the subsequent checks operate on
// the same id. otherwise there could be a race condition
auto exp_id = tx.exp();
auto exp_id = tx_.exp.load();
// a record is NOT visible if:
// 1. it creating transaction aborted (last check)
@ -130,7 +135,7 @@ class Record : public Version<T> {
// newer transactions)
return (exp_id != 0 && exp_id < snapshot.back() &&
engine.clog().is_committed(exp_id) && !snapshot.contains(exp_id)) ||
engine.clog().is_aborted(tx.cre());
engine.clog().is_aborted(tx_.cre);
}
// TODO: Test this
@ -145,8 +150,8 @@ class Record : public Version<T> {
tx::command_id_t cmd_exp;
std::tie(tx_exp, cmd_exp) = fetch_exp();
return (tx.cre() == t.id_ && // inserted by the current transaction
cmd.cre() <= t.cid() && // before OR DURING this command, and
return (tx_.cre == t.id_ && // inserted by the current transaction
cmd_.cre <= t.cid() && // before OR DURING this command, and
(tx_exp == 0 || // the row has not been deleted, or
(tx_exp == t.id_ && // it was deleted by the current
// transaction
@ -158,19 +163,20 @@ class Record : public Version<T> {
* of the given transaction.
*/
bool is_created_by(const tx::Transaction &t) {
return tx.cre() == t.id_ && cmd.cre() == t.cid();
return tx_.cre == t.id_ && cmd_.cre == t.cid();
}
/**
* True if this record is deleted in the current command
* True if this record is expired in the current command
* of the given transaction.
*/
bool is_deleted_by(const tx::Transaction &t) {
return tx.exp() == t.id_ && cmd.exp() == t.cid();
bool is_expired_by(const tx::Transaction &t) {
return tx_.exp == t.id_ && cmd_.exp == t.cid();
}
private:
/** Fetch the (transaction, command) expiration before the check
/**
* Fetch the (transaction, command) expiration before the check
* because they can be concurrently modified by multiple transactions.
* Do it in a loop to ensure that command is consistent with transaction.
*/
@ -178,9 +184,9 @@ class Record : public Version<T> {
tx::transaction_id_t tx_exp;
tx::command_id_t cmd_exp;
do {
tx_exp = tx.exp();
cmd_exp = cmd.exp();
} while (tx_exp != tx.exp());
tx_exp = tx_.exp;
cmd_exp = cmd_.exp;
} while (tx_exp != tx_.exp);
return std::make_pair(tx_exp, cmd_exp);
}

View File

@ -8,31 +8,25 @@ template <class T>
class Version {
public:
Version() = default;
Version(T *older) : older(older) {}
Version(T *older) : older_(older) {}
~Version() { delete older.load(std::memory_order_seq_cst); }
~Version() { delete older_.load(std::memory_order_seq_cst); }
// return a pointer to an older version stored in this record
T *next(std::memory_order order = std::memory_order_seq_cst) {
return older.load(order);
return older_.load(order);
}
const T *next(std::memory_order order = std::memory_order_seq_cst) const {
return older.load(order);
return older_.load(order);
}
// set the older version of this record
void next(T *value, std::memory_order order = std::memory_order_seq_cst) {
older.store(value, order);
}
// sets if as expected
bool cas(T *expected, T *set,
std::memory_order order = std::memory_order_seq_cst) {
return older.compare_exchange_strong(expected, set, order);
older_.store(value, order);
}
private:
std::atomic<T *> older{nullptr};
std::atomic<T *> older_{nullptr};
};
}

View File

@ -1,7 +1,5 @@
#pragma once
#include <shared_mutex>
#include "storage/locking/record_lock.hpp"
#include "threading/sync/lockable.hpp"
#include "transactions/transaction.hpp"
@ -32,14 +30,11 @@ class VersionList {
* creating the first Record (Version) in this VersionList.
*/
template <typename... Args>
VersionList(tx::Transaction &t, Args &&... args) {
VersionList(const tx::Transaction &t, Args &&... args) {
// TODO replace 'new' with something better
auto v1 = new T(std::forward<Args>(args)...);
// mark the record as created by the transaction t
auto *v1 = new T(std::forward<Args>(args)...);
v1->mark_created(t);
head.store(v1, std::memory_order_seq_cst);
head_ = v1;
}
VersionList() = delete;
@ -50,16 +45,16 @@ class VersionList {
// lifteme. We also assume that the VList owner is the database and that
// ownership is also handled via raw pointers so this shouldn't be moved or
// move assigned.
VersionList(const VersionList &&other) = delete;
VersionList &operator=(const VersionList &&other) = delete;
VersionList(VersionList &&other) = delete;
VersionList &operator=(VersionList &&other) = delete;
~VersionList() { delete head.load(); }
~VersionList() { delete head_.load(); }
friend std::ostream &operator<<(std::ostream &stream,
const VersionList<T> &vlist) {
stream << "VersionList" << std::endl;
auto record = vlist.head.load();
T *record = vlist.head_;
while (record != nullptr) {
stream << "-- " << *record << std::endl;
@ -82,7 +77,7 @@ class VersionList {
* @param snapshot - the GC snapshot. Consists of the oldest active
* transaction's snapshot, with that transaction's id appened as last.
* @param engine - transaction engine to use - we need it to check which
* records were commited and which werent
* records were commited and which weren't
* @return pair<status, to_delete>; status is true - If version list is empty
* after garbage collection. to_delete points to the newest record that is not
* visible anymore. If none exists to_delete will point to nullptr.
@ -100,44 +95,37 @@ class VersionList {
// [VerList] ----+ record, or you reach the end of the list
//
auto current = head.load();
T *head_of_deletable_records = current;
T *head = head_;
T *current = head;
T *oldest_visible_record = nullptr;
while (current) {
if (!current->is_not_visible_from(snapshot, engine))
oldest_visible_record = current;
current = current->next();
}
if (oldest_visible_record)
head_of_deletable_records = oldest_visible_record->next();
// This can happen only if the head already points to a deleted record or
// the version list is empty. This means that the version_list is ready
// for complete destruction.
if (oldest_visible_record == nullptr) {
// Head points to a deleted record.
if (head_of_deletable_records != nullptr) {
head.store(nullptr, std::memory_order_seq_cst);
// This is safe to return as ready for deletion since we unlinked head
// above and this will only be deleted after the last active transaction
// ends.
return std::make_pair(true, head_of_deletable_records);
}
return std::make_pair(true, nullptr);
if (oldest_visible_record) {
T *head_of_deletable_records = oldest_visible_record->next();
// oldest_visible_record might be visible to some transaction but
// head_of_deletable_records is not and will never be visted by the find
// function and as such doesn't represent pointer invalidation
// race-condition risk.
oldest_visible_record->next(nullptr); // No transaction will look
// further than this record and
// that's why it's safe to set
// next to nullptr.
// Calling destructor of head_of_deletable_records will clean everything
// older than this record since they are called recursively.
return std::make_pair(false, head_of_deletable_records);
}
// oldest_visible_record might be visible to some transaction but
// head_of_deletable_records is not and will never be visted by the find
// function and as such doesn't represent pointer invalidation
// race-condition risk.
oldest_visible_record->next(
nullptr, std::memory_order_seq_cst); // No transaction will look
// further than this record and
// that's why it's safe to set
// next to nullptr.
// Calling destructor of head_of_deletable_records will clean everything
// older
// than this record since they are called recursively.
return std::make_pair(false, head_of_deletable_records);
// This can happen only if the head points to a expired record. Since there
// is no visible records in this version_list we can remove it.
head_ = nullptr;
// This is safe to return as ready for deletion since we unlinked head
// above and this will only be deleted after the last active transaction
// ends.
return std::make_pair(true, head);
}
/**
@ -145,14 +133,14 @@ class VersionList {
* @return nullptr if none exist
*/
T *Oldest() {
auto r = head.load(std::memory_order_seq_cst);
T *r = head_;
while (r && r->next(std::memory_order_seq_cst))
r = r->next(std::memory_order_seq_cst);
return r;
}
T *find(const tx::Transaction &t) {
auto r = head.load(std::memory_order_seq_cst);
T *r = head_;
// nullptr
// |
@ -189,7 +177,7 @@ class VersionList {
// assume that the sought old record is further down the list
// from new record, so that if we found old we can stop looking
new_ref = nullptr;
old_ref = head.load(std::memory_order_seq_cst);
old_ref = head_;
while (old_ref != nullptr && !old_ref->visible(t)) {
if (!new_ref && old_ref->is_created_by(t)) new_ref = old_ref;
old_ref = old_ref->next(std::memory_order_seq_cst);
@ -206,7 +194,7 @@ class VersionList {
* @param t The transaction
*/
T *update(tx::Transaction &t) {
debug_assert(head != nullptr, "Head is nullptr on update.");
debug_assert(head_ != nullptr, "Head is nullptr on update.");
T *old_record = nullptr;
T *new_record = nullptr;
find_set_old_new(t, old_record, new_record);
@ -222,7 +210,7 @@ class VersionList {
}
void remove(tx::Transaction &t) {
debug_assert(head != nullptr, "Head is nullptr on removal.");
debug_assert(head_ != nullptr, "Head is nullptr on removal.");
auto record = find(t);
permanent_assert(record != nullptr, "Removing nullptr record");
@ -237,7 +225,7 @@ class VersionList {
void remove(T *record, tx::Transaction &t) {
debug_assert(record != nullptr, "Record is nullptr on removal.");
lock_and_validate(record, t);
record->mark_deleted(t);
record->mark_expired(t);
}
private:
@ -246,11 +234,11 @@ class VersionList {
"Record is nullptr on lock and validation.");
// take a lock on this node
t.TakeLock(lock);
t.TakeLock(lock_);
// if the record hasn't been deleted yet or the deleting transaction
// has aborted, it's ok to modify it
if (!record->tx.exp() || !record->exp_committed(t.engine_)) return;
if (!record->tx().exp || !record->exp_committed(t.engine_)) return;
// if it committed, then we have a serialization conflict
throw SerializationError();
@ -263,22 +251,22 @@ class VersionList {
// It could be done with unique_ptr but while this could mean memory
// leak on exception, unique_ptr could mean use after free. Memory
// leak is less dangerous.
auto updated = new T(*record);
auto *updated = record->CloneData();
updated->mark_created(t);
record->mark_deleted(t);
record->mark_expired(t);
// Updated version should point to the latest available version. Older
// versions that can be deleted will be removed during the GC phase.
updated->next(head.load(), std::memory_order_seq_cst);
updated->next(head_.load(), std::memory_order_seq_cst);
// Store the updated version as the first version point to by head.
head.store(updated, std::memory_order_seq_cst);
head_.store(updated, std::memory_order_seq_cst);
return updated;
}
std::atomic<T *> head{nullptr};
RecordLock lock;
std::atomic<T *> head_{nullptr};
RecordLock lock_;
};
}

View File

@ -5,7 +5,6 @@
#include "mvcc/version_list.hpp"
#include "storage/property_value_store.hpp"
// forward declare Vertex because there is a circular usage Edge <-> Vertex
class Vertex;
class Edge : public mvcc::Record<Edge> {
@ -13,9 +12,21 @@ class Edge : public mvcc::Record<Edge> {
Edge(mvcc::VersionList<Vertex> &from, mvcc::VersionList<Vertex> &to,
GraphDbTypes::EdgeType edge_type)
: from_(from), to_(to), edge_type_(edge_type) {}
// Returns new Edge with copy of data stored in this Edge, but without
// copying superclass' members.
Edge *CloneData() { return new Edge(*this); }
mvcc::VersionList<Vertex> &from_;
mvcc::VersionList<Vertex> &to_;
GraphDbTypes::EdgeType edge_type_;
PropertyValueStore<GraphDbTypes::Property> properties_;
private:
Edge(const Edge &other)
: mvcc::Record<Edge>(),
from_(other.from_),
to_(other.to_),
edge_type_(other.edge_type_),
properties_(other.properties_) {}
};
;

View File

@ -5,13 +5,23 @@
#include "storage/edges.hpp"
#include "storage/property_value_store.hpp"
// forward declare Edge because there is a circular usage Edge <-> Vertex
class Edge;
class Vertex : public mvcc::Record<Vertex> {
public:
Vertex() = default;
// Returns new Vertex with copy of data stored in this Vertex, but without
// copying superclass' members.
Vertex *CloneData() { return new Vertex(*this); }
Edges out_;
Edges in_;
std::vector<GraphDbTypes::Label> labels_;
PropertyValueStore<GraphDbTypes::Property> properties_;
private:
Vertex(const Vertex &other)
: mvcc::Record<Vertex>(),
out_(other.out_),
in_(other.in_),
labels_(other.labels_),
properties_(other.properties_) {}
};

View File

@ -5,7 +5,11 @@
#include "mvcc/record.hpp"
#include "mvcc/version_list.hpp"
class Prop : public mvcc::Record<Prop> {};
class Prop : public mvcc::Record<Prop> {
public:
Prop() = default;
Prop *CloneData() { return new Prop; }
};
// Benchmark multiple updates, and finds, focused on finds.
// This a rather weak test, but I'm not sure what's the better way to test this

View File

@ -10,14 +10,12 @@
#include "mvcc_gc_common.hpp"
class TestClass : public mvcc::Record<TestClass> {};
TEST(MVCC, Deadlock) {
tx::Engine engine;
auto t0 = engine.Begin();
mvcc::VersionList<TestClass> version_list1(*t0);
mvcc::VersionList<TestClass> version_list2(*t0);
mvcc::VersionList<Prop> version_list1(*t0);
mvcc::VersionList<Prop> version_list2(*t0);
t0->Commit();
auto t1 = engine.Begin();
@ -59,7 +57,7 @@ TEST(MVCC, UpdateDontDelete) {
TEST(MVCC, Oldest) {
tx::Engine engine;
auto t1 = engine.Begin();
mvcc::VersionList<TestClass> version_list(*t1);
mvcc::VersionList<Prop> version_list(*t1);
auto first = version_list.Oldest();
EXPECT_NE(first, nullptr);
// TODO Gleich: no need to do 10 checks of the same thing

View File

@ -14,12 +14,13 @@ class TestClass : public mvcc::Record<TestClass> {
TestClass(int &version_list_size) : version_list_size_(version_list_size) {
++version_list_size_;
}
TestClass *CloneData() { return new TestClass(version_list_size_); }
// version constructed in version list update
TestClass(TestClass &other) : version_list_size_(other.version_list_size_) {
version_list_size_++;
}
friend std::ostream &operator<<(std::ostream &stream, TestClass &test_class) {
stream << test_class.tx.cre() << " " << test_class.tx.exp();
stream << test_class.tx().cre << " " << test_class.tx().exp;
return stream;
}
// reference to variable version_list_size in test SetUp, increases when new
@ -77,12 +78,14 @@ class Mvcc : public ::testing::Test {
#define T3_COMMIT t3->Commit();
#define T2_ABORT t2->Abort();
#define T3_ABORT t3->Abort();
#define T3_BEGIN auto t3 = engine.Begin(); __attribute__((unused)) int id3 = t3->id_
#define T3_BEGIN \
auto t3 = engine.Begin(); \
__attribute__((unused)) int id3 = t3->id_
#define T4_BEGIN auto t4 = engine.Begin();
#define T2_REMOVE version_list.remove(*t2)
#define T3_REMOVE version_list.remove(*t3)
#define EXPECT_CRE(record, expected) EXPECT_EQ(record->tx.cre(), id##expected)
#define EXPECT_EXP(record, expected) EXPECT_EQ(record->tx.exp(), id##expected)
#define EXPECT_CRE(record, expected) EXPECT_EQ(record->tx().cre, id##expected)
#define EXPECT_EXP(record, expected) EXPECT_EQ(record->tx().exp, id##expected)
#define EXPECT_NXT(v1, v2) EXPECT_EQ(v1->next(), v2)
#define EXPECT_SIZE(n) EXPECT_EQ(version_list_size, n)

View File

@ -5,7 +5,10 @@
/**
* @brief - Empty class which inherits from mvcc:Record.
*/
class Prop : public mvcc::Record<Prop> {};
class Prop : public mvcc::Record<Prop> {
public:
Prop *CloneData() { return new Prop; }
};
/**
* @brief - Class which inherits from mvcc::Record and takes an atomic variable
@ -15,6 +18,7 @@ class Prop : public mvcc::Record<Prop> {};
class DestrCountRec : public mvcc::Record<DestrCountRec> {
public:
DestrCountRec(std::atomic<int> &count) : count_(count) {}
DestrCountRec *CloneData() { return new DestrCountRec(count_); }
~DestrCountRec() { ++count_; }
private:

View File

@ -6,11 +6,11 @@
#undef EXPECT_CRE
#undef EXPECT_EXP
#define EXPECT_CRE(record, transaction, command) \
EXPECT_EQ(record->tx.cre(), id##transaction); \
EXPECT_EQ(record->cmd.cre(), command)
EXPECT_EQ(record->tx().cre, id##transaction); \
EXPECT_EQ(record->cmd().cre, command)
#define EXPECT_EXP(record, transaction, command) \
EXPECT_EQ(record->tx.exp(), id##transaction); \
EXPECT_EQ(record->cmd.exp(), command)
EXPECT_EQ(record->tx().exp, id##transaction); \
EXPECT_EQ(record->cmd().exp, command)
// IMPORTANT: look definiton of EXPECT_CRE and EXPECT_EXP macros in
// tests/mvcc_find_update_common.hpp. Numbers in those macros represent