Continuing major refactor, UNSTABLE STATE. Merged current dev

This commit is contained in:
Florijan Stamenkovic 2017-02-07 13:22:07 +01:00
commit ee523d5080
7 changed files with 740 additions and 7 deletions

View File

@ -145,7 +145,11 @@ class SkipList : private Lockable<lock_t> {
}
static Node *create(const T &item, uint8_t height) {
return create(height, item);
auto node = allocate(height);
// we have raw memory and we need to construct an object
// of type Node on it
return new (node) Node(item, height);
}
static Node *create(T &&item, uint8_t height) {
@ -188,6 +192,10 @@ class SkipList : private Lockable<lock_t> {
this->data.emplace(std::forward<Args>(args)...);
}
Node(const T &data, uint8_t height) : Node(height) {
this->data.set(data);
}
Node(T &&data, uint8_t height) : Node(height) {
this->data.set(std::move(data));
}
@ -940,6 +948,42 @@ class SkipList : private Lockable<lock_t> {
}
}
/**
* Insert unique data
*
* F - type of funct which will create new node if needed. Recieves height
* of node.
*/
// TODO this code is not DRY w.r.t. the other insert function (rvalue ref)
std::pair<Iterator, bool> insert(Node *preds[], Node *succs[], const T &data) {
while (true) {
// TODO: before here was data.first
auto level = find_path(this, H - 1, data, preds, succs);
if (level != -1) {
auto found = succs[level];
if (found->flags.is_marked()) continue;
while (!found->flags.is_fully_linked()) usleep(250);
return {Iterator{succs[level]}, false};
}
auto height = rnd();
guard_t guards[H];
// try to acquire the locks for predecessors up to the height of
// the new node. release the locks and try again if someone else
// has the locks
if (!lock_nodes<true>(height, guards, preds, succs)) continue;
return {insert_here(Node::create(data, height), preds, succs,
height, guards),
true};
}
}
/**
* Insert unique data
*

View File

@ -0,0 +1,117 @@
#pragma once
#include <memory>
#include <string>
#include <cassert>
#include <iostream>
#include <vector>
#include "utils/underlying_cast.hpp"
#include "utils/total_ordering.hpp"
#include "utils/exceptions/basic_exception.hpp"
/**
* Encapsulation of a value and it's type encapsulated in a class that has no
* compiled-time info about that type.
*
* Values can be of a number of predefined types that are enumerated in
* TypedValue::Type. Each such type corresponds to exactly one C++ type.
*/
class TypedValue : public TotalOrdering<TypedValue, TypedValue, TypedValue> {
private:
/** Private default constructor, makes Null */
TypedValue() : type_(Type::Null) {}
public:
/** A value type. Each type corresponds to exactly one C++ type */
enum class Type : unsigned {
Null,
String,
Bool,
Int,
Float
};
// single static reference to Null, used whenever Null should be returned
static const TypedValue Null;
// constructors for primitive types
TypedValue(bool value) : type_(Type::Bool) { bool_v = value; }
TypedValue(int value) : type_(Type::Int) { int_v = value; }
TypedValue(float value) : type_(Type::Float) { float_v = value; }
/// constructors for non-primitive types (shared pointers)
TypedValue(const std::string &value) : type_(Type::String) {
new (&string_v) std::shared_ptr<std::string>(new std::string(value));
}
TypedValue(const char* value) : type_(Type::String) {
new (&string_v) std::shared_ptr<std::string>(new std::string(value));
}
// assignment ops
TypedValue& operator=(TypedValue& other);
TypedValue& operator=(TypedValue&& other);
TypedValue(const TypedValue& other);
~TypedValue();
/**
* Returns the value of the property as given type T.
* The behavior of this function is undefined if
* T does not correspond to this property's type_.
*
* @tparam T Type to interpret the value as.
* @return The value as type T.
*/
template <typename T> T Value() const;
friend std::ostream& operator<<(std::ostream& stream, const TypedValue& prop);
/**
* The Type of property.
*/
const Type type_;
private:
// storage for the value of the property
union {
bool bool_v;
int int_v;
float float_v;
std::shared_ptr<std::string> string_v;
};
};
/**
* An exception raised by the TypedValue system. Typically when
* trying to perform operations (such as addition) on TypedValues
* of incompatible Types.
*/
class TypedValueException : public BasicException {
public:
using ::BasicException::BasicException;
};
// comparison operators
// they return TypedValue because Null can be returned
TypedValue operator==(const TypedValue& a, const TypedValue& b);
TypedValue operator<(const TypedValue& a, const TypedValue& b);
TypedValue operator!(const TypedValue& a);
// arithmetic operators
TypedValue operator-(const TypedValue& a);
TypedValue operator+(const TypedValue& a, const TypedValue& b);
TypedValue operator-(const TypedValue& a, const TypedValue& b);
TypedValue operator/(const TypedValue& a, const TypedValue& b);
TypedValue operator*(const TypedValue& a, const TypedValue& b);
TypedValue operator%(const TypedValue& a, const TypedValue& b);
// binary bool operators
TypedValue operator&&(const TypedValue& a, const TypedValue& b);
TypedValue operator||(const TypedValue& a, const TypedValue& b);
// stream output
std::ostream &operator<<(std::ostream &os, const TypedValue::Type type);

