diff --git a/ConflictSet.cpp b/ConflictSet.cpp index 6be8671..728a69f 100644 --- a/ConflictSet.cpp +++ b/ConflictSet.cpp @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -466,9 +468,32 @@ void lastLeqMulti(Arena &arena, Node *root, std::span keys, fprintf(file, "}\n"); } +[[maybe_unused]] void printLogical(std::string &result, Node *node) { + for (auto iter = extrema(node, false); iter != nullptr;) { + auto *next = ::next(iter, true); + std::string key; + for (auto c : std::string_view((const char *)(iter + 1), iter->len)) { + key += "x"; + key += "0123456789abcdef"[c / 16]; + key += "0123456789abcdef"[c % 16]; + } + if (iter->pointVersion == iter->rangeVersion) { + result += key + " -> " + std::to_string(iter->pointVersion) + "\n"; + } else { + result += key + " -> " + std::to_string(iter->pointVersion) + "\n"; + if (next == nullptr || + (std::string_view((const char *)(next + 1), iter->len) != + (std::string((const char *)(iter + 1), iter->len) + + std::string("\x00", 1)))) { + result += key + "x00 -> " + std::to_string(iter->rangeVersion) + "\n"; + } + } + iter = next; + } +} + [[maybe_unused]] Key toKey(Arena &arena, int n) { constexpr int kMaxLength = 4; - // TODO use arena allocation int i = kMaxLength; uint8_t *itoaBuf = new (arena) uint8_t[kMaxLength]; memset(itoaBuf, '0', kMaxLength); @@ -479,6 +504,19 @@ void lastLeqMulti(Arena &arena, Node *root, std::span keys, return Key{itoaBuf, kMaxLength}; } +[[maybe_unused]] Key toKeyAfter(Arena &arena, int n) { + constexpr int kMaxLength = 4; + int i = kMaxLength; + uint8_t *itoaBuf = new (arena) uint8_t[kMaxLength + 1]; + memset(itoaBuf, '0', kMaxLength); + itoaBuf[kMaxLength] = 0; + do { + itoaBuf[--i] = "0123456789abcdef"[n % 16]; + n /= 16; + } while (n); + return Key{itoaBuf, kMaxLength + 1}; +} + // Recompute maxVersion, and propagate up the tree as necessary // TODO interleave this? Will require careful analysis for correctness, and the // performance gains may not be worth it. @@ -588,8 +626,6 @@ bool checkInvariants(Node *node) { checkMaxVersion(node, success); checkParentPointers(node, success); - // TODO Compare logical contents of map with - // reference implementation return success; } @@ -823,21 +859,105 @@ namespace std { void __throw_length_error(const char *) { abort(); } } // namespace std +namespace { +struct ReferenceImpl { + explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) { + writeVersionMap[""] = oldestVersion; + } + void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results, + int count) const { + for (int i = 0; i < count; ++i) { + if (reads[i].readVersion < oldestVersion) { + results[i] = ConflictSet::TooOld; + continue; + } + auto begin = + std::string((const char *)reads[i].begin.p, reads[i].begin.len); + auto end = + reads[i].end.len == 0 + ? begin + std::string("\x00", 1) + : std::string((const char *)reads[i].end.p, reads[i].end.len); + int64_t maxVersion = oldestVersion; + for (auto iter = --writeVersionMap.upper_bound(begin), + endIter = writeVersionMap.lower_bound(end); + iter != endIter; ++iter) { + maxVersion = std::max(maxVersion, iter->second); + } + results[i] = maxVersion > reads[i].readVersion ? ConflictSet::Conflict + : ConflictSet::Commit; + } + } + void addWrites(const ConflictSet::WriteRange *writes, int count) { + for (int i = 0; i < count; ++i) { + auto begin = + std::string((const char *)writes[i].begin.p, writes[i].begin.len); + auto end = + writes[i].end.len == 0 + ? begin + std::string("\x00", 1) + : std::string((const char *)writes[i].end.p, writes[i].end.len); + auto writeVersion = writes[i].writeVersion; + auto prevVersion = (--writeVersionMap.upper_bound(end))->second; + for (auto iter = writeVersionMap.lower_bound(begin), + endIter = writeVersionMap.lower_bound(end); + iter != endIter;) { + iter = writeVersionMap.erase(iter); + } + writeVersionMap[begin] = writeVersion; + writeVersionMap[end] = prevVersion; + } + } + + void setOldestVersion(int64_t oldestVersion) { + this->oldestVersion = oldestVersion; + } + + void printLogical(std::string &result) { + for (const auto &[k, v] : writeVersionMap) { + std::string key; + for (auto c : k) { + key += "x"; + key += "0123456789abcdef"[c / 16]; + key += "0123456789abcdef"[c % 16]; + } + result += key + " -> " + std::to_string(v) + "\n"; + } + } + + int64_t oldestVersion; + std::map writeVersionMap; +}; +} // namespace + #ifdef ENABLE_TESTS + int main(void) { int64_t writeVersion = 0; ConflictSet::Impl cs{writeVersion}; + ReferenceImpl refImpl{writeVersion}; Arena arena; - constexpr int kNumKeys = 100; + constexpr int kNumKeys = 10; auto *write = new (arena) ConflictSet::WriteRange[kNumKeys]; for (int i = 0; i < kNumKeys; ++i) { write[i].begin = toKey(arena, i); + write[i].end = toKeyAfter(arena, i); write[i].end.len = 0; write[i].writeVersion = ++writeVersion; } cs.addWrites(write, kNumKeys); + refImpl.addWrites(write, kNumKeys); debugPrintDot(stdout, cs.root); bool success = checkInvariants(cs.root); + std::string logicalMap; + std::string referenceLogicalMap; + printLogical(logicalMap, cs.root); + refImpl.printLogical(referenceLogicalMap); + if (logicalMap != referenceLogicalMap) { + fprintf(stderr, + "Logical map not equal to reference logical map.\n\nActual:\n" + "%s\nExpected:\n%s\n", + logicalMap.c_str(), referenceLogicalMap.c_str()); + success = false; + } return success ? 0 : 1; } #endif \ No newline at end of file