From a68ad5dd1777ba9d59fd0db2aa768fef32131da4 Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Sun, 30 Jun 2024 15:25:44 -0700 Subject: [PATCH] Interface change! Return TooOld after 2e9 versions Event if setOldestVersion wasn't called --- ConflictSet.cpp | 17 +++++++++++++++-- SkipList.cpp | 10 ++++++++-- include/ConflictSet.h | 4 ++++ test_conflict_set.py | 7 +++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/ConflictSet.cpp b/ConflictSet.cpp index f85a412..98555c2 100644 --- a/ConflictSet.cpp +++ b/ConflictSet.cpp @@ -2765,7 +2765,9 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { auto begin = std::span(r.begin.p, r.begin.len); auto end = std::span(r.end.p, r.end.len); result[i] = - InternalVersionT(reads[i].readVersion) < oldestVersion ? TooOld + InternalVersionT(reads[i].readVersion) < oldestVersion || + reads[i].readVersion < newestVersionFullPrecision - 2e9 + ? TooOld : (end.size() > 0 ? checkRangeRead(root, begin, end, InternalVersionT(reads[i].readVersion), this) @@ -2777,6 +2779,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { } void addWrites(const WriteRange *writes, int count, int64_t writeVersion) { + assert(writeVersion >= newestVersionFullPrecision); + newestVersionFullPrecision = writeVersion; #if INTERNAL_VERSION_32_BIT InternalVersionT::zero = oldestVersion; #endif @@ -2798,6 +2802,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { void setOldestVersion(int64_t o) { InternalVersionT oldestVersion{o}; + assert(o >= oldestVersionFullPrecision); + this->oldestVersionFullPrecision = o; this->oldestVersion = oldestVersion; #if INTERNAL_VERSION_32_BIT InternalVersionT::zero = oldestVersion; @@ -2841,7 +2847,9 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { } } - explicit Impl(int64_t oldestVersion) : oldestVersion(oldestVersion) { + explicit Impl(int64_t oldestVersion) + : oldestVersion(oldestVersion), oldestVersionFullPrecision(oldestVersion), + newestVersionFullPrecision(oldestVersion) { #if DEBUG_VERBOSE fprintf(stderr, "radix_tree: create\n"); #endif @@ -2876,6 +2884,11 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { Node *root; InternalVersionT rootMaxVersion; InternalVersionT oldestVersion; + // TODO this doesn't fully mitigate the 32-bit precision issue, since we still + // need to make sure we clean up versions in the tree before they fall out of + // the 2e9 window. + int64_t oldestVersionFullPrecision; + int64_t newestVersionFullPrecision; int64_t totalBytes = 0; }; diff --git a/SkipList.cpp b/SkipList.cpp index a4bb4a5..86ee9b5 100644 --- a/SkipList.cpp +++ b/SkipList.cpp @@ -577,7 +577,8 @@ struct SkipListConflictSet {}; struct __attribute__((visibility("hidden"))) ConflictSet::Impl { Impl(int64_t oldestVersion) - : oldestVersion(oldestVersion), skipList(oldestVersion) {} + : oldestVersion(oldestVersion), newestVersion(oldestVersion), + skipList(oldestVersion) {} void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results, int count) const { Arena arena; @@ -592,7 +593,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { } skipList.detectConflicts(ranges, count, results); for (int i = 0; i < count; ++i) { - if (reads[i].readVersion < oldestVersion) { + if (reads[i].readVersion < oldestVersion || + reads[i].readVersion < newestVersion - 2e9) { results[i] = TooOld; } } @@ -600,6 +602,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { void addWrites(const ConflictSet::WriteRange *writes, int count, int64_t writeVersion) { + assert(writeVersion >= newestVersion); + newestVersion = writeVersion; Arena arena; const int stringCount = count * 2; @@ -630,6 +634,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl { } void setOldestVersion(int64_t oldestVersion) { + assert(oldestVersion >= this->oldestVersion); this->oldestVersion = oldestVersion; SkipList::Finger finger; int temp; @@ -648,6 +653,7 @@ private: Arena removalArena; std::span removalKey; int64_t oldestVersion; + int64_t newestVersion; SkipList skipList; }; diff --git a/include/ConflictSet.h b/include/ConflictSet.h index 5bff07f..3a5c3c5 100644 --- a/include/ConflictSet.h +++ b/include/ConflictSet.h @@ -54,6 +54,8 @@ struct __attribute__((__visibility__("default"))) ConflictSet { /** `end` having length 0 denotes that this range is the single key {begin}. * Otherwise this denotes the range [begin, end) */ Key end; + /** `readVersion` older than the the oldestVersion or the version of the + * latest call to `addWrites` minus two billion will result in `TooOld` */ int64_t readVersion; }; @@ -141,6 +143,8 @@ typedef struct { /** `end` having length 0 denotes that this range is the single key {begin}. * Otherwise this denotes the range [begin, end) */ ConflictSet_Key end; + /** `readVersion` older than the the oldestVersion or the version of the + * latest call to `addWrites` minus two billion will result in `TooOld` */ int64_t readVersion; } ConflictSet_ReadRange; diff --git a/test_conflict_set.py b/test_conflict_set.py index 5a404ac..34cdf2c 100644 --- a/test_conflict_set.py +++ b/test_conflict_set.py @@ -77,6 +77,13 @@ def test_internal_version_zero(): cs.check(read(0xFFFFFFF1, b"\x00", b"\xff")) +def test_two_billion_versions(): + with DebugConflictSet() as cs: + cs.addWrites(int(2e9) + 1) + cs.check(read(0, b"\x00", b"\xff")) + cs.check(read(1, b"\x00", b"\xff")) + + def test_decrease_capacity(): # make a Node48, then a Node256 for count in (17, 49):