View File

@ -0,0 +1,82 @@
#pragma once
#include <vector>
#include <map>
#include "typed_value.hpp"
/**
* A collection of properties accessed in a map-like way
* using a key of type Properties::TKey.
*
* The underlying implementation is not necessarily std::map.
*/
class TypedValueStore {
public:
using sptr = std::shared_ptr<TypedValueStore>;
/** The type of key used to get and set properties */
using TKey = uint32_t;
/**
* Returns a TypedValue (by reference) at the given key.
* If the key does not exist, the Null property is returned.
*
* This is NOT thread-safe, the reference might not be valid
* when used in a multithreaded scenario.
*
* @param key The key for which a TypedValue is sought.
* @return See above.
*/
const TypedValue &at(const TKey &key) const;
/**
* Sets the value for the given key. A new TypedValue instance
* is created for the given value (which is of raw type).
*
* @tparam TValue Type of value. It must be one of the
* predefined possible TypedValue values (bool, string, int...)
* @param key The key for which the property is set. The previous
* value at the same key (if there was one) is replaced.
* @param value The value to set.
*/
template<typename TValue>
void set(const TKey &key, const TValue &value);
/**
* Set overriding for character constants. Forces conversion
* to std::string, otherwise templating might cast the pointer
* to something else (bool) and mess things up.
*
* @param key The key for which the property is set. The previous
* value at the same key (if there was one) is replaced.
* @param value The value to set.
*/
void set(const TKey &key, const char *value);
/**
* Removes the TypedValue for the given key.
*
* @param key The key for which to remove the property.
* @return The number of removed properties (0 or 1).
*/
size_t erase(const TKey &key);
/**
* @return The number of Properties in this collection.
*/
size_t size() const;
/**
* Accepts two functions.
*
* @param handler Called for each TypedValue in this collection.
* @param finish Called once in the end.
*/
void Accept(std::function<void(const TKey key, const TypedValue& prop)> handler,
std::function<void()> finish = {}) const;
private:
std::vector<std::pair<TKey, TypedValue>> props_;
};

View File

@ -0,0 +1,57 @@
//
// Copyright 2017 Memgraph
// Created by Florijan Stamenkovic on 01.02.17.
//
#pragma once
#include <ostream>
#include "storage/model/typed_value_store.hpp"
/**
* Writes all of the values from the given store in JSON format
* to the given output stream.
*
* @param store The store that should be serialized to JSON.
* @param ostream The stream to write to.
*/
void TypedValuesToJson(const TypedValueStore& store,
std::ostream& ostream=std::cout) {
bool first = true;
auto write_key = [&ostream, &first](const TypedValueStore::TKey &key) -> std::ostream& {
if (first) {
ostream << '{';
first = false;
} else
ostream << ',';
return ostream << '"' << key << "\":";
};
auto handler = [&ostream, &write_key](const TypedValueStore::TKey& key,
const TypedValue& value) {
switch (value.type_) {
case TypedValue::Type::Null:
break;
case TypedValue::Type::Bool:
write_key(key) << (value.Value<bool>() ? "true" : "false");
break;
case TypedValue::Type::String:
write_key(key) << '"' << value.Value<std::string>() << '"';
break;
case TypedValue::Type::Int:
write_key(key) << value.Value<int>();
break;
case TypedValue::Type::Float:
write_key(key) << value.Value<float>();
break;
}
};
auto finish = [&ostream]() { ostream << '}' << std::endl; };
store.Accept(handler, finish);
}

View File

