14 Commits
Author SHA1 Message Date
andrew cc4306cfe9 Bump version
CI / release (arm64, ubuntu-latest-arm64) (push) Successful in 1m49s
CI / pre-commit (push) Successful in 2m0s
CI / test (-DCMAKE_BUILD_TYPE=Debug, debug) (push) Successful in 2m11s
CI / test (-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 64-bit-versions) (push) Successful in 2m12s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (push) Successful in 2m1s
CI / test (-DUSE_SIMD_FALLBACK=ON, simd-fallback) (push) Successful in 2m9s
CI / release (amd64, ubuntu-latest-amd64) (push) Successful in 4m20s
CI / coverage (push) Successful in 2m43s
2026-07-14 15:27:09 -04:00
andrew 9b7199567b Update README.md
CI / pre-commit (push) Successful in 1m59s
CI / release (arm64, ubuntu-latest-arm64) (push) Successful in 1m51s
CI / test (-DCMAKE_BUILD_TYPE=Debug, debug) (push) Successful in 2m11s
CI / test (-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 64-bit-versions) (push) Successful in 2m11s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (push) Successful in 2m1s
CI / test (-DUSE_SIMD_FALLBACK=ON, simd-fallback) (push) Successful in 2m10s
CI / release (amd64, ubuntu-latest-amd64) (push) Successful in 4m13s
CI / coverage (push) Successful in 2m48s
2026-07-14 14:32:08 -04:00
andrew 7c6c116713 Add new coverage to corpus 2026-07-14 14:23:03 -04:00
andrew 5c9e9603de Add GCOVR_EXCL_LINE
CI / release (arm64, ubuntu-latest-arm64) (push) Successful in 1m51s
CI / pre-commit (push) Successful in 2m58s
CI / test (-DCMAKE_BUILD_TYPE=Debug, debug) (push) Successful in 3m32s
CI / test (-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 64-bit-versions) (push) Successful in 3m32s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (push) Successful in 3m20s
CI / test (-DUSE_SIMD_FALLBACK=ON, simd-fallback) (push) Successful in 3m36s
CI / release (amd64, ubuntu-latest-amd64) (push) Successful in 6m56s
CI / coverage (push) Successful in 4m23s
This was showing as uncovered in CI for some reason
2026-07-14 13:38:15 -04:00
andrew 862b6a4886 Port CI from main
CI / release (arm64, ubuntu-latest-arm64) (push) Successful in 1m51s
CI / pre-commit (push) Successful in 2m59s
CI / test (-DCMAKE_BUILD_TYPE=Debug, debug) (push) Successful in 8m0s
CI / test (-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1, 64-bit-versions) (push) Successful in 3m34s
CI / test (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc) (push) Successful in 3m17s
CI / test (-DUSE_SIMD_FALLBACK=ON, simd-fallback) (push) Successful in 3m30s
CI / release (amd64, ubuntu-latest-amd64) (push) Successful in 6m43s
CI / coverage (push) Failing after 3m57s
2026-07-14 12:28:08 -04:00
andrew a69dac7f16 Merge pull request 'Fix move-assignment leak and self-assignment in ConflictSet' (#55) from weaselbot/conflict-set:weaselbot/issue-54 into main
Reviewed-on: #55
2026-07-14 10:56:12 -04:00
andrew c11b4714b5 Check more preconditions in Debug mode
Tests / Clang total: 2843, passed: 2843
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / 64 bit versions total: 2843, passed: 2843
Tests / Debug total: 2841, passed: 2841
Tests / SIMD fallback total: 2843, passed: 2843
Tests / Release [gcc] total: 2843, passed: 2843
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 2119, passed: 2119
Tests / Coverage total: 2136, passed: 2136
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.92% (1834/1854) * Branch Coverage: 66.85% (1496/2238) * Complexity Density: 0.00 * Lines of Code: 1854 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-08-26 12:43:07 -07:00
andrew bc13094406 Don't violate precondition in test_conflict_set.py 2024-08-26 11:13:16 -07:00
andrew c9d742b696 Make explicit the precondition that versions must be <= latest version
Tests / Clang total: 2843, passed: 2843
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / 64 bit versions total: 2843, passed: 2843
Tests / Debug total: 2841, failed: 1, passed: 2840
Tests / SIMD fallback total: 2843, passed: 2843
Tests / Release [gcc] total: 2843, passed: 2843
Tests / Release [gcc,aarch64] total: 2119, passed: 2119
Tests / Coverage total: 2136, failed: 1, passed: 2135
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-08-24 13:43:39 -07:00
andrew 795ae7cb01 Internal.h doesn't need to include <vector> 2024-08-24 10:38:07 -07:00
andrew 849e2d3e5c Remove unused code 2024-08-22 22:31:36 -07:00
andrew 1560037680 Remove unused struct 2024-08-22 17:55:54 -07:00
andrew 764c31bbc8 Run skip list gc at 200% entry insertion rate 2024-08-22 17:26:08 -07:00
andrew ee3361952a Use better rng in skip list
low bits of lcg have low periods
2024-08-22 17:00:15 -07:00
98 changed files with 522 additions and 94 deletions
+306
View File
@@ -0,0 +1,306 @@
name: CI
on: [push, pull_request]
env:
CC: clang
CXX: clang++
jobs:
pre-commit:
runs-on: ubuntu-latest-amd64
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: /var/cache/apt/archives
key: apt-amd64-${{ hashFiles('.gitea/workflows/ci.yml') }}
- name: Install dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y git nodejs pre-commit
- uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
- name: Run pre-commit
run: |
git config --global --add safe.directory "$PWD"
pre-commit run --all-files --show-diff-on-failure
test:
strategy:
fail-fast: false
matrix:
include:
- name: 64-bit-versions
cmake_args: -DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1
- name: debug
cmake_args: -DCMAKE_BUILD_TYPE=Debug
- name: simd-fallback
cmake_args: -DUSE_SIMD_FALLBACK=ON
- name: gcc
cmake_args: -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
runs-on: ubuntu-latest-amd64
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: /var/cache/apt/archives
key: apt-amd64-${{ hashFiles('.gitea/workflows/ci.yml') }}
- name: Install common dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y \
build-essential ccache cmake gcc g++ libc6-dbg \
ninja-build python3 valgrind zstd
sudo curl -Ls "https://minio.weaselab.dev/public/$(uname -m)/mc.RELEASE.2025-08-13T08-35-41Z" \
-o /usr/local/bin/mc && sudo chmod +x /usr/local/bin/mc
- name: Install LLVM toolchain
if: matrix.name != 'gcc'
run: |
. /etc/os-release
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list
sudo apt-get update -qq
sudo apt-get install -y \
clang-21 llvm-21 lld-21 mold
for tool in clang clang++ llvm-ar llvm-nm llvm-ranlib llvm-objcopy llvm-cov llvm-symbolizer lld ld.lld; do
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-21 100
done
- name: Build
run: |
export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache"
rm -rf build
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache ${{ matrix.cmake_args }}
ninja -C build
ccache -s
- name: Test
run: |
cd build
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 -T Test -j "$(nproc)" --timeout 90 > /dev/null
- name: Upload test results to MinIO
if: always()
env:
MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
MC_HOST_minio: https://${{ secrets.MINIO_ACCESS_KEY }}:${{ secrets.MINIO_SECRET_KEY }}@minio.weaselab.dev
run: |
if [ -z "$MINIO_ACCESS_KEY" ]; then
echo "MinIO credentials not configured; skipping upload"
exit 0
fi
zstd build/Testing/*/Test.xml
mc cp build/Testing/*/Test.xml.zst "minio/jenkins/conflict-set/${{ gitea.run_number }}/${{ matrix.name }}/"
- name: Test summary
if: always()
run: |
python3 ctest_summary.py build/Testing/*/Test.xml \
--link "https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/${{ matrix.name }}/Test.xml.zst" \
| tee -a "$GITHUB_STEP_SUMMARY"
release:
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest-amd64
arch: amd64
- runner: ubuntu-latest-arm64
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: /var/cache/apt/archives
key: apt-${{ matrix.arch }}-${{ hashFiles('.gitea/workflows/ci.yml') }}
- name: Install dependencies
run: |
. /etc/os-release
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list
sudo apt-get update -qq
sudo apt-get install -y \
build-essential ccache clang-21 cmake devscripts \
libc6-dbg llvm-21 lld-21 mold ninja-build rpm \
valgrind zstd
sudo curl -Ls "https://minio.weaselab.dev/public/$(uname -m)/mc.RELEASE.2025-08-13T08-35-41Z" \
-o /usr/local/bin/mc && sudo chmod +x /usr/local/bin/mc
for tool in clang clang++ llvm-ar llvm-nm llvm-ranlib llvm-objcopy llvm-cov llvm-symbolizer lld ld.lld; do
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-21 100
done
- name: Install paper build dependencies
if: matrix.arch == 'amd64'
run: |
sudo apt-get install -y \
biber latexmk \
texlive-bibtex-extra texlive-fonts-recommended \
texlive-latex-extra texlive-pictures
- uses: actions/cache@v4
with:
path: .ccache
key: ccache-release-${{ matrix.arch }}-${{ gitea.sha }}
restore-keys: |
ccache-release-${{ matrix.arch }}-
- name: Build
run: |
export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache"
rm -rf build
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_FLAGS=-DNVALGRIND
ninja -C build
ccache -s
- name: Test
run: |
cd build
# On arm64, valgrind needs the MAKE_MEM_DEFINED client requests for
# https://git.weaselab.dev/weaselab/conflict-set/issues/39, but this
# build has -DNVALGRIND, which compiles them out. Skip valgrind
# tests here; they run annotated in the test job.
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 ${{ matrix.arch == 'arm64' && '-E valgrind' || '' }} -T Test -j "$(nproc)" --timeout 90 > /dev/null
- name: Package
run: |
cd build
cpack -G DEB
cpack -G RPM
- name: Build paper
if: matrix.arch == 'amd64'
run: |
cd paper
make
- name: Upload artifacts to MinIO
if: always()
shell: bash
env:
MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
MC_HOST_minio: https://${{ secrets.MINIO_ACCESS_KEY }}:${{ secrets.MINIO_SECRET_KEY }}@minio.weaselab.dev
run: |
if [ -z "$MINIO_ACCESS_KEY" ]; then
echo "MinIO credentials not configured; skipping upload"
exit 0
fi
dest="minio/jenkins/conflict-set/${{ gitea.run_number }}/release-${{ matrix.arch }}/"
zstd build/Testing/*/Test.xml
mc cp build/Testing/*/Test.xml.zst "$dest"
if compgen -G "build/*.deb" > /dev/null; then
mc cp build/*.deb "$dest"
fi
if compgen -G "build/*.rpm" > /dev/null; then
mc cp build/*.rpm "$dest"
fi
if compgen -G "paper/*.pdf" > /dev/null; then
mc cp paper/*.pdf "$dest"
fi
- name: Test summary
if: always()
run: |
python3 ctest_summary.py build/Testing/*/Test.xml \
--link "https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/release-${{ matrix.arch }}/Test.xml.zst" \
| tee -a "$GITHUB_STEP_SUMMARY"
coverage:
runs-on: ubuntu-latest-amd64
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: /var/cache/apt/archives
key: apt-amd64-${{ hashFiles('.gitea/workflows/ci.yml') }}
- name: Install dependencies
run: |
. /etc/os-release
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
echo "deb http://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" | sudo tee /etc/apt/sources.list.d/llvm.list
sudo apt-get update -qq
sudo apt-get install -y \
build-essential ccache clang-21 cmake gcovr \
libc6-dbg llvm-21 lld-21 mold ninja-build python3 valgrind zstd
sudo curl -Ls "https://minio.weaselab.dev/public/$(uname -m)/mc.RELEASE.2025-08-13T08-35-41Z" \
-o /usr/local/bin/mc && sudo chmod +x /usr/local/bin/mc
for tool in clang clang++ llvm-ar llvm-nm llvm-ranlib llvm-objcopy llvm-cov llvm-symbolizer lld ld.lld; do
sudo update-alternatives --install /usr/bin/${tool} ${tool} /usr/bin/${tool}-21 100
done
- uses: actions/cache@v4
with:
path: .ccache
key: ccache-coverage-${{ gitea.sha }}
restore-keys: |
ccache-coverage-
- name: Build
run: |
export CCACHE_DIR="$GITHUB_WORKSPACE/.ccache"
rm -rf build
cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage \
-DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON
ninja -C build
ccache -s
- name: Test
run: |
cd build
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 -T Test -j "$(nproc)" --timeout 90 > /dev/null
- name: Coverage report
shell: bash
run: |
gcov_args=(-f ConflictSet.cpp -f LongestCommonPrefix.h -f Metrics.h
--gcov-executable "llvm-cov gcov" --exclude-noncode-lines)
gcovr "${gcov_args[@]}" --cobertura > build/coverage.xml
gcovr "${gcov_args[@]}"
mkdir -p build/coverage_html
gcovr "${gcov_args[@]}" --html-details build/coverage_html/index.html
gcovr "${gcov_args[@]}" --fail-under-line 100 > /dev/null
- name: Upload results to MinIO
if: always()
env:
MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
MC_HOST_minio: https://${{ secrets.MINIO_ACCESS_KEY }}:${{ secrets.MINIO_SECRET_KEY }}@minio.weaselab.dev
run: |
if [ -z "$MINIO_ACCESS_KEY" ]; then
echo "MinIO credentials not configured; skipping upload"
exit 0
fi
dest="minio/jenkins/conflict-set/${{ gitea.run_number }}/coverage/"
zstd build/Testing/*/Test.xml
mc cp build/Testing/*/Test.xml.zst "$dest"
if [ -e build/coverage.xml ]; then
mc cp build/coverage.xml "$dest"
fi
if [ -d build/coverage_html ]; then
mc cp -r build/coverage_html "$dest"
fi
- name: Test summary
if: always()
run: |
python3 ctest_summary.py build/Testing/*/Test.xml \
--link "https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/coverage/Test.xml.zst" \
| tee -a "$GITHUB_STEP_SUMMARY"
echo "" | tee -a "$GITHUB_STEP_SUMMARY"
echo "📊 [Coverage report](https://minio.weaselab.dev/jenkins/conflict-set/${{ gitea.run_number }}/coverage/coverage_html/index.html)" | tee -a "$GITHUB_STEP_SUMMARY"
+1 -1
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.18) cmake_minimum_required(VERSION 3.18)
project( project(
conflict-set conflict-set
VERSION 0.0.13 VERSION 0.0.14
DESCRIPTION DESCRIPTION
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys." "A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set" HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
+31 -12
View File
@@ -2912,9 +2912,8 @@ checkMaxBetweenExclusiveImpl<true>(Node *n, int begin, int end,
// of the result will have `maxVersion` set to `writeVersion` as a // of the result will have `maxVersion` set to `writeVersion` as a
// postcondition. Nodes along the search path may be invalidated. Callers must // postcondition. Nodes along the search path may be invalidated. Callers must
// ensure that the max version of the self argument is updated. // ensure that the max version of the self argument is updated.
[[nodiscard]] [[nodiscard]] Node **insert(Node **self, std::span<const uint8_t> key,
Node **insert(Node **self, std::span<const uint8_t> key, InternalVersionT writeVersion, WriteContext *tls) {
InternalVersionT writeVersion, WriteContext *tls) {
for (; key.size() != 0; ++tls->accum.insert_iterations) { for (; key.size() != 0; ++tls->accum.insert_iterations) {
self = &getOrCreateChild(*self, key, writeVersion, tls); self = &getOrCreateChild(*self, key, writeVersion, tls);
@@ -3149,6 +3148,8 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
tls.impl = this; tls.impl = this;
int64_t check_byte_accum = 0; int64_t check_byte_accum = 0;
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
assert(reads[i].readVersion >= 0);
assert(reads[i].readVersion <= newestVersionFullPrecision);
const auto &r = reads[i]; const auto &r = reads[i];
check_byte_accum += r.begin.len + r.end.len; check_byte_accum += r.begin.len + r.end.len;
auto begin = std::span<const uint8_t>(r.begin.p, r.begin.len); auto begin = std::span<const uint8_t>(r.begin.p, r.begin.len);
@@ -3205,7 +3206,10 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
newestVersionFullPrecision = writeVersion; newestVersionFullPrecision = writeVersion;
newest_version.set(newestVersionFullPrecision); newest_version.set(newestVersionFullPrecision);
setOldestVersion(newestVersionFullPrecision - kNominalVersionWindow); if (newestVersionFullPrecision - kNominalVersionWindow >
oldestVersionFullPrecision) {
setOldestVersion(newestVersionFullPrecision - kNominalVersionWindow);
}
while (oldestExtantVersion < while (oldestExtantVersion <
newestVersionFullPrecision - kMaxCorrectVersionWindow) { newestVersionFullPrecision - kMaxCorrectVersionWindow) {
gcScanStep(1000); gcScanStep(1000);
@@ -3213,7 +3217,10 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
} else { } else {
newestVersionFullPrecision = writeVersion; newestVersionFullPrecision = writeVersion;
newest_version.set(newestVersionFullPrecision); newest_version.set(newestVersionFullPrecision);
setOldestVersion(newestVersionFullPrecision - kNominalVersionWindow); if (newestVersionFullPrecision - kNominalVersionWindow >
oldestVersionFullPrecision) {
setOldestVersion(newestVersionFullPrecision - kNominalVersionWindow);
}
} }
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
@@ -3298,12 +3305,18 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
return fuel; return fuel;
} }
void setOldestVersion(int64_t o) { void setOldestVersion(int64_t newOldestVersion) {
if (o <= oldestVersionFullPrecision) { assert(newOldestVersion >= 0);
assert(newOldestVersion <= newestVersionFullPrecision);
// If addWrites advances oldestVersion to keep within valid window, a
// subsequent setOldestVersion can be legitimately called with a version
// older than `oldestVersionFullPrecision`. < instead of <= so that we can
// do garbage collection work without advancing the oldest version.
if (newOldestVersion < oldestVersionFullPrecision) {
return; return;
} }
InternalVersionT oldestVersion{o}; InternalVersionT oldestVersion{newOldestVersion};
this->oldestVersionFullPrecision = o; this->oldestVersionFullPrecision = newOldestVersion;
this->oldestVersion = oldestVersion; this->oldestVersion = oldestVersion;
#if !USE_64_BIT #if !USE_64_BIT
InternalVersionT::zero = tls.zero = oldestVersion; InternalVersionT::zero = tls.zero = oldestVersion;
@@ -3364,6 +3377,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
} }
explicit Impl(int64_t oldestVersion) { explicit Impl(int64_t oldestVersion) {
assert(oldestVersion >= 0);
init(oldestVersion); init(oldestVersion);
initMetrics(); initMetrics();
} }
@@ -3634,7 +3648,12 @@ ConflictSet::ConflictSet(ConflictSet &&other) noexcept
: impl(std::exchange(other.impl, nullptr)) {} : impl(std::exchange(other.impl, nullptr)) {}
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept { ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
impl = std::exchange(other.impl, nullptr); if (this != &other) {
if (impl) {
internal_destroy(impl);
}
impl = std::exchange(other.impl, nullptr);
}
return *this; return *this;
} }
@@ -3758,13 +3777,13 @@ std::string getSearchPath(Node *n) {
fprintf(file, fprintf(file,
" k_%p [label=\"m=%" PRId64 " p=%" PRId64 " r=%" PRId64 " k_%p [label=\"m=%" PRId64 " p=%" PRId64 " r=%" PRId64
"\n%s\", pos=\"%d,%d!\"];\n", "\n%s\", pos=\"%d,%d!\"];\n",
(void *)n, maxVersion(n).toInt64(), (void *)n, n->parent == nullptr ? -1 : maxVersion(n).toInt64(),
n->entry.pointVersion.toInt64(), n->entry.pointVersion.toInt64(),
n->entry.rangeVersion.toInt64(), n->entry.rangeVersion.toInt64(),
getPartialKeyPrintable(n).c_str(), x, y); getPartialKeyPrintable(n).c_str(), x, y);
} else { } else {
fprintf(file, " k_%p [label=\"m=%" PRId64 "\n%s\", pos=\"%d,%d!\"];\n", fprintf(file, " k_%p [label=\"m=%" PRId64 "\n%s\", pos=\"%d,%d!\"];\n",
(void *)n, maxVersion(n).toInt64(), (void *)n, n->parent == nullptr ? -1 : maxVersion(n).toInt64(),
getPartialKeyPrintable(n).c_str(), x, y); getPartialKeyPrintable(n).c_str(), x, y);
} }
x += kSeparation; x += kSeparation;
+7 -1
View File
@@ -119,7 +119,13 @@ ConflictSet::ConflictSet(ConflictSet &&other) noexcept
: impl(std::exchange(other.impl, nullptr)) {} : impl(std::exchange(other.impl, nullptr)) {}
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept { ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
impl = std::exchange(other.impl, nullptr); if (this != &other) {
if (impl) {
impl->~Impl();
safe_free(impl, sizeof(Impl));
}
impl = std::exchange(other.impl, nullptr);
}
return *this; return *this;
} }
-1
View File
@@ -20,7 +20,6 @@ using namespace weaselab;
#include <thread> #include <thread>
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
#include <vector>
#include <callgrind.h> #include <callgrind.h>
+2 -2
View File
@@ -129,7 +129,7 @@ longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
} }
int i = 0; int i = 0;
int end; int end; // GCOVR_EXCL_LINE
// kStride * kUnrollCount at a time // kStride * kUnrollCount at a time
end = cl & ~(kStride * kUnrollFactor - 1); end = cl & ~(kStride * kUnrollFactor - 1);
@@ -182,4 +182,4 @@ longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
} }
return i; return i;
} }
+34 -23
View File
@@ -2,7 +2,16 @@ A data structure for optimistic concurrency control on ranges of bitwise-lexicog
Intended as an alternative to FoundationDB's skip list. Intended as an alternative to FoundationDB's skip list.
Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-34-34-89 1.35V RAM Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-34-34-89 1.35V RAM.
```
$ clang++ --version
Ubuntu clang version 21.1.8 (6ubuntu1)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/lib/llvm-21/bin
```
# Microbenchmark # Microbenchmark
@@ -10,44 +19,45 @@ Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-3
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
| 172.03 | 5,812,791.77 | 0.4% | 3,130.62 | 879.00 | 3.562 | 509.23 | 0.0% | 0.01 | `point reads` | 171.37 | 5,835,467.28 | 0.5% | 2,915.46 | 629.31 | 4.633 | 502.91 | 0.0% | 0.01 | `point reads`
| 167.44 | 5,972,130.71 | 0.2% | 3,065.14 | 862.27 | 3.555 | 494.30 | 0.0% | 0.01 | `prefix reads` | 168.16 | 5,946,774.62 | 0.5% | 2,859.73 | 619.03 | 4.620 | 488.59 | 0.0% | 0.01 | `prefix reads`
| 238.77 | 4,188,130.84 | 0.9% | 3,589.93 | 1,259.30 | 2.851 | 637.12 | 0.0% | 0.01 | `range reads` | 245.86 | 4,067,294.63 | 0.3% | 3,508.97 | 904.82 | 3.878 | 627.73 | 0.0% | 0.01 | `range reads`
| 424.01 | 2,358,426.70 | 0.2% | 5,620.05 | 2,242.35 | 2.506 | 854.80 | 1.7% | 0.01 | `point writes` | 381.14 | 2,623,691.76 | 0.2% | 5,127.16 | 1,402.60 | 3.655 | 830.59 | 1.7% | 0.01 | `point writes`
| 418.45 | 2,389,780.56 | 0.4% | 5,525.07 | 2,211.05 | 2.499 | 831.71 | 1.7% | 0.01 | `prefix writes` | 376.24 | 2,657,887.07 | 0.3% | 5,062.85 | 1,381.07 | 3.666 | 811.68 | 1.6% | 0.01 | `prefix writes`
| 254.87 | 3,923,568.88 | 2.6% | 3,187.01 | 1,366.50 | 2.332 | 529.11 | 2.7% | 0.02 | `range writes` | 257.09 | 3,889,688.44 | 3.8% | 3,010.09 | 949.42 | 3.170 | 521.34 | 2.9% | 0.01 | `range writes`
| 675.96 | 1,479,374.50 | 3.3% | 7,735.41 | 3,468.60 | 2.230 | 1,386.02 | 1.8% | 0.01 | `monotonic increasing point writes` | 629.71 | 1,588,026.28 | 4.0% | 6,809.60 | 2,320.32 | 2.935 | 1,247.08 | 1.3% | 0.01 | `monotonic increasing point writes`
| 137,986.20 | 7,247.10 | 0.6% | 789,752.33 | 699,462.00 | 1.129 | 144,824.14 | 0.0% | 0.01 | `worst case for radix tree` | 126,164.50 | 7,926.16 | 1.7% | 772,460.25 | 463,264.67 | 1.667 | 141,512.00 | 0.2% | 0.01 | `worst case for radix tree`
| 21.63 | 46,231,564.03 | 1.0% | 448.00 | 107.14 | 4.181 | 84.00 | 0.0% | 0.01 | `create and destroy` | 15.33 | 65,217,669.23 | 0.4% | 299.00 | 56.45 | 5.297 | 64.00 | 0.0% | 0.01 | `create and destroy`
## Radix tree (this implementation) ## Radix tree (this implementation)
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
| 11.18 | 89,455,125.34 | 0.6% | 185.37 | 57.08 | 3.248 | 41.51 | 0.4% | 0.01 | `point reads` | 12.81 | 78,088,759.03 | 0.7% | 187.38 | 47.15 | 3.974 | 33.70 | 0.5% | 0.01 | `point reads`
| 14.53 | 68,800,688.89 | 0.4% | 282.41 | 74.80 | 3.776 | 55.06 | 0.3% | 0.01 | `prefix reads` | 16.77 | 59,626,501.14 | 0.4% | 277.61 | 61.74 | 4.497 | 44.76 | 0.4% | 0.01 | `prefix reads`
| 36.54 | 27,367,576.87 | 0.2% | 798.06 | 188.90 | 4.225 | 141.69 | 0.2% | 0.01 | `range reads` | 42.45 | 23,556,818.87 | 0.4% | 856.96 | 156.09 | 5.490 | 126.22 | 0.2% | 0.01 | `range reads`
| 16.69 | 59,912,106.02 | 0.6% | 314.57 | 86.29 | 3.645 | 39.84 | 0.4% | 0.01 | `point writes` | 15.76 | 63,453,196.94 | 0.3% | 249.89 | 58.04 | 4.306 | 33.21 | 0.5% | 0.01 | `point writes`
| 30.09 | 33,235,744.07 | 0.5% | 591.33 | 155.92 | 3.793 | 82.69 | 0.2% | 0.01 | `prefix writes` | 29.71 | 33,662,499.39 | 0.1% | 509.14 | 109.33 | 4.657 | 70.26 | 0.5% | 0.01 | `prefix writes`
| 35.77 | 27,956,388.03 | 1.4% | 682.25 | 187.63 | 3.636 | 96.12 | 0.1% | 0.01 | `range writes` | 35.77 | 27,956,388.03 | 0.8% | 614.18 | 131.72 | 4.663 | 85.57 | 0.1% | 0.01 | `range writes`
| 74.04 | 13,505,408.41 | 2.7% | 1,448.95 | 392.10 | 3.695 | 260.53 | 0.1% | 0.01 | `monotonic increasing point writes` | 80.13 | 12,480,456.47 | 1.5% | 1,279.04 | 294.90 | 4.337 | 234.68 | 0.1% | 0.01 | `monotonic increasing point writes`
| 330,984.50 | 3,021.29 | 1.9% | 3,994,153.50 | 1,667,309.00 | 2.396 | 806,019.50 | 0.0% | 0.01 | `worst case for radix tree` | 360,748.00 | 2,772.02 | 2.6% | 4,401,835.00 | 1,318,754.00 | 3.338 | 728,537.00 | 0.0% | 0.01 | `worst case for radix tree`
| 92.46 | 10,814,961.65 | 0.5% | 1,800.00 | 463.41 | 3.884 | 297.00 | 0.0% | 0.01 | `create and destroy` | 90.69 | 11,027,103.45 | 1.0% | 1,686.00 | 333.80 | 5.051 | 284.00 | 0.0% | 0.01 | `create and destroy`
# "Real data" test # "Real data" test
Point queries only, best of three runs. Gc ratio is the ratio of time spent doing garbage collection to time spent adding writes or doing garbage collection. Lower is better. Point queries only. Gc ratio is the ratio of time spent doing garbage collection to time spent adding writes or doing garbage collection. Lower is better.
## skip list ## skip list
``` ```
Check: 4.47891 seconds, 364.05 MB/s, Add: 4.55599 seconds, 123.058 MB/s, Gc ratio: 37.1145% Check: 4.42779 seconds, 368.253 MB/s, Add: 4.64942 seconds, 120.586 MB/s, Gc ratio: 28.8784%, Peak idle memory: 5.51735e+06
``` ```
## radix tree ## radix tree
``` ```
Check: 0.953012 seconds, 1710.94 MB/s, Add: 1.30025 seconds, 431.188 MB/s, Gc ratio: 43.9816%, Peak idle memory: 2.28375e+06 Check: 0.891163 seconds, 1829.69 MB/s, Add: 1.23064 seconds, 455.579 MB/s, Gc ratio: 42.9417%, Peak idle memory: 2.28333e+06
``` ```
## hash table ## hash table
@@ -55,5 +65,6 @@ Check: 0.953012 seconds, 1710.94 MB/s, Add: 1.30025 seconds, 431.188 MB/s, Gc ra
(The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be) (The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be)
``` ```
Check: 0.804094 seconds, 2027.81 MB/s, Add: 0.652952 seconds, 858.645 MB/s, Gc ratio: 35.3885% Check: 0.858484 seconds, 1899.33 MB/s, Add: 0.632886 seconds, 885.868 MB/s, Gc ratio: 41.252%, Peak idle memory: 0
``` ```
+46 -42
View File
@@ -25,6 +25,7 @@
#include <algorithm> #include <algorithm>
#include <span> #include <span>
#include <vector>
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) { std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
auto result = auto result =
@@ -115,15 +116,6 @@ bool operator==(const KeyInfo &lhs, const KeyInfo &rhs) {
return !(lhs < rhs || rhs < lhs); return !(lhs < rhs || rhs < lhs);
} }
void swapSort(std::vector<KeyInfo> &points, int a, int b) {
if (points[b] < points[a]) {
KeyInfo temp;
temp = points[a];
points[a] = points[b];
points[b] = temp;
}
}
struct SortTask { struct SortTask {
int begin; int begin;
int size; int size;
@@ -183,13 +175,6 @@ void sortPoints(std::vector<KeyInfo> &points) {
} }
} }
static thread_local uint32_t g_seed = 0;
static inline int skfastrand() {
g_seed = g_seed * 1664525L + 1013904223L;
return g_seed;
}
static int compare(const StringRef &a, const StringRef &b) { static int compare(const StringRef &a, const StringRef &b) {
int c = memcmp(a.data(), b.data(), std::min(a.size(), b.size())); int c = memcmp(a.data(), b.data(), std::min(a.size(), b.size()));
if (c < 0) if (c < 0)
@@ -215,20 +200,24 @@ struct ReadConflictRange {
} }
}; };
static constexpr int MaxLevels = 26;
struct RandomLevel {
explicit RandomLevel(uint32_t seed) : seed(seed) {}
int next() {
int result = __builtin_clz(seed | (uint32_t(-1) >> (MaxLevels - 1)));
seed = seed * 1664525L + 1013904223L;
return result;
}
private:
uint32_t seed;
};
class SkipList { class SkipList {
private: private:
static constexpr int MaxLevels = 26; RandomLevel randomLevel{0};
int randomLevel() const {
uint32_t i = uint32_t(skfastrand()) >> (32 - (MaxLevels - 1));
int level = 0;
while (i & 1) {
i >>= 1;
level++;
}
assert(level < MaxLevels);
return level;
}
// Represent a node in the SkipList. The node has multiple (i.e., level) // Represent a node in the SkipList. The node has multiple (i.e., level)
// pointers to other nodes, and keeps a record of the max versions for each // pointers to other nodes, and keeps a record of the max versions for each
@@ -426,18 +415,23 @@ public:
} }
void swap(SkipList &other) { std::swap(header, other.header); } void swap(SkipList &other) { std::swap(header, other.header); }
void addConflictRanges(const Finger *fingers, int rangeCount, // Returns the change in the number of entries
Version version) { int64_t addConflictRanges(const Finger *fingers, int rangeCount,
Version version) {
int64_t result = rangeCount;
for (int r = rangeCount - 1; r >= 0; r--) { for (int r = rangeCount - 1; r >= 0; r--) {
const Finger &startF = fingers[r * 2]; const Finger &startF = fingers[r * 2];
const Finger &endF = fingers[r * 2 + 1]; const Finger &endF = fingers[r * 2 + 1];
if (endF.found() == nullptr) if (endF.found() == nullptr) {
++result;
insert(endF, endF.finger[0]->getMaxVersion(0)); insert(endF, endF.finger[0]->getMaxVersion(0));
}
remove(startF, endF); result -= remove(startF, endF);
insert(startF, version); insert(startF, version);
} }
return result;
} }
void detectConflicts(ReadConflictRange *ranges, int count, void detectConflicts(ReadConflictRange *ranges, int count,
@@ -567,9 +561,10 @@ public:
} }
private: private:
void remove(const Finger &start, const Finger &end) { // Returns the number of entries removed
int64_t remove(const Finger &start, const Finger &end) {
if (start.finger[0] == end.finger[0]) if (start.finger[0] == end.finger[0])
return; return 0;
Node *x = start.finger[0]->getNext(0); Node *x = start.finger[0]->getNext(0);
@@ -578,17 +573,20 @@ private:
if (start.finger[i] != end.finger[i]) if (start.finger[i] != end.finger[i])
start.finger[i]->setNext(i, end.finger[i]->getNext(i)); start.finger[i]->setNext(i, end.finger[i]->getNext(i));
int64_t result = 0;
while (true) { while (true) {
Node *next = x->getNext(0); Node *next = x->getNext(0);
x->destroy(); x->destroy();
++result;
if (x == end.finger[0]) if (x == end.finger[0])
break; break;
x = next; x = next;
} }
return result;
} }
void insert(const Finger &f, Version version) { void insert(const Finger &f, Version version) {
int level = randomLevel(); int level = randomLevel.next();
// std::cout << std::string((const char*)value,length) << " level: " << // std::cout << std::string((const char*)value,length) << " level: " <<
// level << std::endl; // level << std::endl;
Node *x = Node::create(f.value, level); Node *x = Node::create(f.value, level);
@@ -704,8 +702,6 @@ private:
}; };
}; };
struct SkipListConflictSet {};
struct __attribute__((visibility("hidden"))) ConflictSet::Impl { struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
Impl(int64_t oldestVersion) Impl(int64_t oldestVersion)
: oldestVersion(oldestVersion), newestVersion(oldestVersion), : oldestVersion(oldestVersion), newestVersion(oldestVersion),
@@ -775,17 +771,20 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
StringRef values[stripeSize]; StringRef values[stripeSize];
int64_t writeVersions[stripeSize / 2]; int64_t writeVersions[stripeSize / 2];
int ss = stringCount - (stripes - 1) * stripeSize; int ss = stringCount - (stripes - 1) * stripeSize;
int64_t entryDelta = 0;
for (int s = stripes - 1; s >= 0; s--) { for (int s = stripes - 1; s >= 0; s--) {
for (int i = 0; i * 2 < ss; ++i) { for (int i = 0; i * 2 < ss; ++i) {
const auto &w = combinedWriteConflictRanges[s * stripeSize / 2 + i]; const auto &w = combinedWriteConflictRanges[s * stripeSize / 2 + i];
values[i * 2] = w.first; values[i * 2] = w.first;
values[i * 2 + 1] = w.second; values[i * 2 + 1] = w.second;
keyUpdates += 3;
} }
skipList.find(values, fingers, temp, ss); skipList.find(values, fingers, temp, ss);
skipList.addConflictRanges(fingers, ss / 2, writeVersion); entryDelta += skipList.addConflictRanges(fingers, ss / 2, writeVersion);
ss = stripeSize; ss = stripeSize;
} }
// Run gc at least 200% the rate we're inserting entries
keyUpdates += std::max<int64_t>(entryDelta, 0) * 2;
} }
void setOldestVersion(int64_t oldestVersion) { void setOldestVersion(int64_t oldestVersion) {
@@ -795,7 +794,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
int temp; int temp;
std::span<const uint8_t> key = removalKey; std::span<const uint8_t> key = removalKey;
skipList.find(&key, &finger, &temp, 1); skipList.find(&key, &finger, &temp, 1);
skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 10)); skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 0));
removalArena = Arena(); removalArena = Arena();
removalKey = copyToArena( removalKey = copyToArena(
removalArena, {finger.getValue().data(), finger.getValue().size()}); removalArena, {finger.getValue().data(), finger.getValue().size()});
@@ -804,7 +803,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
int64_t totalBytes = 0; int64_t totalBytes = 0;
private: private:
int64_t keyUpdates = 10; int64_t keyUpdates = 0;
Arena removalArena; Arena removalArena;
std::span<const uint8_t> removalKey; std::span<const uint8_t> removalKey;
int64_t oldestVersion; int64_t oldestVersion;
@@ -904,7 +903,12 @@ ConflictSet::ConflictSet(ConflictSet &&other) noexcept
: impl(std::exchange(other.impl, nullptr)) {} : impl(std::exchange(other.impl, nullptr)) {}
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept { ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
impl = std::exchange(other.impl, nullptr); if (this != &other) {
if (impl) {
internal_destroy(impl);
}
impl = std::exchange(other.impl, nullptr);
}
return *this; return *this;
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Summarize a CTest Test.xml as markdown.
Intended for $GITHUB_STEP_SUMMARY in CI, where only the first few failures
are shown inline (pass --link to point at the full Test.xml). Also reusable
locally to print every failure from a downloaded Test.xml with --all.
"""
import argparse
import base64
import gzip
import xml.etree.ElementTree as ET
# Failure output is truncated to this many trailing characters, which is
# usually enough to include e.g. an ASan report's summary.
OUTPUT_TAIL_CHARS = 3000
def test_output(test):
value = test.find("./Results/Measurement/Value")
if value is None or value.text is None:
return ""
text = value.text
if value.get("encoding") == "base64":
raw = base64.b64decode(text)
if value.get("compression") == "gzip":
raw = gzip.decompress(raw)
text = raw.decode(errors="replace")
return text
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("test_xml", help="path to a ctest Testing/*/Test.xml")
parser.add_argument(
"--inline",
type=int,
default=5,
help="how many failures to show inline (default 5)",
)
parser.add_argument("--all", action="store_true", help="show every failure inline")
parser.add_argument(
"--link", help="URL of the full Test.xml, linked when failures are elided"
)
args = parser.parse_args()
testing = ET.parse(args.test_xml).getroot().find("Testing")
tests = testing.findall("Test")
failed = [t for t in tests if t.get("Status") == "failed"]
notrun = sum(1 for t in tests if t.get("Status") == "notrun")
if not failed:
print(f"✅ All {len(tests) - notrun} tests passed")
else:
print(f"{len(failed)} of {len(tests)} tests failed\n")
shown = failed if args.all else failed[: args.inline]
for test in shown:
name = test.findtext("Name")
output = test_output(test)[-OUTPUT_TAIL_CHARS:].strip()
print(f"<details><summary><code>{name}</code></summary>\n")
print("````")
print(output)
print("````")
print("</details>\n")
remaining = len(failed) - len(shown)
if remaining > 0:
more = f"… and {remaining} more"
if args.link:
more += f" — full list in [Test.xml]({args.link})"
print(more)
if notrun:
print(f"\n⚠️ {notrun} tests not run")
if __name__ == "__main__":
main()
+18 -12
View File
@@ -54,8 +54,9 @@ struct __attribute__((__visibility__("default"))) ConflictSet {
/** `end` having length 0 denotes that this range is the single key {begin}. /** `end` having length 0 denotes that this range is the single key {begin}.
* Otherwise this denotes the range [begin, end), and begin must be < end */ * Otherwise this denotes the range [begin, end), and begin must be < end */
Key end; Key end;
/** `readVersion` older than the the oldestVersion or the version of the /** `readVersion` older than the oldestVersion or the version of the
* latest call to `addWrites` minus two billion will result in `TooOld` */ * latest call to `addWrites` minus two billion will result in `TooOld`.
* Must be <= the version of the latest call to `addWrites` */
int64_t readVersion; int64_t readVersion;
}; };
@@ -72,11 +73,13 @@ struct __attribute__((__visibility__("default"))) ConflictSet {
/** Reads intersecting writes where readVersion < `writeVersion` will result /** Reads intersecting writes where readVersion < `writeVersion` will result
* in `Conflict` (or `TooOld`, eventually). `writeVersion` must be greater * in `Conflict` (or `TooOld`, eventually). `writeVersion` must be greater
* than or equal to all previous write versions. */ * than or equal to all previous write versions. Call `addWrites` with `count`
* zero to only advance the version. */
void addWrites(const WriteRange *writes, int count, int64_t writeVersion); void addWrites(const WriteRange *writes, int count, int64_t writeVersion);
/** Reads where readVersion < oldestVersion will result in `TooOld`. Must be /** Reads where readVersion < `oldestVersion` will result in `TooOld`. Must be
* greater than or equal to all previous oldest versions. */ * greater than or equal to all previous oldest versions. Must be <= the
* version of the latest call to `addWrites` */
void setOldestVersion(int64_t oldestVersion); void setOldestVersion(int64_t oldestVersion);
/** Reads where readVersion < oldestVersion will result in `TooOld`. There are /** Reads where readVersion < oldestVersion will result in `TooOld`. There are
@@ -170,8 +173,9 @@ typedef struct {
/** `end` having length 0 denotes that this range is the single key {begin}. /** `end` having length 0 denotes that this range is the single key {begin}.
* Otherwise this denotes the range [begin, end), and begin must be < end */ * Otherwise this denotes the range [begin, end), and begin must be < end */
ConflictSet_Key end; ConflictSet_Key end;
/** `readVersion` older than the the oldestVersion or the version of the /** `readVersion` older than the oldestVersion or the version of the
* latest call to `addWrites` minus two billion will result in `TooOld` */ * latest call to `addWrites` minus two billion will result in `TooOld`.
* Must be <= the version of the latest call to `addWrites` */
int64_t readVersion; int64_t readVersion;
} ConflictSet_ReadRange; } ConflictSet_ReadRange;
@@ -188,15 +192,17 @@ void ConflictSet_check(const ConflictSet *cs,
const ConflictSet_ReadRange *reads, const ConflictSet_ReadRange *reads,
ConflictSet_Result *results, int count); ConflictSet_Result *results, int count);
/** Reads intersecting writes where readVersion < `writeVersion` will result in /** Reads intersecting writes where readVersion < `writeVersion` will result
* `Conflict` (or `TooOld`, eventually). `writeVersion` must be greater than or * in `Conflict` (or `TooOld`, eventually). `writeVersion` must be greater
* equal to all previous write versions. */ * than or equal to all previous write versions. Call `addWrites` with `count`
* zero to only advance the version. */
void ConflictSet_addWrites(ConflictSet *cs, void ConflictSet_addWrites(ConflictSet *cs,
const ConflictSet_WriteRange *writes, int count, const ConflictSet_WriteRange *writes, int count,
int64_t writeVersion); int64_t writeVersion);
/** Reads where readVersion < oldestVersion will result in `TooOld`. Must be /** Reads where readVersion < `oldestVersion` will result in `TooOld`. Must be
* greater than or equal to all previous oldest versions. */ * greater than or equal to all previous oldest versions. Must be <= the
* version of the latest call to `addWrites` */
void ConflictSet_setOldestVersion(ConflictSet *cs, int64_t oldestVersion); void ConflictSet_setOldestVersion(ConflictSet *cs, int64_t oldestVersion);
/** Reads where readVersion < oldestVersion will result in `TooOld`. There are /** Reads where readVersion < oldestVersion will result in `TooOld`. There are
+1
View File
@@ -96,6 +96,7 @@ def test_inner_full_words():
def test_internal_version_zero(): def test_internal_version_zero():
with DebugConflictSet() as cs: with DebugConflictSet() as cs:
cs.addWrites(0xFFFFFFF0)
cs.setOldestVersion(0xFFFFFFF0) cs.setOldestVersion(0xFFFFFFF0)
for i in range(24): for i in range(24):
cs.addWrites(0xFFFFFFF1, write(bytes([i]))) cs.addWrites(0xFFFFFFF1, write(bytes([i])))