diff --git a/src/mvcc/hints.hpp b/src/mvcc/hints.hpp deleted file mode 100644 index 39fec30f1..000000000 --- a/src/mvcc/hints.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -#include -#include - -#include "transactions/commit_log.hpp" -#include "utils/assert.hpp" - -namespace mvcc { - -// known committed and known aborted for both cre and exp -// this hints are used to quickly check the commit/abort status of the -// transaction that created this record. if these are not set, one should -// consult the commit log to find the status and update the status here -// more info https://wiki.postgresql.org/wiki/Hint_Bits -class Hints { - public: - union HintBits; - - private: - enum Flags : uint8_t { - CRE_CMT = 0x01, // __01 - CRE_ABT = 0x02, // __10 - EXP_CMT = 0x04, // 01__ - EXP_ABT = 0x08 // 10__ - }; - - template - class TxHints { - using type = TxHints; - - public: - TxHints(std::atomic &bits) : bits(bits) {} - - struct Value { - bool is_committed() const { return bits & COMMITTED; } - - bool is_aborted() const { return bits & ABORTED; } - - bool is_unknown() const { return !(is_committed() || is_aborted()); } - - uint8_t bits; - }; - - Value load(std::memory_order order = std::memory_order_seq_cst) { - return Value{bits.load(order)}; - } - - void set_committed(std::memory_order order = std::memory_order_seq_cst) { - bits.fetch_or(COMMITTED, order); - } - - void set_aborted(std::memory_order order = std::memory_order_seq_cst) { - bits.fetch_or(ABORTED, order); - } - - void clear_commited(std::memory_order order = std::memory_order_seq_cst) { - bits.fetch_and(~COMMITTED, order); - } - - void clear_aborted(std::memory_order order = std::memory_order_seq_cst) { - bits.fetch_and(~ABORTED, order); - } - - void clear(std::memory_order order = std::memory_order_seq_cst) { - clear_aborted(order); - clear_commited(order); - } - - private: - std::atomic &bits; - }; - - struct Cre : public TxHints { - using TxHints::TxHints; - }; - - struct Exp : public TxHints { - using TxHints::TxHints; - }; - - public: - Hints() : cre(bits), exp(bits) { - debug_assert(bits.is_lock_free(), "Bits are not lock free."); - } - - union HintBits { - uint8_t bits; - - Cre::Value cre; - Exp::Value exp; - }; - - HintBits load(std::memory_order order = std::memory_order_seq_cst) { - return HintBits{bits.load(order)}; - } - - Cre cre; - Exp exp; - - std::atomic bits{0}; -}; -} diff --git a/src/mvcc/record.hpp b/src/mvcc/record.hpp index 0086f2ab3..ec3ef3049 100644 --- a/src/mvcc/record.hpp +++ b/src/mvcc/record.hpp @@ -7,7 +7,6 @@ #include "transactions/engine.hpp" #include "transactions/transaction.hpp" -#include "mvcc/hints.hpp" #include "mvcc/version.hpp" #include "storage/locking/record_lock.hpp" @@ -25,35 +24,6 @@ class Record : public Version { Record(Record &&) = delete; Record &operator=(Record &&) = delete; - private: - template - struct CreExp { - std::atomic cre{0}; - std::atomic 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_; - - // 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 cmd_; - - 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 - // RecordLock lock; - // check if this record is visible to the transaction t bool visible(const tx::Transaction &t) { // Mike Olson says 17 march 1993: the tests in this routine are correct; @@ -65,23 +35,24 @@ class Record : public Version { 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 - (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 - (tx_exp == 0 || // the record has not been deleted, or - (tx_exp == t.id_ && // the row is being deleted by this - // transaction - cmd_exp >= t.cid()) || // but it's not deleted "yet", or - (tx_exp != t.id_ && // the row was deleted by another - // transaction - !exp_committed(tx_exp, t) // that has not been committed - )))); + 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 + (committed(Hints::kCre, 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 + cmd_exp >= t.cid()) || // but it's not deleted "yet", or + (tx_exp != t.id_ && // the row was deleted by another + // transaction + !committed(Hints::kExp, tx_exp, t) // that has not been committed + )))); } void mark_created(const tx::Transaction &t) { @@ -91,21 +62,13 @@ class Record : public Version { } void mark_expired(const tx::Transaction &t) { - if (tx_.exp != 0) hints_.exp.clear(); + if (tx_.exp != 0) hints_.Clear(Hints::kExp); 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); - } - bool exp_committed(tx::Engine &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::kExp, tx_.exp, engine); } /** @@ -174,9 +137,56 @@ class Record : public Version { return tx_.exp == t.id_ && cmd_.exp == t.cid(); } + const auto &tx() const { return tx_; } + const auto &cmd() const { return cmd_; } + private: /** - * Fetch the (transaction, command) expiration before the check + * Fast indicators if a transaction has committed or aborted. It is possible + * the hints do not have that information, in which case the commit log needs + * to be consulted (a slower operation). + */ + class Hints { + public: + /// Masks for the creation/expration and commit/abort positions. + static constexpr uint8_t kCre = 0b0011; + static constexpr uint8_t kExp = 0b1100; + static constexpr uint8_t kCmt = 0b0101; + static constexpr uint8_t kAbt = 0b1010; + + /** Retruns true if any bit under the given mask is set. */ + bool Get(uint8_t mask) const { return bits_ & mask; } + + /** Sets all the bits under the given mask. */ + void Set(uint8_t mask) { bits_.fetch_or(mask); } + + /** Clears all the bits under the given mask. */ + void Clear(uint8_t mask) { bits_.fetch_and(~mask); } + + private: + std::atomic bits_{0}; + }; + + template + struct CreExp { + std::atomic cre{0}; + std::atomic 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_; + + // 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 cmd_; + + Hints hints_; + /** 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. */ @@ -197,13 +207,15 @@ class Record : public Version { * Evaluates to true if that transaction has committed, * it started before `t` and it's not in it's snapshot. * - * @param hints - hints to use to determine commit/abort * about transactions commit/abort status + * @param mask - Hint bits mask (either Hints::kCre or Hints::kExp). * @param id - id to check if it's commited and visible * @return true if the id is commited and visible for the transaction t. */ - template - bool committed(U &hints, tx::transaction_id_t id, const tx::Transaction &t) { + bool committed(uint8_t mask, tx::transaction_id_t id, + const tx::Transaction &t) { + debug_assert(mask == Hints::kCre || mask == Hints::kExp, + "Mask must be either kCre or kExp"); // Dominik Gleich says 4 april 2017: the tests in this routine are correct; // if you think they're not, you're wrong, and you should think about it // again. I know, it happened to me (and also to Matej Gradicek). @@ -216,30 +228,30 @@ class Record : public Version { // The creating transaction is still in progress (examine snapshot) if (t.snapshot().contains(id)) return false; - return committed(hints, id, t.engine_); + return committed(mask, id, t.engine_); } /** * @brief - Check if the transaction with the given `id` * is committed. * - * @param hints - hints to use to determine commit/abort + * @param mask - Hint bits mask (either Hints::kCre or Hints::kExp). * @param id - id to check if commited * @param engine - engine instance with information about transaction * statuses * @return true if it's commited, false otherwise */ - template - bool committed(U &hints, tx::transaction_id_t id, tx::Engine &engine) { - auto hint_bits = hints.load(); + bool committed(uint8_t mask, tx::transaction_id_t id, tx::Engine &engine) { + debug_assert(mask == Hints::kCre || mask == Hints::kExp, + "Mask must be either kCre or kExp"); // if hints are set, return if id is committed - if (!hint_bits.is_unknown()) return hint_bits.is_committed(); + if (hints_.Get(mask)) return hints_.Get(Hints::kCmt & mask); // if hints are not set consult the commit log - auto is_commited = engine.clog().is_committed(id); - - // committed - if (is_commited) return hints.set_committed(), true; + if (engine.clog().is_committed(id)) { + hints_.Set(Hints::kCmt & mask); + return true; + } // we can't set_aborted hints because of a race-condition that // can occurr when tx.exp gets changed by some transaction.