@ -0,0 +1,363 @@
#include <memory>
#include <iostream>
#include <cmath>
#include <fmt/format.h>
#include "storage/model/typed_value.hpp"
#include "utils/assert.hpp"
// Value extraction template instantiations
template<>
bool TypedValue::Value<bool>() const {
runtime_assert(type_ == TypedValue::Type::Bool, "Incompatible template param and type");
return bool_v;
}
template<>
std::string TypedValue::Value<std::string>() const {
runtime_assert(type_ == TypedValue::Type::String, "Incompatible template param and type");
return *string_v;
}
template<>
int TypedValue::Value<int>() const {
runtime_assert(type_ == TypedValue::Type::Int, "Incompatible template param and type");
return int_v;
}
template<>
float TypedValue::Value<float>() const {
runtime_assert(type_ == TypedValue::Type::Float, "Incompatible template param and type");
return float_v;
}
TypedValue::TypedValue(const TypedValue &other) : type_(other.type_) {
switch (other.type_) {
case TypedValue::Type::Null:
return;
case TypedValue::Type::Bool:
this->bool_v = other.bool_v;
return;
case TypedValue::Type::String:
new(&string_v) std::shared_ptr<std::string>(other.string_v);
return;
case Type::Int:
this->int_v = other.int_v;
return;
case Type::Float:
this->float_v = other.float_v;
return;
}
permanent_fail("Unsupported TypedValue::Type");
}
std::ostream &operator<<(std::ostream &os, const TypedValue::Type type) {
switch (type) {
case TypedValue::Type::Null:
return os << "null";
case TypedValue::Type::Bool:
return os << "bool";
case TypedValue::Type::String:
return os << "string";
case TypedValue::Type::Int:
return os << "int";
case TypedValue::Type::Float:
return os << "float";
}
permanent_fail("Unsupported TypedValue::Type");
}
std::ostream &operator<<(std::ostream &os, const TypedValue &property) {
switch (property.type_) {
case TypedValue::Type::Null:
return os << "Null";
case TypedValue::Type::Bool:
return os << (property.Value<bool>() ? "true" : "false");
case TypedValue::Type::String:
return os << property.Value<std::string>();
case TypedValue::Type::Int:
return os << property.Value<int>();
case TypedValue::Type::Float:
return os << property.Value<float>();
}
permanent_fail("Unsupported TypedValue::Type");
}
TypedValue &TypedValue::operator=(TypedValue &&other) {
// set the type of this
const_cast<Type&>(type_) = other.type_;
if (this != &other) {
switch (other.type_) {
case TypedValue::Type::Null:
case TypedValue::Type::Bool:
this->bool_v = other.bool_v;
return *this;
case TypedValue::Type::String:
this->string_v = std::move(other.string_v);
return *this;
case TypedValue::Type::Int:
this->int_v = other.int_v;
return *this;
case TypedValue::Type::Float:
this->float_v = other.float_v;
return *this;
}
}
permanent_fail("Unsupported TypedValue::Type");
}
const TypedValue TypedValue::Null = TypedValue();
TypedValue::~TypedValue() {
switch (type_) {
// destructor for primitive types does nothing
case Type::Null:
case Type::Bool:
case Type::Int:
case Type::Float:
return;
// destructor for shared pointer must release
case Type::String:
string_v.~shared_ptr<std::string>();
return;
}
permanent_fail("Unsupported TypedValue::Type");
}
/**
* Returns the float value of a property.
* The property MUST be either Float or Int.
*
* @param prop
* @return
*/
float ToFloat(const TypedValue& prop) {
switch (prop.type_) {
case TypedValue::Type::Int:
return (float)prop.Value<int>();
case TypedValue::Type::Float:
return prop.Value<float>();
default:
permanent_fail("Unsupported TypedValue::Type");
}
}
TypedValue operator<(const TypedValue& a, const TypedValue& b) {
if (a.type_ == TypedValue::Type::Bool || b.type_ == TypedValue::Type::Bool)
throw TypedValueException("Invalid 'less' operand types({} + {})", a.type_, b.type_);
if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null)
return TypedValue::Null;
if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String) {
if (a.type_ != b.type_)
throw TypedValueException("Invalid equality operand types({} + {})", a.type_, b.type_);
else
return a.Value<std::string>() < b.Value<std::string>();
}
// at this point we only have int and float
if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float)
return ToFloat(a) < ToFloat(b);
else
return a.Value<int>() < b.Value<int>();
}
TypedValue operator==(const TypedValue& a, const TypedValue& b) {
if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null)
return TypedValue::Null;
if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String) {
if (a.type_ != b.type_)
throw TypedValueException("Invalid equality operand types({} + {})", a.type_, b.type_);
else
return a.Value<std::string>() == b.Value<std::string>();
}
if (a.type_ == TypedValue::Type::Bool || b.type_ == TypedValue::Type::Bool) {
if (a.type_ != b.type_)
throw TypedValueException("Invalid equality operand types({} + {})", a.type_, b.type_);
else
return a.Value<bool>() == b.Value<bool>();
}
// at this point we only have int and float
if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){
return ToFloat(a) == ToFloat(b);
} else
return a.Value<int>() == b.Value<int>();
}
TypedValue operator!(const TypedValue& a) {
switch (a.type_) {
case TypedValue::Type::Null:
return TypedValue::Null;
case TypedValue::Type::Bool:
return TypedValue(!a.Value<bool>());
default:
throw TypedValueException("Invalid logical not operand type (!{})", a.type_);
}
}
/**
* Turns a numeric or string property into a string.
*
* @param prop Property.
* @return A string.
*/
std::string PropToString(const TypedValue& prop) {
switch (prop.type_) {
case TypedValue::Type::String:
return prop.Value<std::string>();
case TypedValue::Type::Int:
return std::to_string(prop.Value<int>());
case TypedValue::Type::Float:
return fmt::format("{}", prop.Value<float>());
// unsupported situations
default:
permanent_fail("Unsupported TypedValue::Type");
}
}
TypedValue operator-(const TypedValue &a) {
switch (a.type_) {
case TypedValue::Type::Null:
return TypedValue::Null;
case TypedValue::Type::Int:
return -a.Value<int>();
case TypedValue::Type::Float:
return -a.Value<float>();
default:
throw TypedValueException("Invalid unary minus operand type (-{})", a.type_);
}
}
/**
* Raises a PropertyException if the given properties do not support arithmetic
* operations. If they do, nothing happens.
*
* @param a First prop.
* @param b Second prop.
* @param string_ok If or not for the given operation it's valid to work with
* String values (typically it's OK only for sum).
* @param op_name Name of the operation, used only for exception description,
* if raised.
*/
inline void EnsureArithmeticallyOk(const TypedValue& a, const TypedValue& b,
bool string_ok, const std::string& op_name) {
if (a.type_ == TypedValue::Type::Bool || b.type_ == TypedValue::Type::Bool)
throw TypedValueException("Invalid {} operand types {}, {}", op_name, a.type_, b.type_);
if (string_ok)
return;
if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String)
throw TypedValueException("Invalid subtraction operands types {}, {}", a.type_, b.type_);
}
TypedValue operator+(const TypedValue& a, const TypedValue& b){
EnsureArithmeticallyOk(a, b, true, "addition");
if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null)
return TypedValue::Null;
// no more Bool nor Null, summing works on anything from here onward
if (a.type_ == TypedValue::Type::String || b.type_ == TypedValue::Type::String)
return PropToString(a) + PropToString(b);
// at this point we only have int and float
if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){
return ToFloat(a) + ToFloat(b);
} else
return a.Value<int>() + b.Value<int>();
}
TypedValue operator-(const TypedValue& a, const TypedValue& b){
EnsureArithmeticallyOk(a, b, false, "subtraction");
if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null)
return TypedValue::Null;
// at this point we only have int and float
if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){
return ToFloat(a) - ToFloat(b);
} else
return a.Value<int>() - b.Value<int>();
}
TypedValue operator/(const TypedValue& a, const TypedValue& b){
EnsureArithmeticallyOk(a, b, false, "division");
if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null)
return TypedValue::Null;
// at this point we only have int and float
if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){
return ToFloat(a) / ToFloat(b);
} else
return a.Value<int>() / b.Value<int>();
}
TypedValue operator*(const TypedValue& a, const TypedValue& b){
EnsureArithmeticallyOk(a, b, false, "multiplication");
if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null)
return TypedValue::Null;
// at this point we only have int and float
if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){
return ToFloat(a) * ToFloat(b);
} else
return a.Value<int>() * b.Value<int>();
}
TypedValue operator%(const TypedValue& a, const TypedValue& b){
EnsureArithmeticallyOk(a, b, false, "modulo");
if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null)
return TypedValue::Null;
// at this point we only have int and float
if (a.type_ == TypedValue::Type::Float || b.type_ == TypedValue::Type::Float){
return (float)fmod(ToFloat(a), ToFloat(b));
} else
return a.Value<int>() % b.Value<int>();
}
inline bool IsLogicallyOk(const TypedValue& a) {
return a.type_ == TypedValue::Type::Bool || a.type_ == TypedValue::Type::Null;
}
TypedValue operator&&(const TypedValue& a, const TypedValue& b) {
if(IsLogicallyOk(a) && IsLogicallyOk(b)){
if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null)
return TypedValue::Null;
else
return a.Value<bool>() && b.Value<bool>();
} else
throw TypedValueException("Invalid logical and operand types({} && {})", a.type_, b.type_);
}
TypedValue operator||(const TypedValue& a, const TypedValue& b) {
if(IsLogicallyOk(a) && IsLogicallyOk(b)){
if (a.type_ == TypedValue::Type::Null || b.type_ == TypedValue::Type::Null)
return TypedValue::Null;
else
return a.Value<bool>() || b.Value<bool>();
} else
throw TypedValueException("Invalid logical and operand types({} && {})", a.type_, b.type_);
}

