Skip to content

Commit

Permalink
Implement CDBTransaction and CScopedDBTransaction
Browse files Browse the repository at this point in the history
>>> backports dash@4531f6b896119b2b8f33c03a50cb4d4ee62a6acb

Allows easier commit/rollback handling, especially useful when
AcceptBlock fails and things need to be reverted.
  • Loading branch information
codablock authored and random-zebra committed Apr 11, 2021
1 parent da6a2c1 commit 2496933
Showing 1 changed file with 214 additions and 0 deletions.
214 changes: 214 additions & 0 deletions src/dbwrapper.h
Expand Up @@ -12,6 +12,7 @@
#include "util.h"
#include "version.h"

#include <typeindex>

#include <leveldb/db.h>
#include <leveldb/write_batch.h>
Expand Down Expand Up @@ -309,4 +310,217 @@ class CDBWrapper

};

class CDBTransaction {
private:
CDBWrapper &db;

struct KeyHolder {
virtual ~KeyHolder() = default;
virtual bool Less(const KeyHolder &b) const = 0;
virtual void Erase(CDBBatch &batch) = 0;
};
typedef std::unique_ptr<KeyHolder> KeyHolderPtr;

template <typename K>
struct KeyHolderImpl : KeyHolder {
KeyHolderImpl(const K &_key)
: key(_key) {
}
virtual bool Less(const KeyHolder &b) const {
auto *b2 = dynamic_cast<const KeyHolderImpl<K>*>(&b);
return key < b2->key;
}
virtual void Erase(CDBBatch &batch) {
batch.Erase(key);
}
K key;
};

struct KeyValueHolder {
virtual void Write(CDBBatch &batch) = 0;
};
typedef std::unique_ptr<KeyValueHolder> KeyValueHolderPtr;

template <typename K, typename V>
struct KeyValueHolderImpl : KeyValueHolder {
KeyValueHolderImpl(const KeyHolderImpl<K> &_key, const V &_value)
: key(_key),
value(_value) { }
virtual void Write(CDBBatch &batch) {
batch.Write(key.key, value);
}
const KeyHolderImpl<K> &key;
V value;
};

struct keyCmp {
bool operator()(const KeyHolderPtr &a, const KeyHolderPtr &b) const {
return a->Less(*b);
}
};

typedef std::map<KeyHolderPtr, KeyValueHolderPtr, keyCmp> KeyValueMap;
typedef std::map<std::type_index, KeyValueMap> TypeKeyValueMap;

TypeKeyValueMap writes;
TypeKeyValueMap deletes;

template <typename K>
KeyValueMap *getMapForType(TypeKeyValueMap &m, bool create) {
auto it = m.find(typeid(K));
if (it != m.end()) {
return &it->second;
}
if (!create)
return nullptr;
auto it2 = m.emplace(typeid(K), KeyValueMap());
return &it2.first->second;
}

template <typename K>
KeyValueMap *getWritesMap(bool create) {
return getMapForType<K>(writes, create);
}

template <typename K>
KeyValueMap *getDeletesMap(bool create) {
return getMapForType<K>(deletes, create);
}

public:
CDBTransaction(CDBWrapper &_db) : db(_db) {}

template <typename K, typename V>
void Write(const K& key, const V& value) {
KeyHolderPtr k(new KeyHolderImpl<K>(key));
KeyHolderImpl<K>* k2 = dynamic_cast<KeyHolderImpl<K>*>(k.get());
KeyValueHolderPtr kv(new KeyValueHolderImpl<K,V>(*k2, value));

KeyValueMap *ds = getDeletesMap<K>(false);
if (ds)
ds->erase(k);

KeyValueMap *ws = getWritesMap<K>(true);
ws->erase(k);
ws->emplace(std::make_pair(std::move(k), std::move(kv)));
}

template <typename K, typename V>
bool Read(const K& key, V& value) {
KeyHolderPtr k(new KeyHolderImpl<K>(key));

KeyValueMap *ds = getDeletesMap<K>(false);
if (ds && ds->count(k))
return false;

KeyValueMap *ws = getWritesMap<K>(false);
if (ws) {
KeyValueMap::iterator it = ws->find(k);
if (it != ws->end()) {
auto *impl = dynamic_cast<KeyValueHolderImpl<K, V> *>(it->second.get());
if (!impl)
return false;
value = impl->value;
return true;
}
}

return db.Read(key, value);
}

template <typename K>
bool Exists(const K& key) {
KeyHolderPtr k(new KeyHolderImpl<K>(key));

KeyValueMap *ds = getDeletesMap<K>(false);
if (ds && ds->count(k))
return false;

KeyValueMap *ws = getWritesMap<K>(false);
if (ws && ws->count(k))
return true;

return db.Exists(key);
}

template <typename K>
void Erase(const K& key) {
KeyHolderPtr k(new KeyHolderImpl<K>(key));

KeyValueMap *ws = getWritesMap<K>(false);
if (ws)
ws->erase(k);
KeyValueMap *ds = getDeletesMap<K>(true);
ds->emplace(std::move(k), nullptr);
}

void Clear() {
writes.clear();
deletes.clear();
}

bool Commit() {
CDBBatch batch;
for (auto &p : deletes) {
for (auto &p2 : p.second) {
p2.first->Erase(batch);
}
}
for (auto &p : writes) {
for (auto &p2 : p.second) {
p2.second->Write(batch);
}
}
bool ret = db.WriteBatch(batch, true);
Clear();
return ret;
}

bool IsClean() {
return writes.empty() && deletes.empty();
}
};

class CScopedDBTransaction {
private:
CDBTransaction &dbTransaction;
std::function<void ()> commitHandler;
std::function<void ()> rollbackHandler;
bool didCommitOrRollback{};

public:
CScopedDBTransaction(CDBTransaction &dbTx) : dbTransaction(dbTx) {}
~CScopedDBTransaction() {
if (!didCommitOrRollback)
Rollback();
}
bool Commit() {
assert(!didCommitOrRollback);
didCommitOrRollback = true;
bool result = dbTransaction.Commit();
if (commitHandler)
commitHandler();
return result;
}
void Rollback() {
assert(!didCommitOrRollback);
didCommitOrRollback = true;
dbTransaction.Clear();
if (rollbackHandler)
rollbackHandler();
}

static std::unique_ptr<CScopedDBTransaction> Begin(CDBTransaction &dbTx) {
assert(dbTx.IsClean());
return std::unique_ptr<CScopedDBTransaction>(new CScopedDBTransaction(dbTx));
}

void SetCommitHandler(const std::function<void ()> &h) {
commitHandler = h;
}
void SetRollbackHandler(const std::function<void ()> &h) {
rollbackHandler = h;
}
};

#endif // BITCOIN_DBWRAPPER_H

0 comments on commit 2496933

Please sign in to comment.