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:
parent
e70f4de208
commit
afff458afa
@ -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)
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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};
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
};
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
}
|
||||
|
@ -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_) {}
|
||||
};
|
||||
;
|
||||
|
@ -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_) {}
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user