View File

@ -0,0 +1,68 @@
#include <algorithm>
#include "storage/model/typed_value_store.hpp"
const TypedValue& TypedValueStore::at(const TKey &key) const {
for (const auto& kv : props_)
if (kv.first == key)
return kv.second;
return TypedValue::Null;
}
template<typename TValue>
void TypedValueStore::set(const TKey &key, const TValue &value) {
for (auto& kv: props_)
if (kv.first == key) {
kv.second = TypedValue(value);
return;
}
// there is no value for the given key, add new
// TODO consider vector size increment optimization
props_.push_back(std::move(std::make_pair(key, value)));
}
// instantiations of the TypedValueStore::set function
// instances must be made for all of the supported C++ types
template void TypedValueStore::set<std::string>(const TKey &key, const std::string &value);
template void TypedValueStore::set<bool>(const TKey &key, const bool &value);
template void TypedValueStore::set<int>(const TKey &key, const int &value);
template void TypedValueStore::set<float>(const TKey &key, const float &value);
/**
* Set overriding for character constants. Forces conversion
* to std::string, otherwise templating might cast the pointer
* to something else (bool) and mess things up.
*
* @param key The key for which the property is set. The previous
* value at the same key (if there was one) is replaced.
* @param value The value to set.
*/
void TypedValueStore::set(const TKey &key, const char *value) {
set(key, std::string(value));
}
size_t TypedValueStore::erase(const TKey &key) {
auto found = std::find_if(props_.begin(), props_.end(), [&key](std::pair<TKey, TypedValue> &kv){return kv.first == key;});
if (found != props_.end()) {
props_.erase(found);
return 1;
}
return 0;
}
size_t TypedValueStore::size() const { return props_.size(); }
void TypedValueStore::Accept(std::function<void(const TypedValueStore::TKey, const TypedValue &)> handler,
std::function<void()> finish) const {
if (handler)
for (const auto& prop : props_)
handler(prop.first, prop.second);
if (finish)
finish();
}

View File

@ -15,7 +15,7 @@ int main()
init_log();
memory_check(THREADS_NO, [] {
set_t skiplist;
ConcurrentSet<std::string> skiplist;
auto futures = run<std::vector<long>>(
THREADS_NO, skiplist, [](auto acc, auto index) {
@ -25,14 +25,16 @@ int main()
std::vector<long> set(key_range);
do {
size_t num = rand();
int num = rand();
std::string num_str = std::to_string(num);
if (rand_op()) {
if (acc.remove(num)) {
if (acc.remove(num_str)) {
downcount--;
set[num]--;
}
} else {
if (acc.insert(num).second) {
std::string num_str = std::to_string(num);
if (acc.insert(num_str).second) {
downcount--;
set[num]++;
}
@ -52,12 +54,12 @@ int main()
auto accessor = skiplist.access();
for (int i = 0; i < key_range; i++) {
permanent_assert(set[i] == 0 || set[i] == 1 ||
(set[i] == 1) ^ accessor.contains(i),
(set[i] == 1) ^ accessor.contains(std::to_string(i)),
"Set doesn't hold it's guarantees.");
}
for (auto &e : accessor) {
set[e]--;
set[std::stoi(e)]--;
}
check_zero(key_range, set, "Set");