#include "ConflictSet.h" #include "Internal.h" #include #include #include // This implementation isn't correct for range queries :). It's just intended as // a reference for performance comparison with point queries. // struct is from "https://www.cppstories.com/2021/heterogeneous-access-cpp20/" struct string_hash { using is_transparent = void; [[nodiscard]] size_t operator()(std::string_view txt) const { return std::hash{}(txt); } [[nodiscard]] size_t operator()(const std::string &txt) const { return std::hash{}(txt); } }; struct __attribute__((visibility("hidden"))) ConflictSet::Impl { Impl(int64_t oldestVersion) : oldestVersion(oldestVersion) {} void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results, int count) const { for (int i = 0; i < count; ++i) { auto key = std::string_view((const char *)reads[i].begin.p, reads[i].begin.len); auto version = reads[i].readVersion; if (version < oldestVersion) { results[i] = TooOld; continue; } auto iter = map.find(key); results[i] = iter == map.end() || iter->second <= version ? Commit : Conflict; } } void addWrites(const ConflictSet::WriteRange *writes, int count, int64_t writeVersion) { for (int i = 0; i < count; ++i) { auto &max = map[std::string((const char *)writes[i].begin.p, writes[i].begin.len)]; assert(writeVersion >= max); max = writeVersion; keyUpdates += 2; } } void setOldestVersion(int64_t oldestVersion) { if (oldestVersion <= this->oldestVersion) { return; } this->oldestVersion = oldestVersion; if (keyUpdates < 100) { return; } auto iter = map.find(removalKey); while (keyUpdates > 0) { if (iter == map.end()) { iter = map.begin(); } for (; iter != map.end(); --keyUpdates) { if (iter->second <= oldestVersion) { iter = map.erase(iter); } else { ++iter; } } } if (iter == map.end()) { removalKey.clear(); } else { removalKey = iter->first; } } private: int64_t keyUpdates = 0; int64_t oldestVersion; std::unordered_map> map; std::string removalKey; }; void ConflictSet::check(const ReadRange *reads, Result *results, int count) const { return impl->check(reads, results, count); } void ConflictSet::addWrites(const WriteRange *writes, int count, int64_t writeVersion) { return impl->addWrites(writes, count, writeVersion); } void ConflictSet::setOldestVersion(int64_t oldestVersion) { return impl->setOldestVersion(oldestVersion); } int64_t ConflictSet::getBytes() const { return -1; } void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const { *metrics = nullptr; *count = 0; } double ConflictSet::MetricsV1::getValue() const { return 0; } ConflictSet::ConflictSet(int64_t oldestVersion) : impl(new(safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {} ConflictSet::~ConflictSet() { if (impl) { impl->~Impl(); safe_free(impl, sizeof(Impl)); } } ConflictSet::ConflictSet(ConflictSet &&other) noexcept : impl(std::exchange(other.impl, nullptr)) {} ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept { impl = std::exchange(other.impl, nullptr); return *this; } using ConflictSet_Result = ConflictSet::Result; using ConflictSet_Key = ConflictSet::Key; using ConflictSet_ReadRange = ConflictSet::ReadRange; using ConflictSet_WriteRange = ConflictSet::WriteRange; extern "C" { __attribute__((__visibility__("default"))) void ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads, ConflictSet_Result *results, int count) { ((ConflictSet::Impl *)cs)->check(reads, results, count); } __attribute__((__visibility__("default"))) void ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count, int64_t writeVersion) { ((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion); } __attribute__((__visibility__("default"))) void ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) { ((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion); } __attribute__((__visibility__("default"))) void * ConflictSet_create(int64_t oldestVersion) { return new (safe_malloc(sizeof(ConflictSet::Impl))) ConflictSet::Impl{oldestVersion}; } __attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) { using Impl = ConflictSet::Impl; ((Impl *)cs)->~Impl(); safe_free(cs, sizeof(Impl)); } __attribute__((__visibility__("default"))) int64_t ConflictSet_getBytes(void *cs) { using Impl = ConflictSet::Impl; return -1; } }