2015-11-22 05:56:43 +08:00
|
|
|
#pragma once
|
|
|
|
|
2017-12-05 17:32:31 +08:00
|
|
|
#include "storage/gid.hpp"
|
2016-01-02 19:20:51 +08:00
|
|
|
#include "storage/locking/record_lock.hpp"
|
2017-04-28 16:33:46 +08:00
|
|
|
#include "threading/sync/lockable.hpp"
|
|
|
|
#include "transactions/transaction.hpp"
|
|
|
|
#include "utils/exceptions.hpp"
|
2015-11-22 05:56:43 +08:00
|
|
|
|
2017-02-02 16:03:48 +08:00
|
|
|
namespace mvcc {
|
2015-11-22 05:56:43 +08:00
|
|
|
|
2017-04-28 16:33:46 +08:00
|
|
|
class SerializationError : public utils::BasicException {
|
2017-05-16 18:22:28 +08:00
|
|
|
static constexpr const char *default_message =
|
2017-06-14 19:42:06 +08:00
|
|
|
"Can't serialize due to concurrent operations";
|
2017-04-28 16:33:46 +08:00
|
|
|
|
|
|
|
public:
|
|
|
|
using utils::BasicException::BasicException;
|
|
|
|
SerializationError() : BasicException(default_message) {}
|
|
|
|
};
|
|
|
|
|
2017-02-18 18:54:37 +08:00
|
|
|
template <class T>
|
|
|
|
class VersionList {
|
|
|
|
public:
|
2017-04-10 18:12:40 +08:00
|
|
|
/**
|
|
|
|
* @brief Constructor that is used to insert one item into VersionList.
|
|
|
|
* @param t - transaction
|
2017-12-05 17:32:31 +08:00
|
|
|
* @param gid - Version list identifier. Uniqueness guaranteed by the code
|
2017-11-13 16:50:49 +08:00
|
|
|
* creating this version list.
|
2017-04-10 18:12:40 +08:00
|
|
|
* @param args - args forwarded to constructor of item T (for
|
|
|
|
* creating the first Record (Version) in this VersionList.
|
2017-03-29 18:37:58 +08:00
|
|
|
*/
|
|
|
|
template <typename... Args>
|
2017-12-05 17:32:31 +08:00
|
|
|
VersionList(const tx::Transaction &t, gid::Gid gid, Args &&... args)
|
|
|
|
: gid_(gid) {
|
2017-04-03 17:12:15 +08:00
|
|
|
// TODO replace 'new' with something better
|
2017-09-27 20:45:50 +08:00
|
|
|
auto *v1 = new T(std::forward<Args>(args)...);
|
2017-04-03 17:12:15 +08:00
|
|
|
v1->mark_created(t);
|
2017-09-27 20:45:50 +08:00
|
|
|
head_ = v1;
|
2017-03-29 18:37:58 +08:00
|
|
|
}
|
2015-11-23 04:35:40 +08:00
|
|
|
|
2017-03-29 18:37:58 +08:00
|
|
|
VersionList() = delete;
|
2017-02-18 18:54:37 +08:00
|
|
|
VersionList(const VersionList &) = delete;
|
2017-03-29 18:37:58 +08:00
|
|
|
VersionList &operator=(const VersionList &) = delete;
|
2017-04-06 20:14:19 +08:00
|
|
|
// We do a lot of raw-pointer ops with VLists, and these ops assume that a
|
|
|
|
// VList's address identifies a vertex/edge absolutely and during it's whole
|
|
|
|
// 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.
|
2017-09-27 20:45:50 +08:00
|
|
|
VersionList(VersionList &&other) = delete;
|
|
|
|
VersionList &operator=(VersionList &&other) = delete;
|
2017-02-18 18:54:37 +08:00
|
|
|
|
2017-09-27 20:45:50 +08:00
|
|
|
~VersionList() { delete head_.load(); }
|
2017-02-18 18:54:37 +08:00
|
|
|
|
|
|
|
friend std::ostream &operator<<(std::ostream &stream,
|
|
|
|
const VersionList<T> &vlist) {
|
|
|
|
stream << "VersionList" << std::endl;
|
|
|
|
|
2017-09-27 20:45:50 +08:00
|
|
|
T *record = vlist.head_;
|
2015-12-06 23:42:47 +08:00
|
|
|
|
2017-02-18 18:54:37 +08:00
|
|
|
while (record != nullptr) {
|
|
|
|
stream << "-- " << *record << std::endl;
|
|
|
|
record = record->next();
|
2015-11-23 04:35:40 +08:00
|
|
|
}
|
2015-11-22 05:56:43 +08:00
|
|
|
|
2017-02-18 18:54:37 +08:00
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
|
2017-03-29 18:37:58 +08:00
|
|
|
/**
|
2017-06-12 16:21:19 +08:00
|
|
|
* Garbage collects records that are not reachable/visible anymore.
|
|
|
|
*
|
|
|
|
* Relinks this version-list so that garbage collected records are no
|
|
|
|
* longer reachable through this version list.
|
|
|
|
* Visibility is defined in mvcc::Record::is_not_visible_from,
|
|
|
|
* to which the given `snapshot` is passed.
|
|
|
|
*
|
|
|
|
* This method is NOT thread-safe.
|
|
|
|
*
|
|
|
|
* @param snapshot - the GC snapshot. Consists of the oldest active
|
|
|
|
* transaction's snapshot, with that transaction's id appened as last.
|
2017-04-14 23:32:59 +08:00
|
|
|
* @param engine - transaction engine to use - we need it to check which
|
2017-09-27 20:45:50 +08:00
|
|
|
* records were commited and which weren't
|
2017-04-14 23:32:59 +08:00
|
|
|
* @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.
|
2017-10-11 19:19:10 +08:00
|
|
|
*/
|
2017-06-12 16:21:19 +08:00
|
|
|
std::pair<bool, T *> GcDeleted(const tx::Snapshot &snapshot,
|
2017-09-27 22:53:11 +08:00
|
|
|
const tx::Engine &engine) {
|
2017-02-18 18:54:37 +08:00
|
|
|
// nullptr
|
|
|
|
// |
|
2017-05-18 21:23:08 +08:00
|
|
|
// [v1] ... all of this gets deleted!
|
2017-02-18 18:54:37 +08:00
|
|
|
// |
|
2017-05-18 21:23:08 +08:00
|
|
|
// [v2] <------+ head_of_deletable_records
|
2017-02-18 18:54:37 +08:00
|
|
|
// | |
|
2017-05-18 21:23:08 +08:00
|
|
|
// [v3] <------+ oldest_visible_record
|
|
|
|
// | | Jump backwards until you find the oldest visible
|
2017-03-29 18:37:58 +08:00
|
|
|
// [VerList] ----+ record, or you reach the end of the list
|
2017-02-18 18:54:37 +08:00
|
|
|
//
|
2017-06-02 15:19:23 +08:00
|
|
|
|
2017-09-27 20:45:50 +08:00
|
|
|
T *head = head_;
|
|
|
|
T *current = head;
|
2017-05-18 21:23:08 +08:00
|
|
|
T *oldest_visible_record = nullptr;
|
|
|
|
while (current) {
|
2017-06-12 16:21:19 +08:00
|
|
|
if (!current->is_not_visible_from(snapshot, engine))
|
2017-06-02 15:19:23 +08:00
|
|
|
oldest_visible_record = current;
|
|
|
|
current = current->next();
|
2017-02-18 18:54:37 +08:00
|
|
|
}
|
2017-09-27 20:45:50 +08:00
|
|
|
|
|
|
|
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);
|
2015-11-23 04:35:40 +08:00
|
|
|
}
|
2017-09-27 20:45:50 +08:00
|
|
|
|
|
|
|
// 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);
|
2017-04-14 23:32:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief - returns oldest record
|
|
|
|
* @return nullptr if none exist
|
|
|
|
*/
|
|
|
|
T *Oldest() {
|
2017-09-27 20:45:50 +08:00
|
|
|
T *r = head_;
|
2017-04-14 23:32:59 +08:00
|
|
|
while (r && r->next(std::memory_order_seq_cst))
|
|
|
|
r = r->next(std::memory_order_seq_cst);
|
|
|
|
return r;
|
2017-02-18 18:54:37 +08:00
|
|
|
}
|
2015-11-23 04:35:40 +08:00
|
|
|
|
2017-04-10 18:12:40 +08:00
|
|
|
T *find(const tx::Transaction &t) {
|
2017-09-27 20:45:50 +08:00
|
|
|
T *r = head_;
|
2015-11-23 04:35:40 +08:00
|
|
|
|
2017-02-18 18:54:37 +08:00
|
|
|
// 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);
|
2015-11-23 04:35:40 +08:00
|
|
|
|
2017-02-18 18:54:37 +08:00
|
|
|
return r;
|
|
|
|
}
|
2015-11-23 04:35:40 +08:00
|
|
|
|
2017-04-03 17:12:15 +08:00
|
|
|
/**
|
|
|
|
* Looks for and sets two versions. The 'old' version is the
|
|
|
|
* newest version that is visible by the current transaction+command,
|
|
|
|
* but has not been created by it. The 'new' version is the version
|
|
|
|
* that has been created by current transaction+command.
|
|
|
|
*
|
|
|
|
* It is possible that both, either or neither are found:
|
|
|
|
* - both are found when an existing record has been modified
|
|
|
|
* - only old is found when an existing record has not been modified
|
|
|
|
* - only new is found when the whole vlist was created
|
|
|
|
* - neither is found when for example the record has been deleted but not
|
|
|
|
* garbage collected yet
|
|
|
|
*
|
|
|
|
* @param t The transaction
|
|
|
|
*/
|
2017-04-21 21:39:41 +08:00
|
|
|
void find_set_old_new(const tx::Transaction &t, T *&old_ref, T *&new_ref) {
|
2017-04-03 17:12:15 +08:00
|
|
|
// 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;
|
2017-09-27 20:45:50 +08:00
|
|
|
old_ref = head_;
|
2017-04-03 17:12:15 +08:00
|
|
|
while (old_ref != nullptr && !old_ref->visible(t)) {
|
2017-04-06 20:14:19 +08:00
|
|
|
if (!new_ref && old_ref->is_created_by(t)) new_ref = old_ref;
|
2017-04-03 17:12:15 +08:00
|
|
|
old_ref = old_ref->next(std::memory_order_seq_cst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-02 15:19:23 +08:00
|
|
|
/**
|
|
|
|
* Looks for the first visible record seen by this transaction. If the current
|
|
|
|
* transaction has already created new record in the current command then that
|
|
|
|
* record is returned, else first older visible record is updated. New record
|
|
|
|
* becomes head of the version list and it is returned. There should always be
|
|
|
|
* older visible record when this update is called.
|
|
|
|
*
|
|
|
|
* @param t The transaction
|
|
|
|
*/
|
2017-11-29 16:34:18 +08:00
|
|
|
T *update(const tx::Transaction &t) {
|
2017-10-11 19:19:10 +08:00
|
|
|
DCHECK(head_ != nullptr) << "Head is nullptr on update.";
|
2017-06-02 15:19:23 +08:00
|
|
|
T *old_record = nullptr;
|
|
|
|
T *new_record = nullptr;
|
|
|
|
find_set_old_new(t, old_record, new_record);
|
|
|
|
|
|
|
|
// check if current transaction in current cmd has
|
|
|
|
// already updated version list
|
|
|
|
if (new_record) return new_record;
|
2015-12-06 23:42:47 +08:00
|
|
|
|
2017-02-18 18:54:37 +08:00
|
|
|
// check if we found any visible records
|
2017-10-11 19:19:10 +08:00
|
|
|
CHECK(old_record != nullptr) << "Updating nullptr record";
|
2015-11-23 04:35:40 +08:00
|
|
|
|
2017-06-02 15:19:23 +08:00
|
|
|
return update(old_record, t);
|
2017-02-18 18:54:37 +08:00
|
|
|
}
|
2015-12-06 23:42:47 +08:00
|
|
|
|
2017-10-17 15:52:13 +08:00
|
|
|
/** Makes the given record as being expired by the given transaction. */
|
2017-11-29 16:34:18 +08:00
|
|
|
void remove(T *record, const tx::Transaction &t) {
|
2017-10-11 19:19:10 +08:00
|
|
|
DCHECK(record != nullptr) << "Record is nullptr on removal.";
|
2017-02-18 18:54:37 +08:00
|
|
|
lock_and_validate(record, t);
|
2017-09-27 20:45:50 +08:00
|
|
|
record->mark_expired(t);
|
2017-02-18 18:54:37 +08:00
|
|
|
}
|
|
|
|
|
2017-12-05 17:32:31 +08:00
|
|
|
const gid::Gid gid_;
|
2017-11-13 16:50:49 +08:00
|
|
|
|
2017-02-18 18:54:37 +08:00
|
|
|
private:
|
2017-11-29 16:34:18 +08:00
|
|
|
void lock_and_validate(T *record, const tx::Transaction &t) {
|
2017-10-11 19:19:10 +08:00
|
|
|
DCHECK(record != nullptr) << "Record is nullptr on lock and validation.";
|
2017-02-18 18:54:37 +08:00
|
|
|
|
|
|
|
// take a lock on this node
|
2017-09-27 20:45:50 +08:00
|
|
|
t.TakeLock(lock_);
|
2017-02-18 18:54:37 +08:00
|
|
|
|
|
|
|
// if the record hasn't been deleted yet or the deleting transaction
|
|
|
|
// has aborted, it's ok to modify it
|
2017-09-27 20:45:50 +08:00
|
|
|
if (!record->tx().exp || !record->exp_committed(t.engine_)) return;
|
2016-06-26 00:26:26 +08:00
|
|
|
|
2017-02-18 18:54:37 +08:00
|
|
|
// if it committed, then we have a serialization conflict
|
|
|
|
throw SerializationError();
|
|
|
|
}
|
|
|
|
|
2017-11-29 16:34:18 +08:00
|
|
|
T *update(T *record, const tx::Transaction &t) {
|
2017-10-11 19:19:10 +08:00
|
|
|
DCHECK(record != nullptr) << "Record is nullptr on update.";
|
2017-04-10 18:12:40 +08:00
|
|
|
lock_and_validate(record, t);
|
|
|
|
|
|
|
|
// 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.
|
2017-09-27 20:45:50 +08:00
|
|
|
auto *updated = record->CloneData();
|
2017-04-10 18:12:40 +08:00
|
|
|
|
|
|
|
updated->mark_created(t);
|
2017-09-27 20:45:50 +08:00
|
|
|
record->mark_expired(t);
|
2017-04-10 18:12:40 +08:00
|
|
|
|
|
|
|
// Updated version should point to the latest available version. Older
|
|
|
|
// versions that can be deleted will be removed during the GC phase.
|
2017-09-27 20:45:50 +08:00
|
|
|
updated->next(head_.load(), std::memory_order_seq_cst);
|
2017-04-10 18:12:40 +08:00
|
|
|
|
|
|
|
// Store the updated version as the first version point to by head.
|
2017-09-27 20:45:50 +08:00
|
|
|
head_.store(updated, std::memory_order_seq_cst);
|
2017-04-10 18:12:40 +08:00
|
|
|
|
|
|
|
return updated;
|
|
|
|
}
|
|
|
|
|
2017-09-27 20:45:50 +08:00
|
|
|
std::atomic<T *> head_{nullptr};
|
|
|
|
RecordLock lock_;
|
2017-02-18 18:54:37 +08:00
|
|
|
};
|
2017-10-11 19:19:10 +08:00
|
|
|
} // namespace mvcc
|