memgraph/src/mvcc/version_list.hpp

183 lines
4.1 KiB
C++
Raw Normal View History

#pragma once
#include "threading/sync/lockable.hpp"
#include "transactions/transaction.hpp"
#include "memory/lazy_gc.hpp"
#include "mvcc/serialization_error.hpp"
2016-01-02 19:20:51 +08:00
#include "storage/locking/record_lock.hpp"
namespace mvcc
{
template <class T>
class VersionList : public LazyGC<VersionList<T>>
{
2015-12-08 01:45:44 +08:00
friend class Accessor;
public:
using uptr = std::unique_ptr<VersionList<T>>;
2016-03-12 17:39:10 +08:00
using item_t = T;
2016-03-12 17:39:10 +08:00
VersionList(Id id) : id(id) {}
VersionList(const VersionList&) = delete;
2015-12-09 01:30:20 +08:00
/* @brief Move constructs the version list
* Note: use only at the beginning of the "other's" lifecycle since this
* constructor doesn't move the RecordLock, but only the head pointer
*/
2016-03-12 17:39:10 +08:00
VersionList(VersionList&& other) : id(other.id)
2015-12-09 01:30:20 +08:00
{
this->head = other.head.load();
2015-12-09 01:30:20 +08:00
other.head = nullptr;
}
~VersionList()
{
delete head.load();
}
friend std::ostream& operator<<(std::ostream& stream,
const VersionList<T>& vlist)
{
stream << "VersionList" << std::endl;
auto record = vlist.head.load();
while(record != nullptr)
{
stream << "-- " << *record << std::endl;
record = record->next();
}
return stream;
}
auto gc_lock_acquire()
{
return std::unique_lock<RecordLock>(lock);
}
void vacuum()
{
}
2016-08-08 16:32:34 +08:00
T* find(const tx::Transaction& t) const
{
auto r = head.load(std::memory_order_seq_cst);
// nullptr
// |
// [v1] ...
// |
// [v2] <------+
// | |
// [v3] <------+
// | | Jump backwards until you find a first visible
// [VerList] ----+ version, or you reach the end of the list
//
while(r != nullptr && !r->visible(t))
r = r->next(std::memory_order_seq_cst);
return r;
}
T* insert(tx::Transaction& t)
{
assert(head == nullptr);
// create a first version of the record
// TODO replace 'new' with something better
auto v1 = new T();
// mark the record as created by the transaction t
v1->mark_created(t);
head.store(v1, std::memory_order_seq_cst);
return v1;
}
T* update(tx::Transaction& t)
{
assert(head != nullptr);
2016-01-02 19:20:51 +08:00
auto record = find(t);
// check if we found any visible records
if(!record)
return nullptr;
2016-01-02 19:20:51 +08:00
return update(record, t);
}
T* update(T* record, tx::Transaction& t)
{
assert(record != nullptr);
lock_and_validate(record, t);
auto updated = new T();
updated->data = record->data;
updated->mark_created(t);
record->mark_deleted(t);
updated->next(record, std::memory_order_seq_cst);
head.store(updated, std::memory_order_seq_cst);
return updated;
}
bool remove(tx::Transaction& t)
{
assert(head != nullptr);
2016-01-02 19:20:51 +08:00
auto record = find(t);
if(!record)
return false;
2016-01-02 19:20:51 +08:00
lock_and_validate(record, t);
return remove(record, t), true;
}
bool remove(T* record, tx::Transaction& t)
2016-01-02 19:20:51 +08:00
{
assert(record != nullptr);
lock_and_validate(record, t);
record->mark_deleted(t);
return true;
}
const Id id;
private:
2016-01-02 19:20:51 +08:00
void lock_and_validate(T* record, tx::Transaction& t)
{
assert(record != nullptr);
assert(record == find(t));
// take a lock on this node
t.take_lock(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))
2016-01-02 19:20:51 +08:00
return;
// if it committed, then we have a serialization conflict
2015-12-08 01:45:44 +08:00
assert(record->hints.load().exp.is_committed());
throw SerializationError();
}
std::atomic<T*> head {nullptr};
RecordLock lock;
};
}
class Vertex;
class Edge;
using VertexRecord = mvcc::VersionList<Vertex>;
using EdgeRecord = mvcc::VersionList<Edge>;