Compute the TooOld window in integer arithmetic
CI / test (arm64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 22, https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst, debug-arm64, ubuntu-latest-arm64) (pull_request) Successful in 3m54s
CI / release (arm64, , ubuntu-latest-arm64) (pull_request) Successful in 4m26s
CI / pre-commit (pull_request) Successful in 2m14s
CI / test (amd64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 21, https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst, debug, ubuntu-latest-amd64) (pull_request) Successful in 4m1s
CI / test (amd64, -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 21, , 64-bit-versions, ubuntu-latest-amd64) (pull_request) Successful in 3m35s
CI / test (amd64, -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, 21, , gcc, ubuntu-latest-amd64) (pull_request) Successful in 3m31s
CI / test (amd64, -DUSE_SIMD_FALLBACK=ON, 21, , simd-fallback, ubuntu-latest-amd64) (pull_request) Successful in 3m36s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (pull_request) Successful in 6m0s
CI / coverage (pull_request) Successful in 4m1s
CI / test (arm64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 22, https://minio.weaselab.dev/public/aarch64/msan-toolchain-22.1.8.tar.zst, debug-arm64, ubuntu-latest-arm64) (pull_request) Successful in 3m54s
CI / release (arm64, , ubuntu-latest-arm64) (pull_request) Successful in 4m26s
CI / pre-commit (pull_request) Successful in 2m14s
CI / test (amd64, -DCMAKE_BUILD_TYPE=Debug -DMSAN_TOOLCHAIN_PATH=/opt/msan, 21, https://minio.weaselab.dev/public/x86_64/msan-toolchain-21.1.8.tar.zst, debug, ubuntu-latest-amd64) (pull_request) Successful in 4m1s
CI / test (amd64, -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 21, , 64-bit-versions, ubuntu-latest-amd64) (pull_request) Successful in 3m35s
CI / test (amd64, -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, 21, , gcc, ubuntu-latest-amd64) (pull_request) Successful in 3m31s
CI / test (amd64, -DUSE_SIMD_FALLBACK=ON, 21, , simd-fallback, ubuntu-latest-amd64) (pull_request) Successful in 3m36s
CI / release (amd64, -DMSAN_TOOLCHAIN_PATH=/opt/msan, ubuntu-latest-amd64) (pull_request) Successful in 6m0s
CI / coverage (pull_request) Successful in 4m1s
`newestVersion - 2e9` is evaluated in `double`, whose 53 bit mantissa makes the `newestVersion - 2000000000` boundary off by up to 512 versions once `newestVersion >= 2^53`. skip_list therefore reported `Commit` for reads one version below the window, and `TooOld` for reads exactly at it. `ReferenceImpl` in Internal.h had the same expression, so the differential tests could not catch it. Share a single integer `kNominalVersionWindow` via Internal.h, use it in both places, and drop the duplicate (already integer) definition from ConflictSet.cpp. Adds a regression test that bisimulates skip_list against the radix tree above 2^53. Closes #99
This commit is contained in:
1 parent
e23018c952
commit
ae5bd27d18
4 files changed
+27
-3
No files matched your search
@@ -96,7 +96,6 @@ int64_t getSearchPathLength(struct Node *);
|
|||||||
|
|
||||||
// ==================== BEGIN IMPLEMENTATION ====================
|
// ==================== BEGIN IMPLEMENTATION ====================
|
||||||
|
|
||||||
constexpr int64_t kNominalVersionWindow = 2e9;
|
|
||||||
constexpr int64_t kMaxCorrectVersionWindow =
|
constexpr int64_t kMaxCorrectVersionWindow =
|
||||||
std::numeric_limits<int32_t>::max();
|
std::numeric_limits<int32_t>::max();
|
||||||
static_assert(kNominalVersionWindow <= kMaxCorrectVersionWindow);
|
static_assert(kNominalVersionWindow <= kMaxCorrectVersionWindow);
|
||||||
|
|||||||
+10
-1
@@ -27,6 +27,15 @@ using namespace weaselab;
|
|||||||
#define SHOW_MEMORY 0
|
#define SHOW_MEMORY 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// The window of versions, ending at `newestVersion`, that a subsequent `check`
|
||||||
|
// is guaranteed to answer correctly. Reads older than
|
||||||
|
// `newestVersion - kNominalVersionWindow` are reported as `TooOld`.
|
||||||
|
//
|
||||||
|
// This must be an integer constant: `newestVersion - 2e9` is computed in
|
||||||
|
// `double`, whose 53 bit mantissa makes the boundary off by up to 512 versions
|
||||||
|
// once `newestVersion >= 2^53`.
|
||||||
|
constexpr int64_t kNominalVersionWindow = 2000000000;
|
||||||
|
|
||||||
// std::span is not trivially constructible. We want a span that leaves its
|
// std::span is not trivially constructible. We want a span that leaves its
|
||||||
// members uninitialized for performance reasons.
|
// members uninitialized for performance reasons.
|
||||||
struct TrivialSpan {
|
struct TrivialSpan {
|
||||||
@@ -513,7 +522,7 @@ struct ReferenceImpl {
|
|||||||
int count) const {
|
int count) const {
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
if (reads[i].readVersion < oldestVersion ||
|
if (reads[i].readVersion < oldestVersion ||
|
||||||
reads[i].readVersion < newestVersion - 2e9) {
|
reads[i].readVersion < newestVersion - kNominalVersionWindow) {
|
||||||
results[i] = ConflictSet::TooOld;
|
results[i] = ConflictSet::TooOld;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -741,7 +741,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
int iters = skipList.detectConflicts(ranges, count, results);
|
int iters = skipList.detectConflicts(ranges, count, results);
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
if (reads[i].readVersion < oldestVersion ||
|
if (reads[i].readVersion < oldestVersion ||
|
||||||
reads[i].readVersion < newestVersion - 2e9) {
|
reads[i].readVersion < newestVersion - kNominalVersionWindow) {
|
||||||
results[i] = TooOld;
|
results[i] = TooOld;
|
||||||
}
|
}
|
||||||
tls.commits_accum += results[i] == Commit;
|
tls.commits_accum += results[i] == Commit;
|
||||||
|
|||||||
@@ -209,6 +209,22 @@ def test_two_billion_versions():
|
|||||||
cs.check(read(1, b"\x00", b"\xff"))
|
cs.check(read(1, b"\x00", b"\xff"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_too_old_window_precision():
|
||||||
|
# Regression test for issue #99: the TooOld window boundary
|
||||||
|
# (`newestVersion - 2e9`) must be computed with integer arithmetic. It used
|
||||||
|
# to be computed in `double`, whose 53 bit mantissa makes the boundary off
|
||||||
|
# by up to ~512 versions once newestVersion >= 2^53.
|
||||||
|
for newest in (1 << 53, (1 << 53) + 1, (1 << 53) + 7, 2**63 - 1):
|
||||||
|
with DebugConflictSet() as cs:
|
||||||
|
cs.addWrites(newest) # No writes; advances newestVersion
|
||||||
|
boundary = newest - 2 * 10**9
|
||||||
|
# One below the window is TooOld, even though `double` rounding
|
||||||
|
# used to put it inside the window.
|
||||||
|
assert cs.check(read(boundary - 1, b"k")) == [Result.TOO_OLD]
|
||||||
|
# Exactly at the boundary is not TooOld.
|
||||||
|
assert cs.check(read(boundary, b"k")) == [Result.COMMIT]
|
||||||
|
|
||||||
|
|
||||||
def test_positive_bytes():
|
def test_positive_bytes():
|
||||||
with DebugConflictSet() as cs:
|
with DebugConflictSet() as cs:
|
||||||
cs.addWrites(1, write(b"hello"))
|
cs.addWrites(1, write(b"hello"))
|
||||||
|
|||||||
Reference in new issue
Block a user