submitted all work done on storage and mvcc
This commit is contained in:
parent
ed34e47481
commit
204fdaeb5f
68
api/link_resources.py
Normal file
68
api/link_resources.py
Normal file
@ -0,0 +1,68 @@
|
||||
# this script generates the include.hpp file for restful resources
|
||||
import re
|
||||
import os
|
||||
|
||||
resource_path = 'resources'
|
||||
template_path = 'resources/include.hpp.template'
|
||||
include_path = 'resources/include.hpp'
|
||||
|
||||
# remove the old version of the file if exists
|
||||
if os.path.isfile(include_path):
|
||||
os.remove(include_path)
|
||||
|
||||
|
||||
class Resource(object):
|
||||
""" represents a restful resource class for speedy """
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
|
||||
with open(os.path.join(resource_path, filename)) as f:
|
||||
class_name = re.compile('\s*class\s*(\w+)\s*\:')
|
||||
|
||||
for line in f:
|
||||
result = re.search(class_name, line)
|
||||
|
||||
if result is None:
|
||||
continue
|
||||
|
||||
self.class_name = result.group(1)
|
||||
|
||||
break
|
||||
|
||||
|
||||
def load_resources():
|
||||
return [Resource(f) for f in os.listdir(resource_path)
|
||||
if f.endswith('.hpp')]
|
||||
|
||||
|
||||
def write_includes(file, resources):
|
||||
for filename in [resource.filename for resource in resources]:
|
||||
print 'writing include for', filename
|
||||
file.write('#include "{}"\n'.format(filename))
|
||||
|
||||
|
||||
def write_inits(file, resources):
|
||||
for class_name in [resource.class_name for resource in resources]:
|
||||
print 'writing init for', class_name
|
||||
file.write(' insert<{}>(app);\n'.format(class_name))
|
||||
|
||||
|
||||
def make_include_file():
|
||||
resources = load_resources()
|
||||
|
||||
with open(template_path, 'r') as ftemplate:
|
||||
with open(include_path, 'w') as finclude:
|
||||
for line in ftemplate:
|
||||
if '<INCLUDE>' in line:
|
||||
write_includes(finclude, resources)
|
||||
continue
|
||||
|
||||
if '<INIT>' in line:
|
||||
write_inits(finclude, resources)
|
||||
continue
|
||||
|
||||
finclude.write(line)
|
||||
|
||||
if __name__ == '__main__':
|
||||
make_include_file()
|
23
api/resources/animal.hpp
Normal file
23
api/resources/animal.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef MEMGRAPH_ANIMAL_HPP
|
||||
#define MEMGRAPH_ANIMAL_HPP
|
||||
|
||||
#include "speedy/speedy.hpp"
|
||||
#include "api/restful/resource.hpp"
|
||||
|
||||
class Animal : public api::Resource<Animal, api::GET, api::POST>
|
||||
{
|
||||
public:
|
||||
Animal(speedy::Speedy& app) : Resource(app, "/animal") {}
|
||||
|
||||
void get(http::Request& req, http::Response& res)
|
||||
{
|
||||
return res.send("Ok, here is a Dog");
|
||||
}
|
||||
|
||||
void post(http::Request& req, http::Response& res)
|
||||
{
|
||||
return res.send("Oh, you gave me an animal?");
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
33
api/resources/include.hpp
Normal file
33
api/resources/include.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
/** @file include.hpp
|
||||
* @brief Links all restful resources to the application
|
||||
*
|
||||
* This file is autogenerated by the python script link_resources.py
|
||||
*
|
||||
* YOU SHOULD NOT EDIT THIS FILE MANUALLY!
|
||||
*/
|
||||
|
||||
#ifndef MEMGRAPH_API_RESOURCES_INCLUDE_HPP
|
||||
#define MEMGRAPH_API_RESOURCES_INCLUDE_HPP
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "api/restful/resource.hpp"
|
||||
#include "speedy/speedy.hpp"
|
||||
|
||||
#include "animal.hpp"
|
||||
|
||||
static std::list<std::unique_ptr<api::RestfulResource>> resources;
|
||||
|
||||
template <class T>
|
||||
void insert(speedy::Speedy& app)
|
||||
{
|
||||
resources.push_back(std::unique_ptr<api::RestfulResource>(new T(app)));
|
||||
}
|
||||
|
||||
void init(speedy::Speedy& app)
|
||||
{
|
||||
insert<Animal>(app);
|
||||
}
|
||||
|
||||
#endif
|
33
api/resources/include.hpp.template
Normal file
33
api/resources/include.hpp.template
Normal file
@ -0,0 +1,33 @@
|
||||
/** @file include.hpp
|
||||
* @brief Links all restful resources to the application
|
||||
*
|
||||
* This file is autogenerated by the python script link_resources.py
|
||||
*
|
||||
* YOU SHOULD NOT EDIT THIS FILE MANUALLY!
|
||||
*/
|
||||
|
||||
#ifndef MEMGRAPH_API_RESOURCES_INCLUDE_HPP
|
||||
#define MEMGRAPH_API_RESOURCES_INCLUDE_HPP
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "api/restful/resource.hpp"
|
||||
#include "speedy/speedy.hpp"
|
||||
|
||||
<INCLUDE>
|
||||
|
||||
static std::list<std::unique_ptr<api::RestfulResource>> resources;
|
||||
|
||||
template <class T>
|
||||
void insert(speedy::Speedy& app)
|
||||
{
|
||||
resources.push_back(std::unique_ptr<api::RestfulResource>(new T(app)));
|
||||
}
|
||||
|
||||
void init(speedy::Speedy& app)
|
||||
{
|
||||
<INIT>
|
||||
}
|
||||
|
||||
#endif
|
@ -114,6 +114,8 @@ struct Methods<T, M> : public Method<T, M>
|
||||
|
||||
}
|
||||
|
||||
struct RestfulResource {};
|
||||
|
||||
/** @brief Represents a restful resource
|
||||
*
|
||||
* Automatically registers get, put, post, del... methods inside the derived
|
||||
@ -128,7 +130,7 @@ struct Methods<T, M> : public Method<T, M>
|
||||
* @tparam Ms... HTTP methods to register for this resource (GET, POST...)
|
||||
*/
|
||||
template <class T, class... Ms>
|
||||
class Resource : public detail::Methods<T, Ms...>
|
||||
class Resource : public detail::Methods<T, Ms...>, public RestfulResource
|
||||
{
|
||||
public:
|
||||
Resource(speedy::Speedy& app, const std::string& path)
|
20
api/test.cpp
20
api/test.cpp
@ -1,30 +1,14 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "speedy/speedy.hpp"
|
||||
#include "resource.hpp"
|
||||
|
||||
class Animal : public api::Resource<Animal, api::GET, api::POST>
|
||||
{
|
||||
public:
|
||||
Animal(speedy::Speedy& app) : Resource(app, "/animal") {}
|
||||
|
||||
void get(http::Request& req, http::Response& res)
|
||||
{
|
||||
return res.send("Ok, here is a Dog");
|
||||
}
|
||||
|
||||
void post(http::Request& req, http::Response& res)
|
||||
{
|
||||
return res.send("Oh, you gave me an animal?");
|
||||
}
|
||||
};
|
||||
#include "resources/include.hpp"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uv::UvLoop loop;
|
||||
speedy::Speedy app(loop);
|
||||
|
||||
auto animal = Animal(app);
|
||||
init(app);
|
||||
|
||||
http::Ipv4 ip("0.0.0.0", 3400);
|
||||
app.listen(ip);
|
||||
|
@ -2,11 +2,7 @@
|
||||
#define MEMGRAPH_DATA_STRUCTURES_BITSET_DYNAMIC_BITSET_HPP
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "threading/sync/lockable.hpp"
|
||||
#include "threading/sync/spinlock.hpp"
|
||||
@ -140,7 +136,7 @@ private:
|
||||
// the next chunk does not exist and we need it. take an exclusive
|
||||
// lock to prevent others that also want to create a new chunk
|
||||
// from creating it
|
||||
auto guard = acquire();
|
||||
auto guard = acquire_unique();
|
||||
|
||||
// double-check locking. if the chunk exists now, some other thread
|
||||
// has just created it, continue searching for my chunk
|
||||
|
@ -136,7 +136,7 @@ public:
|
||||
// we only care about push_front and iterator performance so we can
|
||||
// tradeoff some remove speed for better reads and inserts. remove is
|
||||
// used exclusively by the GC thread(s) so it can be slower
|
||||
auto guard = acquire();
|
||||
auto guard = acquire_unique();
|
||||
|
||||
// even though concurrent removes are synchronized, we need to worry
|
||||
// about concurrent reads (solved by using atomics) and concurrent
|
||||
|
@ -6,42 +6,39 @@
|
||||
#include "threading/sync/lockable.hpp"
|
||||
#include "threading/sync/spinlock.hpp"
|
||||
|
||||
namespace spinlock
|
||||
{
|
||||
|
||||
template <class T>
|
||||
class Queue : Lockable<SpinLock>
|
||||
class SlQueue : Lockable<SpinLock>
|
||||
{
|
||||
public:
|
||||
|
||||
template <class... Args>
|
||||
void emplace(Args&&... args)
|
||||
{
|
||||
auto guard = acquire();
|
||||
auto guard = acquire_unique();
|
||||
queue.emplace(args...);
|
||||
}
|
||||
|
||||
void push(const T& item)
|
||||
{
|
||||
auto guard = acquire();
|
||||
auto guard = acquire_unique();
|
||||
queue.push(item);
|
||||
}
|
||||
|
||||
T front()
|
||||
{
|
||||
auto guard = acquire();
|
||||
auto guard = acquire_unique();
|
||||
return queue.front();
|
||||
}
|
||||
|
||||
void pop()
|
||||
{
|
||||
auto guard = acquire();
|
||||
auto guard = acquire_unique();
|
||||
queue.pop();
|
||||
}
|
||||
|
||||
bool pop(T& item)
|
||||
{
|
||||
auto guard = acquire();
|
||||
auto guard = acquire_unique();
|
||||
if(queue.empty())
|
||||
return false;
|
||||
|
||||
@ -52,13 +49,13 @@ public:
|
||||
|
||||
bool empty()
|
||||
{
|
||||
auto guard = acquire();
|
||||
auto guard = acquire_unique();
|
||||
return queue.empty();
|
||||
}
|
||||
|
||||
size_t size()
|
||||
{
|
||||
auto guard = acquire();
|
||||
auto guard = acquire_unique();
|
||||
return queue.size();
|
||||
}
|
||||
|
||||
@ -66,6 +63,4 @@ private:
|
||||
std::queue<T> queue;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
61
data_structures/skiplist/lockfree_skiplist.hpp
Normal file
61
data_structures/skiplist/lockfree_skiplist.hpp
Normal file
@ -0,0 +1,61 @@
|
||||
#ifndef MEMGRAPH_DATA_STRUCTURES_SKIPLIST_LOCKFREE_SKIPLIST_HPP
|
||||
#define MEMGRAPH_DATA_STRUCTURES_SKIPLIST_LOCKFREE_SKIPLIST_HPP
|
||||
|
||||
#include <cstdlib>
|
||||
#include <atomic>
|
||||
|
||||
#include "utils/mark_ref.hpp"
|
||||
|
||||
namespace lockfree
|
||||
{
|
||||
|
||||
template <class K, class T>
|
||||
class SkipList
|
||||
{
|
||||
public:
|
||||
struct Node
|
||||
{
|
||||
using ref_t = MarkRef<Node>;
|
||||
|
||||
K* key;
|
||||
T* item;
|
||||
|
||||
const uint8_t height;
|
||||
|
||||
static Node* create(uint8_t height, K* key, T* item)
|
||||
{
|
||||
auto size = sizeof(Node) + height * sizeof(std::atomic<ref_t>);
|
||||
auto node = static_cast<Node*>(std::malloc(size));
|
||||
return new (node) Node(height, key, item);
|
||||
}
|
||||
|
||||
static void destroy(Node* node)
|
||||
{
|
||||
node->~SkipNode();
|
||||
free(node);
|
||||
}
|
||||
|
||||
private:
|
||||
Node(uint8_t height, K* key, T* item)
|
||||
: key(key), item(item), height(height)
|
||||
{
|
||||
for(uint8_t i = 0; i < height; ++i)
|
||||
new (&tower[i]) std::atomic<ref_t>(nullptr);
|
||||
}
|
||||
|
||||
// this creates an array of the size zero. we can't put any sensible
|
||||
// value here since we don't know what size it will be untill the
|
||||
// node is allocated. we could make it a SkipNode** but then we would
|
||||
// have two memory allocations, one for node and one for the forward
|
||||
// list. this way we avoid expensive malloc/free calls and also cache
|
||||
// thrashing when following a pointer on the heap
|
||||
std::atomic<ref_t> tower[0];
|
||||
};
|
||||
|
||||
//void list_search(const K& key,
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -28,7 +28,7 @@ public:
|
||||
|
||||
~SkipList()
|
||||
{
|
||||
for(Node* current = header.load(std::memory_order_relaxed); current;)
|
||||
for(Node* current = header.load(); current;)
|
||||
{
|
||||
Node* next = current->forward(0);
|
||||
Node::destroy(current);
|
||||
@ -38,7 +38,7 @@ public:
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return size_.load(std::memory_order_relaxed);
|
||||
return size_.load();
|
||||
}
|
||||
|
||||
uint8_t height() const
|
||||
@ -60,7 +60,7 @@ public:
|
||||
|
||||
size_t increment_size(size_t delta)
|
||||
{
|
||||
return size_.fetch_add(delta, std::memory_order_relaxed) + delta;
|
||||
return size_.fetch_add(delta) + delta;
|
||||
}
|
||||
|
||||
int find_path(Node* from,
|
||||
@ -92,7 +92,7 @@ public:
|
||||
|
||||
Node* find(const K* const key)
|
||||
{
|
||||
Node* pred = header.load(std::memory_order_consume);
|
||||
Node* pred = header.load();
|
||||
Node* node = nullptr;
|
||||
|
||||
uint8_t level = pred->height;
|
||||
@ -150,7 +150,7 @@ public:
|
||||
|
||||
while(true)
|
||||
{
|
||||
auto head = header.load(std::memory_order_consume);
|
||||
auto head = header.load();
|
||||
auto lfound = find_path(head, MAX_HEIGHT - 1, key, preds, succs);
|
||||
|
||||
if(lfound != -1)
|
||||
@ -215,7 +215,7 @@ public:
|
||||
|
||||
while(true)
|
||||
{
|
||||
auto head = header.load(std::memory_order_consume);
|
||||
auto head = header.load();
|
||||
auto lfound = find_path(head, MAX_HEIGHT - 1, key, preds, succs);
|
||||
|
||||
if(!marked && (lfound == -1 || !ok_delete(succs[lfound], lfound)))
|
||||
|
@ -3,15 +3,14 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "utils/sync/spinlock.hpp"
|
||||
#include "threading/sync/spinlock.hpp"
|
||||
|
||||
template <class K, class T>
|
||||
class SlRbTree
|
||||
class SlRbTree : Lockable<SpinLock>
|
||||
{
|
||||
public:
|
||||
|
||||
private:
|
||||
SpinLock lock;
|
||||
std::map<K, T> tree;
|
||||
};
|
||||
|
||||
|
@ -29,7 +29,6 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
SpinLock lock;
|
||||
std::stack<T> stack;
|
||||
};
|
||||
|
||||
|
20
database/db.hpp
Normal file
20
database/db.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef MEMGRAPH_DATABASE_DB_HPP
|
||||
#define MEMGRAPH_DATABASE_DB_HPP
|
||||
|
||||
#include "transactions/transaction_engine.hpp"
|
||||
#include "transactions/commit_log.hpp"
|
||||
|
||||
class Db
|
||||
{
|
||||
public:
|
||||
static Db& get()
|
||||
{
|
||||
static Db db;
|
||||
return db;
|
||||
}
|
||||
|
||||
tx::CommitLog commit_log;
|
||||
tx::TransactionEngine transaction_engine;
|
||||
};
|
||||
|
||||
#endif
|
77
ioctest.cpp
77
ioctest.cpp
@ -1,77 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "utils/ioc/container.hpp"
|
||||
|
||||
struct A
|
||||
{
|
||||
A()
|
||||
{
|
||||
std::cout << "Constructor of A" << std::endl;
|
||||
}
|
||||
|
||||
int a = 3;
|
||||
};
|
||||
|
||||
struct C;
|
||||
|
||||
struct B
|
||||
{
|
||||
B(std::shared_ptr<A>&& a, std::shared_ptr<C>&& c)
|
||||
: a(a), c(c)
|
||||
{
|
||||
std::cout << "Constructor of B" << std::endl;
|
||||
}
|
||||
|
||||
int b = 4;
|
||||
|
||||
std::shared_ptr<A> a;
|
||||
std::shared_ptr<C> c;
|
||||
};
|
||||
|
||||
struct C
|
||||
{
|
||||
C(int c) : c(c)
|
||||
{
|
||||
std::cout << "Constructor of C" << std::endl;
|
||||
}
|
||||
|
||||
int c;
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
ioc::Container container;
|
||||
|
||||
// register a singleton class A
|
||||
auto a = container.singleton<A>();
|
||||
std::cout << a->a << std::endl; // should print 3
|
||||
|
||||
// register a factory function that makes C
|
||||
container.factory<C>([]() {
|
||||
return std::make_shared<C>(5);
|
||||
});
|
||||
|
||||
// try to resolve A
|
||||
auto aa = container.resolve<A>();
|
||||
std::cout << aa->a << std::endl; // should print 3
|
||||
|
||||
// register a singleton class B with dependencies A and C
|
||||
// (A will be resolved as an existing singleton and C will
|
||||
// be created by the factory function defined above)
|
||||
auto b = container.singleton<B, A, C>();
|
||||
std::cout << b->b << std::endl; // should print 4
|
||||
std::cout << b->a->a << std::endl; // should print 3
|
||||
std::cout << b->c->c << std::endl; // should print 5
|
||||
|
||||
// try to resolve B
|
||||
auto bb = container.resolve<B>();
|
||||
std::cout << bb->b << std::endl; // should print 4
|
||||
|
||||
// try to resolve C
|
||||
// (will be created as a new instance by the factory
|
||||
// function defined above)
|
||||
auto c = container.resolve<C>();
|
||||
std::cout << c->c << std::endl; // should print 5
|
||||
|
||||
return 0;
|
||||
};
|
29
memory/literals.hpp
Normal file
29
memory/literals.hpp
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef MEMGRAPH_MEMORY_LITERALS_HPP
|
||||
#define MEMGRAPH_MEMORY_LITERALS_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace memory
|
||||
{
|
||||
namespace literals
|
||||
{
|
||||
|
||||
constexpr unsigned long long operator"" _GB(unsigned long long gb)
|
||||
{
|
||||
return 1024 * 1024 * 1024 * gb;
|
||||
}
|
||||
|
||||
constexpr unsigned long long operator"" _MB(unsigned long long mb)
|
||||
{
|
||||
return 1024 * 1024 * mb;
|
||||
}
|
||||
|
||||
constexpr unsigned long long operator"" _kB(unsigned long long kb)
|
||||
{
|
||||
return 1024 * kb;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include "threading/sync/lockable.hpp"
|
||||
|
||||
#include "transaction.hpp"
|
||||
#include "transactions/transaction.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
namespace mvcc
|
||||
@ -11,11 +11,10 @@ namespace mvcc
|
||||
|
||||
template <class T>
|
||||
class Atom : public Version<T>,
|
||||
public Lockable<>
|
||||
public Lockable<SpinLock>
|
||||
{
|
||||
public:
|
||||
Atom(uint64_t id, T* first)
|
||||
: Version<T>(first), id(id)
|
||||
Atom(uint64_t id, T* first) : Version<T>(first), id(id)
|
||||
{
|
||||
// it's illegal that the first version is nullptr. there should be at
|
||||
// least one version of a record
|
||||
@ -29,27 +28,17 @@ public:
|
||||
|
||||
// inspects the record change history and returns the record version visible
|
||||
// to the current transaction if it exists, otherwise it returns nullptr
|
||||
T* latest_visible(const Transaction& t)
|
||||
T* latest_visible(const tx::Transaction& t)
|
||||
{
|
||||
return first()->latest_visible(t);
|
||||
}
|
||||
|
||||
// inspects the record change history and returns the newest available
|
||||
// version from all transactions
|
||||
T& newest_available()
|
||||
{
|
||||
T* record = this->newer(), newer = this->newer();
|
||||
|
||||
while(newer != nullptr)
|
||||
record = newer, newer = record->newer();
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
// every record has a unique id. 2^64 = 1.8 x 10^19. that should be enough
|
||||
// for a looong time :) but keep in mind that some vacuuming would be nice
|
||||
// to reuse indices for deleted nodes.
|
||||
uint64_t id;
|
||||
|
||||
std::atomic<Atom<T>*> next;
|
||||
};
|
||||
|
||||
}
|
||||
|
115
mvcc/hints.hpp
Normal file
115
mvcc/hints.hpp
Normal file
@ -0,0 +1,115 @@
|
||||
#ifndef MEMGRAPH_MVCC_HINTS_HPP
|
||||
#define MEMGRAPH_MVCC_HINTS_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "transactions/commit_log.hpp"
|
||||
|
||||
namespace mvcc
|
||||
{
|
||||
|
||||
// known committed and known aborted for both xmax and xmin
|
||||
// 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 {
|
||||
MIN_CMT = 1, // XX01
|
||||
MIN_ABT = 1 << 1, // XX10
|
||||
MAX_CMT = 1 << 2, // 01XX
|
||||
MAX_ABT = 1 << 3 // 10XX
|
||||
};
|
||||
|
||||
template <Flags COMMITTED, Flags ABORTED>
|
||||
class TxHints
|
||||
{
|
||||
using type = TxHints<COMMITTED, ABORTED>;
|
||||
|
||||
public:
|
||||
TxHints(std::atomic<uint8_t>& 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()
|
||||
{
|
||||
bits.fetch_or(COMMITTED);
|
||||
}
|
||||
|
||||
void set_aborted()
|
||||
{
|
||||
bits.fetch_or(ABORTED);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<uint8_t>& bits;
|
||||
};
|
||||
|
||||
struct Min : public TxHints<MIN_CMT, MIN_ABT>
|
||||
{
|
||||
using TxHints::TxHints;
|
||||
};
|
||||
|
||||
struct Max : public TxHints<MAX_CMT, MAX_ABT>
|
||||
{
|
||||
using TxHints::TxHints;
|
||||
};
|
||||
|
||||
public:
|
||||
Hints() : min(bits), max(bits)
|
||||
{
|
||||
assert(bits.is_lock_free());
|
||||
}
|
||||
|
||||
union HintBits
|
||||
{
|
||||
uint8_t bits;
|
||||
|
||||
Min::Value min;
|
||||
Max::Value max;
|
||||
};
|
||||
|
||||
HintBits load(std::memory_order order = std::memory_order_seq_cst)
|
||||
{
|
||||
return HintBits { bits.load(order) };
|
||||
}
|
||||
|
||||
Min min;
|
||||
Max max;
|
||||
|
||||
std::atomic<uint8_t> bits { 0 };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -3,9 +3,12 @@
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "transaction.hpp"
|
||||
#include "transactions/transaction.hpp"
|
||||
#include "transactions/commit_log.hpp"
|
||||
|
||||
#include "minmax.hpp"
|
||||
#include "version.hpp"
|
||||
#include "hints.hpp"
|
||||
|
||||
// the mvcc implementation used here is very much like postgresql's
|
||||
// more info: https://momjian.us/main/writings/pgsql/mvcc.pdf
|
||||
@ -17,7 +20,7 @@ template <class T>
|
||||
class Mvcc : public Version<T>
|
||||
{
|
||||
public:
|
||||
Mvcc() {}
|
||||
Mvcc() = default;
|
||||
|
||||
// tx.min is the id of the transaction that created the record
|
||||
// and tx.max is the id of the transaction that deleted the record
|
||||
@ -32,7 +35,7 @@ public:
|
||||
MinMax<uint8_t> cmd;
|
||||
|
||||
// check if this record is visible to the transaction t
|
||||
bool visible(const Transaction& t)
|
||||
bool visible(const tx::Transaction& t)
|
||||
{
|
||||
// TODO check if the record was created by a transaction that has been
|
||||
// aborted. one might implement this by checking the hints in mvcc
|
||||
@ -42,30 +45,30 @@ public:
|
||||
// if you think they're not, you're wrong, and you should think about it
|
||||
// again. i know, it happened to me.
|
||||
|
||||
return ((tx.min() == t.id && // inserted by the current transaction
|
||||
cmd.min() < t.cid && // before this command, and
|
||||
(tx.max() == 0 || // the row has not been deleted, or
|
||||
(tx.max() == t.id && // it was deleted by the current
|
||||
// transaction
|
||||
cmd.max() >= t.cid))) // but not before this command,
|
||||
|| // or
|
||||
(t.committed(tx.min()) && // the record was inserted by a
|
||||
// committed transaction, and
|
||||
(tx.max() == 0 || // the record has not been deleted, or
|
||||
(tx.max() == t.id && // the row is being deleted by this
|
||||
// transaction
|
||||
cmd.max() >= t.cid) || // but it's not deleted "yet", or
|
||||
(tx.max() != t.id && // the row was deleted by another
|
||||
// transaction
|
||||
!t.committed(tx.max()) // that has not been committed
|
||||
return ((tx.min() == t.id && // inserted by the current transaction
|
||||
cmd.min() < t.cid && // before this command, and
|
||||
(tx.max() == 0 || // the row has not been deleted, or
|
||||
(tx.max() == t.id && // it was deleted by the current
|
||||
// transaction
|
||||
cmd.max() >= t.cid))) // but not before this command,
|
||||
|| // or
|
||||
(min_committed(tx.min(), t) && // the record was inserted by a
|
||||
// committed transaction, and
|
||||
(tx.max() == 0 || // the record has not been deleted, or
|
||||
(tx.max() == t.id && // the row is being deleted by this
|
||||
// transaction
|
||||
cmd.max() >= t.cid) || // but it's not deleted "yet", or
|
||||
(tx.max() != t.id && // the row was deleted by another
|
||||
// transaction
|
||||
!max_committed(tx.max(), t) // that has not been committed
|
||||
))));
|
||||
}
|
||||
|
||||
// inspects the record change history and returns the record version visible
|
||||
// to the current transaction if it exists, otherwise it returns nullptr
|
||||
T* latest_visible(const Transaction& t)
|
||||
T* latest_visible(const tx::Transaction& t)
|
||||
{
|
||||
T* record = this, newer = this->newer();
|
||||
T* record = static_cast<T*>(this), *newer = this->newer();
|
||||
|
||||
// move down through the versions of the nodes until you find the first
|
||||
// one visible to this transaction. if no visible records are found,
|
||||
@ -76,25 +79,59 @@ public:
|
||||
return record;
|
||||
}
|
||||
|
||||
void mark_created(const Transaction& t)
|
||||
void mark_created(const tx::Transaction& t)
|
||||
{
|
||||
tx.min(t.id);
|
||||
cmd.min(t.cid);
|
||||
}
|
||||
|
||||
void mark_deleted(const Transaction& t)
|
||||
void mark_deleted(const tx::Transaction& t)
|
||||
{
|
||||
tx.max(t.id);
|
||||
cmd.max(t.cid);
|
||||
}
|
||||
|
||||
protected:
|
||||
// known committed and known aborted for both xmax and xmin
|
||||
// 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
|
||||
std::atomic<uint8_t> hints;
|
||||
Hints hints;
|
||||
|
||||
bool max_committed(uint64_t id, const tx::Transaction& t)
|
||||
{
|
||||
return committed(hints.max, id, t);
|
||||
}
|
||||
|
||||
bool min_committed(uint64_t id, const tx::Transaction& t)
|
||||
{
|
||||
return committed(hints.min, id, t);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
bool committed(U& hints, uint64_t id, const tx::Transaction& t)
|
||||
{
|
||||
auto hint_bits = hints.load();
|
||||
|
||||
// if hints are set, return if xid is committed
|
||||
if(!hint_bits.is_unknown())
|
||||
return hint_bits.is_committed();
|
||||
|
||||
// if hints are not set:
|
||||
// - the creating transaction is still in progress (examine snapshot)
|
||||
if(t.snapshot.is_active(id))
|
||||
return false;
|
||||
|
||||
// - you are the first one to check since it ended, consult commit log
|
||||
auto& clog = tx::CommitLog::get();
|
||||
auto info = clog.fetch_info(id);
|
||||
|
||||
if(info.is_committed())
|
||||
{
|
||||
hints.set_committed();
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(info.is_aborted());
|
||||
hints.set_aborted();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
17
mvcc/mvcc_error.hpp
Normal file
17
mvcc/mvcc_error.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef MEMGRAPH_MVCC_MVCC_ERROR_HPP
|
||||
#define MEMGRAPH_MVCC_MVCC_ERROR_HPP
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace mvcc
|
||||
{
|
||||
|
||||
class MvccError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
using runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,10 +1,9 @@
|
||||
#ifndef MEMGRAPH_STORAGE_MVCC_STORE_HPP
|
||||
#define MEMGRAPH_STORAGE_MVCC_STORE_HPP
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "mvcc/transaction.hpp"
|
||||
#include "transactions/transaction.hpp"
|
||||
#include "mvcc/atom.hpp"
|
||||
#include "mvcc/mvcc_error.hpp"
|
||||
|
||||
#include "data_structures/list/lockfree_list.hpp"
|
||||
#include "utils/counters/atomic_counter.hpp"
|
||||
@ -15,40 +14,57 @@
|
||||
namespace mvcc
|
||||
{
|
||||
|
||||
class MvccError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
using runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class MvccStore
|
||||
{
|
||||
using list_t = lockfree::List<Atom<T>>;
|
||||
|
||||
public:
|
||||
using iterator = typename list_t::iterator;
|
||||
using read_iterator = typename list_t::read_iterator;
|
||||
using read_write_iterator = typename list_t::read_write_iterator;
|
||||
|
||||
MvccStore() : counter(0) {}
|
||||
|
||||
iterator insert(const Transaction& t)
|
||||
read_iterator begin()
|
||||
{
|
||||
auto record = new T();
|
||||
|
||||
record->mark_created(t);
|
||||
|
||||
return data.push_front(Atom<T>(counter.next(), record));
|
||||
return data.begin();
|
||||
}
|
||||
|
||||
T* update(Atom<T>& atom, T& record, const Transaction& t)
|
||||
read_write_iterator rw_begin()
|
||||
{
|
||||
return data.rw_begin();
|
||||
}
|
||||
|
||||
Atom<T>* insert(const tx::Transaction& t)
|
||||
{
|
||||
// create a first version of the record
|
||||
auto record = new T();
|
||||
|
||||
// mark the record as created by the transaction t
|
||||
record->mark_created(t);
|
||||
|
||||
// create an Atom to put in the list
|
||||
auto atom = new Atom<T>(counter.next(), record);
|
||||
|
||||
// put the atom with the record to the linked list
|
||||
data.push_front(atom);
|
||||
|
||||
return atom;
|
||||
}
|
||||
|
||||
T* update(Atom<T>& atom, T& record, const tx::Transaction& t)
|
||||
{
|
||||
auto guard = atom.acquire();
|
||||
|
||||
// if xmax is not zero, that means there is a newer version of this
|
||||
// record or it has been deleted. we cannot do anything here
|
||||
// record or it has been deleted. we cannot do anything here until
|
||||
// we implement some more intelligent locking mechanisms
|
||||
if(record.tx.max())
|
||||
throw MvccError("can't serialize due to concurrent operation(s)");
|
||||
|
||||
assert(atom.latest_visible(t) == &record);
|
||||
assert(atom.latest_visible(t) == record.latest_visible(t));
|
||||
|
||||
// make a new version
|
||||
auto updated = new T();
|
||||
*updated = *record;
|
||||
@ -63,7 +79,7 @@ public:
|
||||
return updated;
|
||||
}
|
||||
|
||||
void remove(Atom<T>& atom, T& record, const Transaction& t)
|
||||
void remove(Atom<T>& atom, T& record, const tx::Transaction& t)
|
||||
{
|
||||
auto guard = atom.acquire();
|
||||
|
||||
|
@ -12,18 +12,23 @@ class Version
|
||||
public:
|
||||
Version() : versions(nullptr) {}
|
||||
|
||||
~Version()
|
||||
{
|
||||
delete versions.load();
|
||||
}
|
||||
|
||||
Version(T* value) : versions(value) {}
|
||||
|
||||
// return a pointer to a newer version stored in this record
|
||||
T* newer()
|
||||
{
|
||||
return versions.load(std::memory_order_relaxed);
|
||||
return versions.load();
|
||||
}
|
||||
|
||||
// set a newer version of this record
|
||||
void newer(T* value)
|
||||
{
|
||||
versions.store(value, std::memory_order_relaxed);
|
||||
versions.store(value);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -99,6 +99,7 @@ public:
|
||||
|
||||
R3(R3&& other)
|
||||
{
|
||||
this->routes = std::move(other.routes);
|
||||
this->root = other.root;
|
||||
other.root = nullptr;
|
||||
}
|
||||
|
@ -20,7 +20,10 @@ namespace speedy
|
||||
class Speedy
|
||||
{
|
||||
public:
|
||||
Speedy(uv::UvLoop& loop) : server(loop), router(100) {}
|
||||
using sptr = std::shared_ptr<Speedy>;
|
||||
|
||||
Speedy(uv::UvLoop& loop, size_t capacity = 100)
|
||||
: server(loop), router(capacity) {}
|
||||
|
||||
Speedy(Speedy&) = delete;
|
||||
Speedy(Speedy&&) = delete;
|
||||
|
94
speedy/speedy.inl
Normal file
94
speedy/speedy.inl
Normal file
@ -0,0 +1,94 @@
|
||||
#ifndef MEMGRAPH_SPEEDY_INL
|
||||
#define MEMGRAPH_SPEEDY_INL
|
||||
|
||||
#include "speedy.hpp"
|
||||
|
||||
namespace speedy
|
||||
{
|
||||
|
||||
int r3_request_method(http::Method method)
|
||||
{
|
||||
switch (method) {
|
||||
case http::Method::GET: return METHOD_GET;
|
||||
case http::Method::POST: return METHOD_POST;
|
||||
case http::Method::PUT: return METHOD_PUT;
|
||||
case http::Method::DELETE: return METHOD_DELETE;
|
||||
case http::Method::HEAD: return METHOD_HEAD;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: better implementation
|
||||
|
||||
Speedy::Speedy(uv::UvLoop& loop, const http::Ipv4& ip) : server(loop), ip(ip)
|
||||
{
|
||||
n = r3_tree_create(100);
|
||||
}
|
||||
|
||||
void Speedy::store_callback(int method,
|
||||
const std::string &path,
|
||||
http::request_cb_t callback)
|
||||
{
|
||||
callbacks.push_back(callback);
|
||||
void *ptr = malloc(sizeof(uint));
|
||||
*((uint *)ptr) = callbacks.size() - 1;
|
||||
r3_tree_insert_routel(n, method, path.c_str(), path.size(), ptr);
|
||||
}
|
||||
|
||||
void Speedy::get(const std::string &path, http::request_cb_t callback)
|
||||
{
|
||||
store_callback(METHOD_GET, path, callback);
|
||||
|
||||
// TODO: something like this
|
||||
// this solution doesn't work, currenlty I don't know why
|
||||
// callbacks.push_back(callback)
|
||||
// r3_tree_insert_pathl(n, path.c_str(), path.size(), &callbacks.back());
|
||||
}
|
||||
|
||||
void Speedy::post(const std::string &path, http::request_cb_t callback)
|
||||
{
|
||||
store_callback(METHOD_POST, path, callback);
|
||||
}
|
||||
|
||||
void Speedy::put(const std::string &path, http::request_cb_t callback)
|
||||
{
|
||||
store_callback(METHOD_PUT, path, callback);
|
||||
}
|
||||
|
||||
void Speedy::del(const std::string &path, http::request_cb_t callback)
|
||||
{
|
||||
store_callback(METHOD_DELETE, path, callback);
|
||||
}
|
||||
|
||||
void Speedy::listen()
|
||||
{
|
||||
char *errstr = NULL;
|
||||
int err = r3_tree_compile(n, &errstr);
|
||||
if (err) {
|
||||
std::cout << "R3 compile error" << std::endl;
|
||||
}
|
||||
|
||||
server.listen(ip, [this](http::Request& req, http::Response& res) {
|
||||
auto url = req.url;
|
||||
auto c_url = url.c_str();
|
||||
match_entry *entry = match_entry_create(c_url);
|
||||
entry->request_method = r3_request_method(req.method);
|
||||
route *r = r3_tree_match_route(this->n, entry);
|
||||
match_entry_free(entry);
|
||||
if (r) {
|
||||
int index = *((int *)r->data);
|
||||
auto callback = this->callbacks[index];
|
||||
callback(req, res);
|
||||
// TODO: and something like this
|
||||
// auto callback = *reinterpret_cast<http::request_cb_t*>(n->data);
|
||||
// callback(req, res);
|
||||
} else {
|
||||
res.send("Not found");
|
||||
}
|
||||
});
|
||||
|
||||
std::cout << "Server is UP" << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "mvcc/atom.hpp"
|
||||
#include "mvcc/store.hpp"
|
||||
#include "mvcc/mvcc_error.hpp"
|
||||
|
||||
#include "vertex.hpp"
|
||||
#include "edge.hpp"
|
||||
@ -17,13 +18,24 @@ class Graph
|
||||
public:
|
||||
Graph() {}
|
||||
|
||||
EdgeStore::iterator connect(Vertex a, Vertex b, const Transaction& t)
|
||||
mvcc::Atom<Edge>* connect(mvcc::Atom<Vertex>& atom_a, Vertex& a,
|
||||
mvcc::Atom<Vertex>& atom_b, Vertex& b,
|
||||
const tx::Transaction& t)
|
||||
{
|
||||
auto it = edges.insert(t);
|
||||
// try to lock A
|
||||
auto guard_a = atom_a.acquire_unique();
|
||||
|
||||
it->
|
||||
|
||||
return it;
|
||||
if(a.tx.max())
|
||||
throw mvcc::MvccError("can't serialize due to\
|
||||
concurrent operation(s)");
|
||||
|
||||
auto guard_b = atom_b.acquire_unique();
|
||||
|
||||
if(b.tx.max())
|
||||
throw mvcc::MvccError("can't serialize due to\
|
||||
concurrent operation(s)");
|
||||
|
||||
return edges.insert(t);
|
||||
}
|
||||
|
||||
VertexStore vertices;
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <map>
|
||||
|
||||
#include "property.hpp"
|
||||
#include "string.hpp"
|
||||
|
||||
namespace model
|
||||
{
|
||||
@ -43,9 +44,9 @@ public:
|
||||
buffer += '"'; buffer += kvp.first; buffer += "\":";
|
||||
kvp.second->dump(buffer); buffer += ',';
|
||||
}
|
||||
|
||||
buffer.pop_back(); // erase last comma
|
||||
buffer += '}';
|
||||
|
||||
// replace last redundant comma with }
|
||||
buffer.back() = '}';
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -1,6 +1,7 @@
|
||||
#ifndef MEMGRAPH_STORAGE_RECORD_HPP
|
||||
#define MEMGRAPH_STORAGE_RECORD_HPP
|
||||
|
||||
#include <ostream>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
|
@ -12,4 +12,14 @@ struct Vertex : public Record<Vertex>
|
||||
std::vector<Edge*> out;
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, Vertex& record)
|
||||
{
|
||||
std::string props;
|
||||
record.properties.dump(props);
|
||||
|
||||
return stream << "Vertex"
|
||||
<< "(xmin = " << record.tx.min()
|
||||
<< ", xmax = " << record.tx.max()
|
||||
<< "): " << props;
|
||||
}
|
||||
#endif
|
||||
|
@ -37,7 +37,7 @@ public:
|
||||
void run(F&& f, Args&&... args)
|
||||
{
|
||||
{
|
||||
auto lock = acquire();
|
||||
auto lock = acquire_unique();
|
||||
|
||||
tasks.emplace([&f, &args...]() {
|
||||
f(std::forward<Args>(args)...);
|
||||
@ -62,7 +62,7 @@ private:
|
||||
task_t task;
|
||||
|
||||
{
|
||||
auto lock = acquire();
|
||||
auto lock = acquire_unique();
|
||||
|
||||
cond.wait(lock, [this] {
|
||||
return !this->alive || !this->tasks.empty();
|
||||
|
@ -7,7 +7,9 @@
|
||||
template <class lock_t = SpinLock>
|
||||
class Lockable
|
||||
{
|
||||
protected:
|
||||
public:
|
||||
using lock_type = lock_t;
|
||||
|
||||
std::lock_guard<lock_t> acquire_guard()
|
||||
{
|
||||
return std::lock_guard<lock_t>(lock);
|
||||
|
92
transactions/commit_log.hpp
Normal file
92
transactions/commit_log.hpp
Normal file
@ -0,0 +1,92 @@
|
||||
#ifndef MEMGRAPH_TRANSACTIONS_COMMIT_LOG_HPP
|
||||
#define MEMGRAPH_TRANSACTIONS_COMMIT_LOG_HPP
|
||||
|
||||
#include "data_structures/bitset/dynamic_bitset.hpp"
|
||||
|
||||
namespace tx
|
||||
{
|
||||
|
||||
class CommitLog
|
||||
{
|
||||
public:
|
||||
struct Info
|
||||
{
|
||||
enum Status
|
||||
{
|
||||
ACTIVE = 0, // 00
|
||||
COMMITTED = 1, // 01
|
||||
ABORTED = 2, // 10
|
||||
};
|
||||
|
||||
bool is_active() const
|
||||
{
|
||||
return flags & ACTIVE;
|
||||
}
|
||||
|
||||
bool is_committed() const
|
||||
{
|
||||
return flags & COMMITTED;
|
||||
}
|
||||
|
||||
bool is_aborted() const
|
||||
{
|
||||
return flags & ABORTED;
|
||||
}
|
||||
|
||||
operator uint8_t() const
|
||||
{
|
||||
return flags;
|
||||
}
|
||||
|
||||
uint8_t flags;
|
||||
};
|
||||
|
||||
CommitLog() = default;
|
||||
CommitLog(CommitLog&) = delete;
|
||||
CommitLog(CommitLog&&) = delete;
|
||||
|
||||
CommitLog operator=(CommitLog) = delete;
|
||||
|
||||
static CommitLog& get()
|
||||
{
|
||||
static CommitLog log;
|
||||
return log;
|
||||
}
|
||||
|
||||
Info fetch_info(uint64_t id)
|
||||
{
|
||||
return Info { log.at(2 * id, 2) };
|
||||
}
|
||||
|
||||
bool is_active(uint64_t id)
|
||||
{
|
||||
return fetch_info(id).is_active();
|
||||
}
|
||||
|
||||
bool is_committed(uint64_t id)
|
||||
{
|
||||
return fetch_info(id).is_committed();
|
||||
}
|
||||
|
||||
void set_committed(uint64_t id)
|
||||
{
|
||||
log.set(2 * id);
|
||||
}
|
||||
|
||||
bool is_aborted(uint64_t id)
|
||||
{
|
||||
return fetch_info(id).is_aborted();
|
||||
}
|
||||
|
||||
void set_aborted(uint64_t id)
|
||||
{
|
||||
log.set(2 * id + 1);
|
||||
}
|
||||
|
||||
private:
|
||||
DynamicBitset<uint8_t, 32768> log;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
37
transactions/snapshot.hpp
Normal file
37
transactions/snapshot.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef MEMGRAPH_TRANSACTIONS_SNAPSHOT_HPP
|
||||
#define MEMGRAPH_TRANSACTIONS_SNAPSHOT_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace tx
|
||||
{
|
||||
|
||||
template <class id_t>
|
||||
class Snapshot
|
||||
{
|
||||
public:
|
||||
Snapshot(std::vector<id_t> active) : active(std::move(active)) {}
|
||||
|
||||
Snapshot(const Snapshot& other)
|
||||
{
|
||||
active = other.active;
|
||||
}
|
||||
|
||||
Snapshot(Snapshot&& other)
|
||||
{
|
||||
active = std::move(other.active);
|
||||
}
|
||||
|
||||
bool is_active(id_t xid) const
|
||||
{
|
||||
return std::binary_search(active.begin(), active.end(), xid);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<id_t> active;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -5,13 +5,15 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "snapshot.hpp"
|
||||
|
||||
namespace tx
|
||||
{
|
||||
|
||||
struct Transaction
|
||||
{
|
||||
Transaction(uint64_t id, std::vector<uint64_t> active)
|
||||
: id(id), cid(1), active(std::move(active)) {}
|
||||
Transaction(uint64_t id, Snapshot<uint64_t> snapshot)
|
||||
: id(id), cid(1), snapshot(std::move(snapshot)) {}
|
||||
|
||||
// index of this transaction
|
||||
uint64_t id;
|
||||
@ -19,30 +21,8 @@ struct Transaction
|
||||
// index of the current command in the current transaction;
|
||||
uint8_t cid;
|
||||
|
||||
// the ids of the currently active transactions used by the mvcc
|
||||
// implementation for snapshot transaction isolation.
|
||||
// std::vector is much faster than std::set for fewer number of items
|
||||
// we don't expect the number of active transactions getting too large.
|
||||
std::vector<uint64_t> active;
|
||||
|
||||
// check weather the transaction with the xid looks committed from the
|
||||
// database snapshot given to this transaction
|
||||
bool looks_committed(uint64_t xid) const
|
||||
{
|
||||
// transaction xid is newer than id and therefore not visible at all
|
||||
if (xid > id)
|
||||
return false;
|
||||
|
||||
// transaction xid is not visible if it's currently active. the
|
||||
// active transactions are sorted ascending and therefore we can stop
|
||||
// looking as soon as we hit the active transaction with id greater
|
||||
// than xid
|
||||
for(size_t i = 0; i < active.size(); ++i)
|
||||
if(xid <= active[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
// a snapshot of currently active transactions
|
||||
Snapshot<uint64_t> snapshot;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,16 +13,15 @@ template <class id_t>
|
||||
class TransactionCache
|
||||
{
|
||||
public:
|
||||
|
||||
Transaction* get(id_t id) const
|
||||
{
|
||||
auto it = cache.find(id);
|
||||
return it != cache.end() ? it->second : nullptr;
|
||||
return it != cache.end() ? it->second.get() : nullptr;
|
||||
}
|
||||
|
||||
void put(id_t id, Transaction* transaction)
|
||||
{
|
||||
cache.emplace(std::make_pair(id, transaction));
|
||||
cache.emplace(std::make_pair(id, std::unique_ptr<Transaction>(transaction)));
|
||||
}
|
||||
|
||||
void del(id_t id)
|
||||
|
@ -1,16 +1,12 @@
|
||||
#ifndef MEMGRAPH_MVCC_TRANSACTIONENGINE_HPP
|
||||
#define MEMGRAPH_MVCC_TRANSACTIONENGINE_HPP
|
||||
|
||||
#include <cstdlib>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <algorithm>
|
||||
|
||||
#include "transaction.hpp"
|
||||
#include "transaction_cache.hpp"
|
||||
#include "commit_log.hpp"
|
||||
|
||||
#include "utils/counters/simple_counter.hpp"
|
||||
|
||||
@ -29,11 +25,11 @@ public:
|
||||
class TransactionEngine : Lockable<SpinLock>
|
||||
{
|
||||
public:
|
||||
TransactionEngine(uint64_t n) : counter(n) {}
|
||||
TransactionEngine() : counter(0) {}
|
||||
|
||||
const Transaction& begin()
|
||||
{
|
||||
auto guard = this->acquire();
|
||||
auto guard = this->acquire_unique();
|
||||
|
||||
auto id = counter.next();
|
||||
auto t = new Transaction(id, active);
|
||||
@ -46,7 +42,7 @@ public:
|
||||
|
||||
const Transaction& advance(uint64_t id)
|
||||
{
|
||||
auto guard = this->acquire();
|
||||
auto guard = this->acquire_unique();
|
||||
|
||||
auto* t = cache.get(id);
|
||||
|
||||
@ -61,35 +57,37 @@ public:
|
||||
|
||||
void commit(const Transaction& t)
|
||||
{
|
||||
auto guard = this->acquire();
|
||||
auto guard = this->acquire_unique();
|
||||
CommitLog::get().set_committed(t.id);
|
||||
|
||||
finalize(t);
|
||||
}
|
||||
|
||||
void rollback(const Transaction& t)
|
||||
void abort(const Transaction& t)
|
||||
{
|
||||
auto guard = this->acquire();
|
||||
auto guard = this->acquire_unique();
|
||||
CommitLog::get().set_aborted(t.id);
|
||||
|
||||
finalize(t);
|
||||
}
|
||||
|
||||
uint64_t last_known_active()
|
||||
{
|
||||
auto guard = this->acquire();
|
||||
auto guard = this->acquire_unique();
|
||||
return active.front();
|
||||
}
|
||||
|
||||
// total number of transactions started from the beginning of time
|
||||
uint64_t count()
|
||||
{
|
||||
auto guard = this->acquire();
|
||||
auto guard = this->acquire_unique();
|
||||
return counter.count();
|
||||
}
|
||||
|
||||
// the number of currently active transactions
|
||||
size_t size()
|
||||
{
|
||||
auto guard = this->acquire();
|
||||
auto guard = this->acquire_unique();
|
||||
return active.size();
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ public:
|
||||
|
||||
template <class T>
|
||||
void factory(typename Creator<T>::func&& f)
|
||||
{
|
||||
{
|
||||
items[key<T>()] = std::move(Holdable::uptr(
|
||||
new Creator<T>(std::forward<typename Creator<T>::func>(f))
|
||||
));
|
||||
|
@ -33,13 +33,9 @@ struct MarkRef
|
||||
T& operator*() { return *get(); }
|
||||
T* operator->() { return get(); }
|
||||
|
||||
operator T*() { return get(); }
|
||||
|
||||
uintptr_t ptr;
|
||||
};
|
||||
|
||||
//template <class T, class... Args>
|
||||
//MarkRef<T> make_markref(Args&&... args)
|
||||
//{
|
||||
//
|
||||
//}
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user